Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import jQuery from 'jquery';
import tabs from './common/tabs';
import filters from './common/filters';
import subscribe from './common/subscribe';
jQuery(function () {
tabs();
filters();
subscribe();
});

View File

@@ -0,0 +1,12 @@
import jQuery from 'jquery';
export default function () {
// change btn icon on click
jQuery('#advads-show-filters').on('click', function () {
const dashicons = jQuery(this).find('.dashicons');
const disabled = dashicons.hasClass('dashicons-arrow-up');
dashicons.toggleClass('dashicons-filter', disabled);
dashicons.toggleClass('dashicons-arrow-up', !disabled);
});
}

View File

@@ -0,0 +1,52 @@
export default function () {
document
.querySelectorAll('.advads-multiple-subscribe_button')
.forEach((button) => {
button.addEventListener('click', function () {
const parent = button.closest('.advads-multiple-subscribe');
const groups = Array.from(
parent.querySelectorAll(
'input[name="advads-multiple-subscribe"]:checked'
)
).map(function (input) {
return input.value;
});
if (groups.length === 0) {
return;
}
const spinner = document.createElement('span');
spinner.className = 'spinner advads-spinner';
button.insertAdjacentElement('afterend', spinner);
const formData = new FormData();
formData.append('action', 'advads-multiple-subscribe');
formData.append('groups', JSON.stringify(groups));
formData.append('nonce', advadsglobal.ajax_nonce);
fetch(ajaxurl, {
method: 'POST',
body: formData,
})
.then((response) => response.json())
.then((response) => {
button.style.display = 'none';
const message = document.createElement('p');
message.innerHTML = response.data.message;
parent.innerHTML = '';
parent.appendChild(message);
parent.classList.add('notice-success', 'notice');
})
.catch((error) => {
const message = document.createElement('p');
message.innerHTML =
error.responseJSON?.data?.message ||
'An error occurred';
parent.innerHTML = '';
parent.appendChild(message);
parent.classList.add('notice-error', 'notice');
});
});
});
}

View File

@@ -0,0 +1,29 @@
import jQuery from 'jquery';
export default function () {
const tabs = jQuery('.advads-tab-menu', '.advads-tab-container');
tabs.on('click', 'a', function (event) {
event.preventDefault();
const link = jQuery(this);
const parent = link.closest('.advads-tab-container');
const target = jQuery(link.attr('href'));
parent.find('a.is-active').removeClass('is-active');
link.addClass('is-active');
parent.find('.advads-tab-target').hide();
target.show();
});
// Trigger tab
tabs.each(function () {
const thisContainer = jQuery(this);
const { hash = false } = window.location;
let tab = thisContainer.find('a:first');
if (hash && thisContainer.find('a[href=' + hash + ']').length > 0) {
tab = thisContainer.find('a[href=' + hash + ']');
}
tab.trigger('click');
});
}

View File

@@ -0,0 +1,99 @@
const createEditor = () => {
const wrap = document.createElement('div');
wrap.id = 'qwerty';
wrap.style = `position:fixed;z-index:99999;bottom:20px;left:${document.getElementById('adminmenuwrap').clientWidth + 22}px`;
const inner = document.createElement('div');
inner.style = 'height:0;overflow:hidden;';
const content = document.createElement('div');
content.style =
'width:640px;height:322px;background-color:#f0f0f0;border:1px solid #a6a6a6;padding:20px';
const icon = document.createElement('i');
icon.style = 'position:absolute;top:-35px;left:5px;cursor:pointer';
icon.className = 'dashicons dashicons-plus-alt2';
icon.id = 'show-tester';
const desc = document.createElement('p');
desc.style = 'background-color:#fbfbfb;padding:1em';
desc.innerHTML =
'<i class="dashicons dashicons-info"></i>Please don\'t use HTML tags other than links';
const text = document.createElement('textarea');
text.style = 'resize:none;width:100%;height:210px';
const type = document.createElement('select');
type.innerHTML =
'<option value="addError">Error</option><option value="addInfo">Info</option><option value="addSuccess">Success</option>';
const label = document.createElement('label');
label.innerText = 'Type: ';
label.className = 'alignleft';
label.append(type);
const create = document.createElement('button');
create.className = 'button button-primary alignright';
create.innerText = 'Create notification';
create.addEventListener('click', () => createNotification(text.value));
const createNotification = (str) => {
if (!str.length) {
return;
}
window.advancedAds.notifications[type.value](str);
};
wrap.append(icon);
inner.append(content);
content.append(desc);
content.append(text);
content.append(label);
content.append(create);
wrap.append(inner);
let busy = false;
icon.addEventListener('click', (ev) => {
if (busy) {
return;
}
busy = true;
ev.target.classList.toggle('dashicons-plus-alt2');
ev.target.classList.toggle('dashicons-minus');
console.log(inner.clientHeight);
const anim = new Animation(
new KeyframeEffect(
inner,
{
height: inner.clientHeight === 0 ? '365px' : 0,
},
{
duration: 250,
easing: 'ease-in-out',
iterations: 1,
fill: 'forwards',
}
)
);
anim.onfinish = () => {
busy = false;
};
anim.play();
});
document.getElementById('wpwrap').append(wrap);
};
export default () => {
if (
'notifications' ===
new URLSearchParams(window.location.search).get('aa-debug')
) {
createEditor();
}
};

View File

@@ -0,0 +1,317 @@
import notificationTester from './notification-tester';
let wrapper, innerWrapper;
// Notification markup template.
const template =
'<div class="item-inner"><div class="content"><p>__MSG__</p></div><div class="sep"></div><div class="dismiss"><span class="dashicons"></span></div></div>';
// Queue animations, so we keep animating one item at a time and keep things simple.
const queue = [],
SLIDE_DURATION = 500;
/**
* Animation class
*
* @type {{addItem: notification.addItem, unlockPositions: notification.unlockPositions, endOfDismiss: notification.endOfDismiss, busy: boolean, dismiss: notification.dismiss, moveOtherItems: notification.moveOtherItems, lockPositions: notification.lockPositions, checkQueue: notification.checkQueue}}
*/
const notification = {
/**
* Animate one element at a time
*/
busy: false,
/**
* Add a notification
*
* @param {string} htmlContent the content.
* @param {string} type the type of notification.
*/
addItem: (htmlContent, type) => {
if (notification.busy) {
queue.push({
fn: notification.addItem,
args: [htmlContent, type],
});
return;
}
notification.busy = true;
const types = ['error', 'info', 'success'];
if (!types.includes(type)) {
type = 'info';
}
const item = document.createElement('div');
item.className = `item item-${type}`;
item.innerHTML = template.replace('__MSG__', htmlContent);
innerWrapper.append(item);
item.style.left = 'auto';
const anim = new Animation(
new KeyframeEffect(
item,
[{ right: `${-item.clientWidth - 30}px` }, { right: 0 }],
{
duration: SLIDE_DURATION,
easing: 'ease-in-out',
iterations: 1,
}
)
);
anim.onfinish = () => {
notification.unlockPositions();
if ('error' !== type) {
// Non-error types last only 5 seconds.
setTimeout(() => notification.dismiss(item), 5000);
}
notification.busy = false;
notification.checkQueue();
};
anim.play();
},
/**
* Go back to relative positioning
*/
unlockPositions: () => {
innerWrapper.querySelectorAll('.item').forEach((item) => {
item.style = 'position:relative;left:0;top:0;margin:0;right:auto;';
});
},
/**
* Capture offsets then switch to absolute positioning with these offsets
*/
lockPositions: () => {
const itemsOffsets = [],
items = innerWrapper.querySelectorAll('.item');
items.forEach((item) => {
itemsOffsets.push(item.offsetTop);
});
items.forEach((el, index) => {
el.style = `position:absolute;top:${itemsOffsets[index]}px;right:0;`;
});
},
/**
* If there's something queued, run the function one at a time
*/
checkQueue: () => {
if (!queue.length) {
return;
}
const fn = queue.shift();
fn.fn.apply(null, fn.args);
},
/**
* Dismiss notification
*
* @param {Node} item the notification.
*/
dismiss: (item) => {
if (notification.busy) {
queue.push({
fn: notification.dismiss,
args: [item],
});
return;
}
notification.busy = true;
if (!document.contains(item)) {
// The notification isn't in the DOM anymore (multiple clicks on the same icon).
notification.busy = false;
notification.checkQueue();
return;
}
// Collect all items that are below the one that is meing removed, then move them upward later.
const otherItems = [...innerWrapper.querySelectorAll('.item')].filter(
(elem) => {
return (
!elem.isEqualNode(item) && elem.offsetTop > item.offsetTop
);
}
);
notification.lockPositions();
const anim = new Animation(
new KeyframeEffect(
item,
[
{
right: `-${item.querySelector('.item-inner').clientWidth + 60}px`,
},
],
{
duration: SLIDE_DURATION,
easing: 'ease-in-out',
iterations: 1,
fill: 'forwards',
}
)
);
anim.onfinish = () => {
if (otherItems.length) {
notification.moveOtherItems(
item.clientHeight,
otherItems,
notification.endOfDismiss,
item
);
} else {
notification.endOfDismiss(item);
}
};
anim.play();
},
/**
* Remove the item, switch to relative positioning, check queued functions.
*
* @param {Node} item the dismissed item
*/
endOfDismiss: (item) => {
item.remove();
notification.unlockPositions();
notification.busy = false;
notification.checkQueue();
},
/**
* Move items up after dismissal of the one above them
*
* @param {number} height height of the removed item.
* @param {Array} items items that need to be moved.
* @param {Function} complete on complete callback.
* @param {Array} completeArgs arguments of the callback.
*/
moveOtherItems: (height, items, complete, completeArgs) => {
let completed = 0;
const animate = (items, index) => {
const anim = new Animation(
new KeyframeEffect(
items[index],
[{ marginTop: `-${height}px` }],
{
duration: 200,
easing: 'ease-in-out',
iterations: 1,
}
)
);
anim.onfinish = () => {
if ('function' !== typeof complete) {
return;
}
completed++;
if (items.length === completed) {
complete.call(null, completeArgs);
}
};
anim.play();
};
items.forEach((elem, index) => {
animate(items, index);
});
},
};
// Bind notification dismiss event listener.
const bindListeners = () => {
document.addEventListener('click', (event) => {
const el = event.target;
if (
el.closest('#advads-notifications') &&
el.classList &&
(el.classList.contains('dismiss') ||
(el.parentNode.classList &&
el.parentNode.classList.contains('dismiss')))
) {
notification.dismiss(el.closest('.item'));
}
});
};
/**
* Publicly available functions.
*
* @type {{addError: publicHelper.addError, addSuccess: publicHelper.addSuccess, addInfo: publicHelper.addInfo}}
*/
const publicHelper = {
/**
* Add an error notification
*
* @param {string} htmlContent the content
*/
addError: (htmlContent) => {
notification.addItem(htmlContent, 'error');
},
/**
* Add an info notification
*
* @param {string} htmlContent the content
*/
addInfo: (htmlContent) => {
notification.addItem(htmlContent, 'info');
},
/**
* Add a success notification
*
* @param {string} htmlContent the content
*/
addSuccess: (htmlContent) => {
notification.addItem(htmlContent, 'success');
},
};
/**
* Ads/Placements posts updated
*/
const addPostUpdate = () => {
const msg = document.getElementById('message');
if (msg) {
publicHelper.addSuccess(msg.querySelector('p').innerHTML);
}
const updateMessage = localStorage.getItem('advadsUpdateMessage');
if (updateMessage) {
const notice = JSON.parse(updateMessage);
notification.addItem(notice.message, notice.type);
localStorage.removeItem('advadsUpdateMessage');
}
};
/**
* Settings updated
*/
const addSettingsUpdate = () => {
const msg = document.getElementById('setting-error-settings_updated');
if (msg) {
publicHelper.addSuccess(msg.querySelector('p').innerHTML);
}
};
document.addEventListener('DOMContentLoaded', (event) => {
wrapper = document.createElement('div');
wrapper.id = 'advads-notifications';
innerWrapper = document.createElement('div');
wrapper.append(innerWrapper);
document.getElementById('wpwrap').append(wrapper);
bindListeners();
notificationTester();
addPostUpdate();
addSettingsUpdate();
// Make public function available.
window.advancedAds.notifications = publicHelper;
});

View File

@@ -0,0 +1,51 @@
import jQuery from 'jquery';
import apiFetch from '@wordpress/api-fetch';
/**
* Get disabled ads status
*
* @param {number} id
*/
function getPostData(id) {
apiFetch({
path: `/advanced-ads/v1/page_quick_edit?id=${id}&nonce=${window.advancedAds.page_quick_edit.nonce}`,
method: 'GET',
}).then(function (data) {
setQuickEditValues(id, data);
});
}
/**
* Update checkboxes states upon click on a quick edit link
*
* @param {number} id
* @param {Object} data
*/
function setQuickEditValues(id, data) {
const theRow = jQuery(`#edit-${id}`);
const disableAds = theRow.find('[name="advads-disable-ads"]');
disableAds.closest('fieldset').prop('disabled', false);
disableAds.prop('checked', Boolean(data.disable_ads));
const inContent = theRow.find('[name="advads-disable-the-content"]');
if (inContent.length) {
inContent
.prop('disabled', false)
.prop('checked', Boolean(data.disable_the_content));
}
}
jQuery(function () {
const editCopy = window.inlineEditPost.edit;
// Replace the default WP function
window.inlineEditPost.edit = function (id) {
/* eslint-enable no-undef */
// Call the original WP edit function.
editCopy.apply(this, arguments);
// Now we do our stuff.
if ('object' === typeof id) {
getPostData(parseInt(this.getId(id)));
}
};
});

View File

@@ -0,0 +1,74 @@
/* global AdvancedAdsAdmin */
import jQuery from 'jquery';
import Constants from '../../constants';
const adTypesList = jQuery('#advanced-ad-type');
const parametersBox = jQuery('#advanced-ads-ad-parameters');
const tinyMceWrapper = jQuery('#advanced-ads-tinymce-wrapper');
function loadAdTypeParameter(adType) {
adTypesList.addClass('is-list-disabled');
parametersBox.html(Constants.spinnerHTML);
tinyMceWrapper.hide();
let firstLoad = true;
jQuery
.ajax({
type: 'POST',
url: advancedAds.endpoints.ajaxUrl,
data: {
action: 'load_ad_parameters_metabox',
ad_type: adType,
ad_id: jQuery('#post_ID').val(),
nonce: advadsglobal.ajax_nonce,
},
})
.done((data) => {
if (data) {
parametersBox.html(data).trigger('paramloaded');
// eslint-disable-next-line no-undef
advads_maybe_textarea_to_tinymce(adType);
if (firstLoad) {
firstLoad = false;
setTimeout(() => {
advancedAds.termination.resetInitialValues();
}, 500);
}
}
})
.fail((MLHttpRequest, textStatus, errorThrown) => {
parametersBox.html(errorThrown);
})
.always(() => {
adTypesList.removeClass('is-list-disabled');
});
}
function adsenseSubscribeNotice() {
jQuery('input[name="advanced_ad[type]"]').on('change', function () {
const isAdsense = jQuery(this).val() === 'adsense';
jQuery('.advads-notice-adsense')
.toggleClass('block-important', isAdsense)
.toggleClass('!hidden', !isAdsense);
});
}
export default function () {
const inputs = jQuery('#advanced-ad-type input');
const metaboxHeadingElem = jQuery('#ad-types-box h2');
const metaboxTitle = metaboxHeadingElem.text();
inputs.on('change', () => {
AdvancedAdsAdmin.AdImporter.onChangedAdType();
const selected = inputs.filter(':checked');
const selectedText = selected.next('label').text();
metaboxHeadingElem.html(metaboxTitle + ': ' + selectedText);
loadAdTypeParameter(selected.val());
});
inputs.eq(0).trigger('change');
adsenseSubscribeNotice();
}

View File

@@ -0,0 +1,86 @@
export default function () {
'use strict';
let editor = null;
/**
* Check ad code and toggle warnings for shortcode or PHP.
*/
const checkSource = () => {
const text = editor
? editor.codemirror.getValue()
: jQuery('#advads-ad-content-plain').val();
const phpWarning = jQuery('#advads-parameters-php-warning');
const allowPhpWarning = jQuery('#advads-allow-php-warning');
phpWarning.hide();
allowPhpWarning.hide();
// Allow PHP is enabled
if (jQuery('#advads-parameters-php').prop('checked')) {
// ad content has opening php tag.
if (/<\?(?:php|=)/.test(text)) {
allowPhpWarning.show();
} else {
phpWarning.show();
}
}
// Shortcode warning.
jQuery('#advads-parameters-shortcodes-warning').toggle(
jQuery('#advads-parameters-shortcodes').prop('checked') &&
!/\[[^\]]+\]/.test(text)
);
};
jQuery(document).on('keyup', '#advads-ad-content-plain', checkSource);
jQuery(document).on(
'click',
'#advads-parameters-php,#advads-parameters-shortcodes',
checkSource
);
jQuery(document).on('paramloaded', '#advanced-ads-ad-parameters', () => {
let settings;
try {
settings = window.advancedAds.admin.codeMirror.settings;
} catch (Ex) {
editor = null;
return;
}
if (
'plain' !== jQuery('input[name="advanced_ad[type]"]:checked').val()
) {
editor = null;
return;
}
const source = jQuery('#advads-ad-content-plain');
if (!source.length) {
editor = null;
return;
}
editor = wp.codeEditor.initialize(source, settings);
editor.codemirror.on('keyup', checkSource);
window.advancedAds.admin.codeMirror = editor.codemirror;
window.advancedAds = window.advancedAds || {};
window.advancedAds.admin.getSourceCode = () => {
return editor
? editor.codemirror.getValue()
: jQuery('#advads-ad-content-plain').val();
};
window.advancedAds.admin.setSourceCode = (text) => {
if (editor) {
editor.codemirror.setValue(text);
} else {
jQuery('#advads-ad-content-plain').val(text);
}
};
});
}

View File

@@ -0,0 +1,10 @@
import jQuery from 'jquery';
import adTypes from './ad-types';
import placementBox from './placement-box';
import codeHighlighter from './code-highlighter';
jQuery(function () {
adTypes();
placementBox();
codeHighlighter();
});

View File

@@ -0,0 +1,72 @@
import jQuery from 'jquery';
function setOptions(placementType, options) {
if ('post_content' === placementType) {
const paragraph = prompt(advadstxt.after_paragraph_promt, 1);
if (paragraph !== null) {
return { ...options, index: parseInt(paragraph, 10) };
}
}
return options;
}
function sendAjaxRequest(placementType, placementId, postId, options) {
const advadsBox = jQuery('#advads-ad-injection-box');
jQuery.ajax({
type: 'POST',
url: ajaxurl,
data: {
action: 'advads-ad-injection-content',
placement_type: placementType,
placement_id: placementId,
ad_id: postId,
options,
nonce: advadsglobal.ajax_nonce,
},
success(r) {
if (!r) {
advadsBox.html('an error occured');
return;
}
jQuery('#advads-ad-injection-box *').hide();
jQuery(
'#advads-ad-injection-message-placement-created, #advads-ad-injection-message-placement-created *'
).show();
if ('server' === placementType) {
jQuery('.hide-server-placement').hide();
}
},
error(MLHttpRequest, textStatus, errorThrown) {
advadsBox.html(errorThrown);
},
});
}
export default function () {
const advadsBox = jQuery('#advads-ad-injection-box');
const postId = jQuery('#post_ID').val();
jQuery(document).on('click', '.advads-ad-injection-button', function () {
const placementType = jQuery(this).data('placement-type');
const placementId = jQuery(this).data('placement-id');
let options = {};
if (!placementType && !placementId) {
return;
}
options = setOptions(placementType, options);
sendAjaxRequest(placementType, placementId, postId, options);
advadsBox.find('.advads-loader').show();
advadsBox.find('.advads-ad-injection-box-placements').hide();
jQuery('body').animate(
{ scrollTop: advadsBox.offset().top - 40 },
1,
'linear'
);
});
}

View File

@@ -0,0 +1,11 @@
import jQuery from 'jquery';
function btnCustomizeFilters() {
jQuery('#advads-ad-filter-customize').on('click', function () {
jQuery('#show-settings-link').trigger('click');
});
}
export default function () {
btnCustomizeFilters();
}

View File

@@ -0,0 +1,10 @@
import jQuery from 'jquery';
import quickBulkEdit from './quick-bulk-edit';
import commonFilters from '../shared-modules/ads-placements-listing.js';
import adsFilter from './filters.js';
jQuery(function () {
quickBulkEdit();
commonFilters();
adsFilter();
});

View File

@@ -0,0 +1,96 @@
import jQuery from 'jquery';
/**
* Retrieves ad data for a given ID using AJAX.
*
* @param {number} id - The ID of the ad.
*
* @return {void}
*/
function getAdData(id) {
const adVar = `ad_json_${id}`;
const adData = window[adVar];
fillInputs(id, adData);
}
/**
* Fills the input fields in the specified row with the provided data.
*
* @param {number} id - The ID of the row.
* @param {Object} data - The data to fill the input fields with.
*
* @return {void}
*/
function fillInputs(id, data) {
const theRow = jQuery(`#edit-${id}`);
theRow.find('.advads-quick-edit').prop('disabled', false);
theRow.find('[name="debugmode"]').prop('checked', data.debug_mode);
if (data.expiry.expires) {
theRow.find('[name="enable_expiry"]').prop('checked', true);
const inputs = theRow.find('.expiry-inputs').show();
for (const key in data.expiry.expiry_date) {
inputs.find(`[name="${key}"]`).val(data.expiry.expiry_date[key]);
}
}
// Privacy module enabled.
const privacyCheckbox = theRow.find('[name="ignore_privacy"]');
if (privacyCheckbox.length) {
privacyCheckbox.prop('checked', data.ignore_privacy);
}
// Ad label.
const adLabel = theRow.find('[name="ad_label"]');
if (adLabel.length) {
adLabel.val(data.ad_label);
}
/**
* Allow add-ons to do field initialization
*/
wp.hooks.doAction('advanced-ads-quick-edit-fields-init', id, data);
}
export default function QuickBulkEdit() {
/* eslint-disable no-undef */
const editCopy = window.inlineEditPost.edit;
// Replace the default WP function
window.inlineEditPost.edit = function (id) {
/* eslint-enable no-undef */
// Call the original WP edit function.
editCopy.apply(this, arguments);
// Now we do our stuff.
if ('object' === typeof id) {
getAdData(parseInt(this.getId(id), 10));
}
};
// Show/hide expiry date inputs on bulk edit.
jQuery(document).on(
'change',
'.advads-bulk-edit [name="expiry_date"]',
function () {
const select = jQuery(this);
select
.closest('fieldset')
.find('.expiry-inputs')
.css('display', 'on' === select.val() ? 'block' : 'none');
}
);
// Show/hide expiry date inputs on quick edit.
jQuery(document).on('click', '[name="enable_expiry"]', function () {
const checkbox = jQuery(this);
checkbox
.closest('fieldset')
.find('.expiry-inputs')
.css('display', checkbox.prop('checked') ? 'block' : 'none');
});
jQuery(function () {
jQuery('.inline-edit-group select option[value="private"]').remove();
});
}

View File

@@ -0,0 +1,107 @@
/**
* Background plugin activation
*
* @param {string} plugin path to the plugin's main file relative to WP's plugins folder.
* @param {string} name the plugin name.
* @param {string} nonce ajax nonce.
* @param {Element} button the button that triggered the activation
*/
const activateAddOn = (plugin, name, nonce, button) => {
button.classList.add('disabled');
const slug = plugin.substring(plugin.indexOf('/') + 1, plugin.indexOf('.'));
wp.ajax
.post('advads_activate_addon', {
_ajax_nonce: nonce,
plugin,
slug,
name,
})
.done(function () {
button.className = 'button active disabled';
button.innerText = window.advadstxt.active;
const icon = document.createElement('i');
icon.className = 'dashicons';
icon.style.cssText = 'content:"\\f147"';
button.insertBefore(icon, button.firstChild);
})
.fail(function (response) {
if ('undefined' !== typeof response.errorMessage) {
// Error message from `wp_ajax_activate_plugin()`.
button
.closest('.cta')
.parentNode.querySelector('.description').innerText =
response.errorMessage;
}
});
};
export default function () {
const addonBox = document.getElementById('advads_overview_addons');
if (!addonBox) {
return;
}
addonBox.addEventListener('click', (ev) => {
const target = ev.target;
if (
'a' === target.tagName.toLowerCase() &&
target.classList.contains('disabled')
) {
ev.preventDefault();
return;
}
// Newsletter subscription.
if (
target.classList.contains('button') &&
target.classList.contains('subscribe')
) {
ev.preventDefault();
target.disabled = true;
target.classList.add('disabled');
wp.ajax
.post('advads_newsletter', {
nonce: target.dataset.nonce,
})
.done(function (response) {
if (response) {
target
.closest('.item-details')
.querySelector('.description').innerHTML =
`<p style="font-weight: 600;">${response}</p>`;
}
})
.fail(function (response) {
try {
target
.closest('.item-details')
.querySelector('.description').innerHTML =
`<p style="font-weight: 600;">${response.responseJSON.data.message}</p>`;
} catch (e) {}
});
}
// Background plugin activation
if (
target.classList.contains('button') &&
target.classList.contains('installed')
) {
const marker = '#activate-aaplugin_';
const href = target.href ? target.href : '';
if (-1 !== href.indexOf(marker)) {
ev.preventDefault();
const data = href.split('_');
activateAddOn(
data[2],
decodeURIComponent(data[3]),
data[1],
target
);
}
}
});
}

View File

@@ -0,0 +1,52 @@
import jQuery from 'jquery';
function createNotice(message, type, after, fadeAway = false) {
const notice = jQuery(
`<div class="notice notice-${type} is-dismissible" />`
);
notice.html(
'<div class="py-3">' + message + '</div>'
);
after.after(notice);
if (fadeAway) {
setTimeout(() => {
notice.fadeOut(500, () => {
notice.remove();
});
}, 3000);
}
jQuery(document).trigger( 'wp-notice-added');
}
export default function () {
jQuery(document).on('click', '.js-btn-backup-adstxt', function () {
const button = jQuery(this);
const wrap = button.closest('.notice');
button.prop('disabled', true);
button.html(button.data('loading'));
jQuery
.ajax({
url: advancedAds.endpoints.ajaxUrl,
method: 'POST',
data: {
action: 'pubguru_backup_ads_txt',
security: button.data('security'),
},
})
.always(() => {
button.prop('disabled', false);
button.html(button.data('text'));
})
.done((result) => {
if (result.success) {
createNotice(result.data, 'success', wrap, true);
wrap.remove();
} else {
createNotice(result.data, 'error', wrap);
wrap.remove();
}
});
});
}

View File

@@ -0,0 +1,21 @@
import jQuery from 'jquery';
import welcome from './welcome';
import addonBox from './addon-box';
import backupAdstxt from './backup-adstxt';
jQuery(() => {
welcome();
jQuery('#advads-overview').on('click', '.notice-dismiss', function (event) {
event.preventDefault();
const button = jQuery(this);
const notice = button.parent();
notice.fadeOut(500, function () {
notice.remove();
});
});
addonBox();
backupAdstxt();
});

View File

@@ -0,0 +1,15 @@
import jQuery from 'jquery';
export default function () {
jQuery(document).on('click', '#dismiss-welcome i', function () {
jQuery.ajax(window.ajaxurl, {
method: 'POST',
data: {
action: 'advads_dismiss_welcome',
},
success() {
jQuery('#welcome').remove();
},
});
});
}

View File

@@ -0,0 +1,93 @@
import jQuery from 'jquery';
const $ = jQuery;
function handleAddAdToGroup() {
const groupForm = $(this).closest('.advads-ad-group-form'),
$ad = groupForm.find('.advads-group-add-ad-list-ads option:selected'),
weightSelector = groupForm
.find('.advads-group-add-ad-list-weights')
.last(),
adTable = groupForm.find('.advads-group-ads tbody');
const adVal = $ad.val().match(/\d+/g);
let adUrl = '';
if (adVal) {
adUrl = advancedAds.endpoints.editAd + adVal.pop();
}
const statusType = $ad.data('status');
const statusString = $ad.data('status-string');
// add new row if does not already exist
if (
$ad.length &&
weightSelector.length &&
!adTable.find('[name="' + $ad.val() + '"]').length
) {
adTable.append(
$('<tr></tr>').append(
$('<td></td>').html(
`<a target="_blank" href="${adUrl}">${$ad.text()}</a>`
),
$('<td></td>').html(
`<span class="advads-help advads-help-no-icon advads-ad-status-icon advads-ad-status-icon-${statusType}">
<span class="advads-tooltip">${statusString}</span>
</span>`
),
$('<td></td>').append(
weightSelector
.clone()
.removeClass()
.val(weightSelector.val())
.prop('name', $ad.val())
),
'<td><button type="button" class="advads-remove-ad-from-group button">x</button></td>'
)
);
}
}
function handleRemoveAdFromGroupClick() {
const adRow = $(this).closest('tr');
adRow.remove();
}
function showTypeOptions(el) {
el.each(function () {
const _this = jQuery(this);
// first, hide all options except title and type
_this
.closest('.advads-ad-group-form')
.find('.advads-option:not(.static)')
.hide();
const currentType = _this.val();
// now, show only the ones corresponding with the group type
_this
.parents('.advads-ad-group-form')
.find('.advads-group-type-' + currentType)
.show();
});
}
export default function () {
// add ad to group
$('.advads-group-add-ad button').on('click', handleAddAdToGroup);
// remove ad from group
$('#advads-ad-group-list').on(
'click',
'.advads-remove-ad-from-group',
handleRemoveAdFromGroupClick
);
// handle switching of group types based on a class derrived from that type
$('.advads-ad-group-type input').on('click', function () {
showTypeOptions($(this));
});
// set default group options for each group
showTypeOptions($('.advads-ad-group-type input:checked'));
}

View File

@@ -0,0 +1,12 @@
import jQuery from 'jquery';
export default function () {
const groupFilter = jQuery('#advads-group-filter');
jQuery('#advads-show-filters').on('click', () => groupFilter.toggle());
// always show filters when reset btn exist.
if (jQuery('#advads-reset-filters').length) {
groupFilter.show();
}
}

View File

@@ -0,0 +1,214 @@
import jQuery from 'jquery';
import apiFetch from '@wordpress/api-fetch';
/**
* Disable inputs on a form
*
* @param {Node} form the form.
* @param {boolean} disabled disable inputs if `true`.
*/
function disable(form, disabled) {
if ('undefined' === typeof disabled) {
disabled = true;
}
if (!form.useableInputs) {
form.useableInputs = jQuery(form)
.closest('dialog')
.find('select,input,textarea,button,a.button')
.not(':disabled');
}
form.useableInputs.prop('disabled', disabled);
}
/**
* Edit group form
*
* @param {Node} form the form node.
*/
function submitUpdateGroup(form) {
const $form = jQuery(form),
formData = $form.serialize();
disable(form);
apiFetch({
path: '/advanced-ads/v1/group',
method: 'PUT',
data: {
fields: formData,
},
}).then(function (response) {
if (response.error) {
// Show an error message if there is an "error" field in the response
disable(form, false);
form.closest('dialog').close();
window.advancedAds.notifications.addError(response.error);
return;
}
const dialog = form.closest('dialog');
dialog.advadsTermination.resetInitialValues();
if (response.reload) {
// Reload the page if needed.
localStorage.setItem(
'advadsUpdateMessage',
JSON.stringify({
type: 'success',
message: window.advadstxt.group_forms.updated,
})
);
window.location.reload();
return;
}
window.advancedAds.notifications.addSuccess(
window.advadstxt.group_forms.updated
);
dialog.close();
});
}
/**
* Create new group
*
* @param {Node} form the form.
*/
function submitNewGroup(form) {
const $form = jQuery(form),
formData = $form.serialize();
disable(form);
apiFetch({
path: '/advanced-ads/v1/group',
method: 'POST',
data: {
fields: formData,
},
}).then(function (response) {
if (response.error) {
// Show an error message if there is an "error" field in the response
disable(form, false);
form.closest('dialog').close();
window.advancedAds.notifications.addError(response.error);
return;
}
const dialog = form.closest('dialog');
dialog.advadsTermination.resetInitialValues();
document.location.href = `#modal-group-edit-${response.group_data.id}`;
localStorage.setItem(
'advadsUpdateMessage',
JSON.stringify({
type: 'success',
message: window.advadstxt.group_forms.save_new,
})
);
document.location.reload();
});
}
export default function () {
// Stop create group form submission.
wp.hooks.addFilter(
'advanced-ads-submit-modal-form',
'advancedAds',
function (send, form) {
if ('advads-group-new-form' === form.id) {
submitNewGroup(form);
return false;
}
return send;
}
);
// Stop edit group form submission.
wp.hooks.addFilter(
'advanced-ads-submit-modal-form',
'advancedAds',
function (send, form) {
if ('update-group' === form.name) {
submitUpdateGroup(form);
return false;
}
return send;
}
);
// Add custom submit button for each edit group form.
jQuery('[id^="modal-group-edit-"]').each(function () {
jQuery(this)
.find('.tablenav.bottom')
.html(
`<button class="button button-primary submit-edit-group">${window.advadstxt.group_forms.save}</button>`
);
});
// Add custom submit button for the create group form.
jQuery('#modal-group-new')
.find('.tablenav.bottom')
.html(
`<button class="button button-primary" id="submit-new-group">${window.advadstxt.group_forms.save_new}</button>`
);
// Click on custom submit button of an edit group form.
jQuery(document).on('click', '.submit-edit-group', function () {
submitUpdateGroup(jQuery(this).closest('dialog').find('form')[0]);
});
// Click on the submit button for the create group form.
jQuery(document).on('click', '#submit-new-group', function () {
const $form = jQuery('#advads-group-new-form'),
validation = $form.closest('dialog')[0].closeValidation;
if (!window[validation.function](validation.modal_id)) {
return;
}
submitNewGroup($form[0]);
});
// Click on the delete group link.
jQuery(document).on(
'click',
'#advads-ad-group-list .delete-tag',
function (event) {
event.preventDefault();
// Confirm deletion dialog.
if (
// eslint-disable-next-line no-alert,no-undef
!confirm(
window.advadstxt.group_forms.confirmation.replace(
'%s',
jQuery(this)
.closest('div')
.siblings('.advads-table-name')
.find('a')
.text()
)
)
) {
return;
}
const queryVars = new URLSearchParams(jQuery(this).attr('href')),
tr = jQuery(this).closest('tr');
apiFetch({
path: '/advanced-ads/v1/group',
method: 'DELETE',
data: {
id: queryVars.get('group_id'),
nonce: queryVars.get('_wpnonce'),
},
}).then(function (response) {
if (response.done) {
tr.remove();
window.advancedAds.notifications.addSuccess(
window.advadstxt.group_forms.deleted
);
}
});
}
);
}

View File

@@ -0,0 +1,10 @@
import jQuery from 'jquery';
import filters from './filters';
import editGroup from './edit-group';
import formSubmission from './form-submission';
jQuery(function () {
filters();
editGroup();
formSubmission();
});

View File

@@ -0,0 +1,201 @@
import jQuery from 'jquery';
import apiFetch from '@wordpress/api-fetch';
/**
* Disable inputs on a form
*
* @param {Node} form the form.
* @param {boolean} disabled disable inputs if `true`.
*/
function disable(form, disabled) {
if ('undefined' === typeof disabled) {
disabled = true;
}
jQuery(form)
.find('select,input,textarea')
.add(
`.submit-placement-form[data-id="${form.id.replace(
'advanced-ads-placement-form-',
''
)}"]`
)
.prop('disabled', disabled);
}
/**
* Submit edit placement form
*
* @param {Node} form the form to be submitted.
*/
function submitEditPlacement(form) {
const $form = jQuery(form),
formData = $form.serialize();
disable(form);
apiFetch({
path: '/advanced-ads/v1/placement',
method: 'PUT',
data: {
fields: formData,
},
}).then(function (response) {
disable(form, false);
if (response.error) {
// Show an error message if there is a "error" field in the response
disable(form, false);
form.closest('dialog').close();
window.advancedAds.notifications.addError(response.error);
return;
}
const dialog = form.closest('dialog');
dialog.advadsTermination.resetInitialValues();
const rowTitle = jQuery(
`#post-${response.placement_data.id} .column-name .row-title`
);
rowTitle.text(response.title);
jQuery(
`#post-${response.placement_data.id} .column-ad_group .advads-placement-item-select`
).val(response.item);
if (
response.payload.post_status &&
'draft' === response.payload.post_status
) {
const rowParent = rowTitle.parent();
if (!rowParent.text().includes(advancedAds.placements.draft)) {
rowTitle
.parent()
.append(
jQuery(
`<strong>— <span class="post-state">${advancedAds.placements.draft}</span></strong>`
)
);
}
} else {
rowTitle.siblings().remove();
}
/**
* Allow add-on to update the table without refreshing the page if needed.
*/
wp.hooks.doAction('advanced-ads-placement-updated', response);
if (response.reload) {
// Reload the page if needed.
// eslint-disable-next-line no-undef
localStorage.setItem(
'advadsUpdateMessage',
JSON.stringify({
type: 'success',
message: window.advadstxt.placement_forms.updated,
})
);
window.location.reload();
return;
}
window.advancedAds.notifications.addSuccess(
window.advadstxt.placement_forms.updated
);
dialog.close();
});
}
/**
* Submit create placement form
*
* @param {Node} form the form.
*/
function submitNewPlacement(form) {
const dialog = form.closest('dialog');
if ('function' === typeof window[dialog.closeValidation.function]) {
const validForm = window[dialog.closeValidation.function](
dialog.closeValidation.modal_id
);
if (!validForm) {
return;
}
}
const formData = jQuery(form).serialize();
disable(form);
apiFetch({
path: '/advanced-ads/v1/placement',
method: 'POST',
data: {
fields: formData,
},
}).then(function (response) {
disable(form, false);
if (response.redirectUrl) {
window.location.href = response.redirectUrl;
} else if (response.reload) {
// Reload the page if needed.
// eslint-disable-next-line no-undef
localStorage.setItem(
'advadsUpdateMessage',
JSON.stringify({
type: 'success',
message: window.advadstxt.placement_forms.created,
})
);
window.location.reload();
}
});
}
// Submit edit placement form
jQuery(document).on('click', '.submit-placement-edit', function () {
submitEditPlacement(
jQuery(`#advanced-ads-placement-form-${this.dataset.id}`)[0]
);
});
// Submit new placement form
jQuery(document).on('click', '#submit-new-placement', function () {
submitNewPlacement(jQuery('#advads-placements-new-form')[0]);
});
export default function () {
// Stop normal new placement form submission.
wp.hooks.addFilter(
'advanced-ads-submit-modal-form',
'advancedAds',
function (send, form) {
if ('advads-placements-new-form' === form.id) {
submitNewPlacement(form);
return false;
}
return send;
}
);
// Stop normal edit placement form submission.
wp.hooks.addFilter(
'advanced-ads-submit-modal-form',
'advancedAds',
function (send, form) {
if (0 === form.id.indexOf('advanced-ads-placement-form-')) {
submitEditPlacement(form);
return false;
}
return send;
}
);
// Place our custom "Save and close" button to edit forms.
jQuery('[id^="advanced-ads-placement-form-"]').each(function () {
const id = this.id.replace('advanced-ads-placement-form-', '');
jQuery(`#modal-placement-edit-${id}`).find('.tablenav.bottom').html(
`<button class="button button-primary submit-placement-edit" data-id="${id}">${advadstxt.close_save}</button>` // eslint-disable-line no-undef
);
});
jQuery('#modal-placement-new').find('.tablenav.bottom').html(
`<button class="button button-primary" id="submit-new-placement">${advadstxt.save_new_placement}</button>` // eslint-disable-line no-undef
);
}

View File

@@ -0,0 +1,116 @@
/* eslint-disable no-console */
/* global localStorage */
const resetLocalStorage = () => {
const keys = [
'advads_frontend_action',
'advads_frontend_element',
'advads_frontend_picker',
'advads_prev_url',
'advads_frontend_pathtype',
'advads_frontend_boundary',
'advads_frontend_blog_id',
'advads_frontend_starttime',
];
keys.forEach((key) => localStorage.removeItem(key));
window.Advanced_Ads_Admin.set_cookie('advads_frontend_picker', '', -1);
};
export default function () {
const storageElement = localStorage.getItem('advads_frontend_element');
const frontendPicker = localStorage.getItem('advads_frontend_picker');
// Set element from frontend into placement input field
if (storageElement) {
const pickerWrapper = document.querySelector(
'[id="advads-frontend-element-' + frontendPicker + '"]'
);
pickerWrapper.querySelector('.advads-frontend-element').value =
storageElement;
if (
typeof localStorage.getItem('advads_frontend_action') !==
'undefined'
) {
// Auto-save the placement after selecting an element in the frontend.
const form = pickerWrapper.closest('form');
const formData = new FormData(form);
formData.set('nonce', advadsglobal.ajax_nonce);
formData.set('ID', formData.get('post_ID'));
wp.ajax
.post(
'advads-update-frontend-element',
Object.fromEntries(formData.entries())
)
.then(resetLocalStorage)
.fail((r) => console.error(r));
}
}
Array.from(
document.querySelectorAll('.advads-activate-frontend-picker')
).forEach(function (element) {
element.addEventListener('click', function () {
localStorage.setItem(
'advads_frontend_picker',
this.dataset.placementid
);
localStorage.setItem('advads_frontend_action', this.dataset.action);
localStorage.setItem('advads_prev_url', window.location);
localStorage.setItem(
'advads_frontend_pathtype',
this.dataset.pathtype
);
localStorage.setItem(
'advads_frontend_boundary',
this.dataset.boundary
);
localStorage.setItem(
'advads_frontend_blog_id',
window.advancedAds.siteInfo.blogId
);
localStorage.setItem(
'advads_frontend_starttime',
new Date().getTime()
);
window.Advanced_Ads_Admin.set_cookie(
'advads_frontend_picker',
this.dataset.placementid,
null
);
if (this.dataset.boundary) {
window.location =
window.advancedAds.content_placement_picker_url;
} else {
window.location = window.advancedAds.siteInfo.homeUrl;
}
});
});
// allow to deactivate frontend picker
if (frontendPicker) {
document
.querySelector(
'[id="advads-frontend-element-' + frontendPicker + '"]'
)
.querySelector('.advads-deactivate-frontend-picker').style.display =
'block';
}
Array.from(
document.querySelectorAll('.advads-deactivate-frontend-picker')
).forEach(function (element) {
element.addEventListener('click', function () {
resetLocalStorage();
Array.from(
document.querySelectorAll('.advads-deactivate-frontend-picker')
).forEach(function (item) {
item.style.display = 'none';
});
});
});
}

View File

@@ -0,0 +1,41 @@
import jQuery from 'jquery';
import filters from '../shared-modules/ads-placements-listing';
import frontendPicker from './frontend-picker';
import itemSelect from './item-select';
import formSubmission from './form-submission';
import QuickEdit from './quick-edit';
jQuery(function () {
filters();
frontendPicker();
itemSelect();
formSubmission();
QuickEdit();
newPlacement();
});
function newPlacement() {
// open modal if no placements are available.
if (jQuery('#posts-filter tr.no-items').length) {
const modal = document.querySelector('#modal-placement-new');
if (modal && typeof modal.showModal === 'function') {
modal.showModal();
}
}
}
jQuery(document).on(
'click',
'.post-type-advanced_ads_plcmnt .wp-list-table [type="checkbox"]',
() => {
jQuery(
'.post-type-advanced_ads_plcmnt .tablenav.bottom .bulkactions'
).toggleClass(
'fixed',
0 <
jQuery(
'.post-type-advanced_ads_plcmnt .check-column [type="checkbox"]:checked'
).length
);
}
);

View File

@@ -0,0 +1,61 @@
import jQuery from 'jquery';
export default function () {
jQuery('.js-update-placement-item').on('change', function () {
const select = jQuery(this);
const wrap = select.parent();
const spinner = wrap.find('.advads-loader');
const errorMessage = wrap.find('.advads-error');
select.prop('disabled', true);
spinner.removeClass('hidden');
jQuery
.ajax({
type: 'POST',
url: advancedAds.endpoints.ajaxUrl,
data: {
action: 'advads-placement-update-item',
placement_id: select.data('placement-id'),
item_id: select.val(),
},
})
.always(function () {
select.prop('disabled', false);
spinner.addClass('hidden');
})
.fail(function (response) {
window.advancedAds.notifications.addError(
response.responseJSON.data.message
);
})
.done(function (response) {
const { data } = response;
const success = wrap.find('.advads-success-message');
const modalForm = jQuery(
'#advanced-ads-placement-form-' + data.placement_id
);
const editLinks = [
wrap.find('.advads-placement-item-edit'),
modalForm.find('.advads-placement-item-edit'),
];
editLinks.forEach(function (link) {
link.attr('href', data.edit_href);
link.css(
'display',
data.edit_href === '' ? 'none' : 'inline'
);
});
modalForm
.find('.advads-placement-item-select')
.val(data.item_id);
window.advancedAds.notifications.addSuccess(
window.advadstxt.placement_forms.updated
);
});
});
}

View File

@@ -0,0 +1,62 @@
import jQuery from 'jquery';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
/**
* Fetch the placement data
*
* @param {Number} id the placement ID.
*/
const getPlacementData = (id) => {
apiFetch({
path: addQueryArgs('/advanced-ads/v1/placement', { id: id }),
method: 'GET',
}).then((data) => {
if (data.error) {
return;
}
updateInputs(id, data);
});
};
/**
* Update quick edit fields with the fetched data
*
* @param {Number} id placement ID.
* @param {Object} data placement data.
*/
const updateInputs = (id, data) => {
const row = jQuery(`#edit-${id}`);
row.find('fieldset:disabled').prop('disabled', false);
row.find('select[name="status"]').val(data.status);
// Add values to field required by the default quick edit functions.
row.find('[name="post_title"]').val(data.title);
row.find('[name="mm"]').val('01');
/**
* Allow add-ons to do field initialization
*/
wp.hooks.doAction(
'advanced-ads-quick-edit-plaacement-fields-init',
id,
data
);
};
export default () => {
/* eslint-disable no-undef */
const editCopy = window.inlineEditPost.edit;
// Replace the default WP function
window.inlineEditPost.edit = function (id) {
/* eslint-enable no-undef */
// Call the original WP edit function.
editCopy.apply(this, arguments);
// Now we do our stuff.
if ('object' === typeof id) {
getPlacementData(parseInt(this.getId(id), 10));
}
};
};

View File

@@ -0,0 +1,6 @@
import jQuery from 'jquery';
import addonBox from '../screen-dashboard/addon-box';
jQuery(() => {
addonBox();
});

View File

@@ -0,0 +1,17 @@
import jQuery from 'jquery';
import versionControl from './version-control';
jQuery(function () {
// switch import type
jQuery('.advads_import_type').on('change', function () {
if (this.value === 'xml_content') {
jQuery('#advads_xml_file').hide();
jQuery('#advads_xml_content').show();
} else {
jQuery('#advads_xml_file').show();
jQuery('#advads_xml_content').hide();
}
});
versionControl();
});

View File

@@ -0,0 +1,90 @@
/* eslint-disable no-console */
import jQuery from 'jquery';
/**
* Get usable versions. Fetch it from https://api.wordpress.org/plugins/info/1.0/advanced-ads.json if needed
*/
function getUsableVersions() {
jQuery
.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'advads_get_usable_versions',
nonce: jQuery('#version-control-nonce').val(),
},
})
.done((response) => {
const versions = [];
const versionSelect = jQuery('#plugin-version');
for (const index in response.data.order) {
const number = response.data.order[index];
const selected = 0 === index ? ' selected' : '';
versions.push(
`<option value="${
number + '|' + response.data.versions[number]
}"${selected}>${number}</option>`
);
}
versionSelect.prop('disabled', false).html(versions.join('\n'));
jQuery('#install-version').prop('disabled', false);
})
.fail((response) => {
console.error(response);
});
}
/**
* Launch the installation process
*
* @param {string} variables the form inputs values serialized.
*/
function installVersion(variables) {
const inputs = jQuery('#plugin-version,#install-version').prop(
'disabled',
true
);
const spinner = jQuery('#install-version')
.siblings('.spinner')
.css('visibility', 'visible');
jQuery
.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'advads_install_alternate_version',
vars: variables,
},
})
.done((response) => {
if (response.data.redirect) {
document.location.href = response.data.redirect;
return;
}
inputs.prop('disabled', false);
spinner.css('visibility', 'hidden');
})
.fail((response) => {
console.error(response);
inputs.prop('disabled', false);
spinner.css('visibility', 'hidden');
});
}
export default function () {
jQuery(document).on('submit', '#alternative-version', function (event) {
event.preventDefault();
installVersion(jQuery(this).serialize());
});
const pluginVersion = jQuery('#plugin-version');
if (!pluginVersion.length) {
return;
}
if (!pluginVersion.val()) {
getUsableVersions();
}
}

View File

@@ -0,0 +1,17 @@
import jQuery from 'jquery';
const toggleFilters = () => {
jQuery('.search-box').toggle();
jQuery('.tablenav.top .alignleft.actions:not(.bulkactions)').toggle();
};
const filters = () => {
jQuery('#advads-show-filters').on('click', toggleFilters);
if (jQuery('#advads-reset-filters').length) {
toggleFilters();
}
};
export default function () {
filters();
}

View File

@@ -0,0 +1,33 @@
/* eslint-disable no-console */
/* eslint-disable camelcase */
import jQuery from 'jquery';
jQuery(function () {
const element = jQuery('.advanced-ads-adsense-dashboard');
const reportNeedRefresh = element.find('.report-need-refresh');
if (reportNeedRefresh.length) {
element.html(
'<p style="text-align:center;"><span class="report-need-refresh spinner advads-ad-parameters-spinner advads-spinner"></span></p>'
);
jQuery.ajax({
type: 'POST',
url: ajaxurl,
data: {
nonce: Advanced_Ads_Adsense_Report_Helper.nonce,
type: 'domain',
filter: '',
action: 'advads_adsense_report_refresh',
},
success(response) {
if (response.success && response.data && response.data.html) {
element.html(response.data.html);
}
},
error(request, status, error) {
console.log('Refreshing report error: ' + error);
},
});
}
});

View File

@@ -0,0 +1,3 @@
export default function Divider({ className = '' }) {
return <div className={`bg-gray-200 my-8 h-[1px] ${className}`}></div>;
}

View File

@@ -0,0 +1,7 @@
export default function ErrorMessage({ message }) {
return (
<div className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400">
{message}
</div>
);
}

View File

@@ -0,0 +1,12 @@
import { wizard } from '@advancedAds/i18n';
export default function Preloader() {
return (
<div className="absolute inset-0 flex justify-center items-center z-10 bg-white bg-opacity-70">
<img
alt={wizard.processing}
src={`${advancedAds.endpoints.adminUrl}images/spinner-2x.gif`}
/>
</div>
);
}

View File

@@ -0,0 +1,56 @@
import { wizard } from '@advancedAds/i18n';
export default function SelectAccount({ accounts, tokenData, done, fail }) {
const params = new URLSearchParams(document.location.search);
const options = [
<option value="" key="select-account">
{wizard.selectAccount.optionZero}
</option>,
];
for (const id in accounts) {
options.push(
<option value={JSON.stringify(accounts[id])} key={id}>
{accounts[id].name} ({id})
</option>
);
}
const saveSelection = function (event) {
if (!event.target.value) {
return;
}
event.target.disabled = true;
const account = JSON.parse(event.target.value);
wp.ajax
.post('advads_gadsense_mapi_select_account', {
nonce: params.get('nonce'),
account,
token_data: tokenData,
})
.done(function (response) {
if ('function' === typeof done) {
done.call(null, response);
}
})
.fail(function (response) {
if ('function' === typeof fail) {
fail.call(null, response);
}
event.target.disabled = false;
});
};
return (
<>
<h2>{wizard.selectAccount.title}</h2>
<label htmlFor="g-account">
<select id="g-account" onChange={(ev) => saveSelection(ev)}>
{options}
</select>
</label>
</>
);
}

View File

@@ -0,0 +1,44 @@
/**
* External Dependencies
*/
import clx from 'classnames';
export default function CheckboxList({
id,
onChange,
options,
value = '',
isButton = false,
}) {
const wrapClassName = clx('advads-radio-list', { 'is-button': isButton });
return (
<div className={wrapClassName}>
{options.map((option) => {
const checkboxId = `checkbox-${option.value}-${id}`;
const props = {
type: 'checkbox',
id: checkboxId,
value: option.value,
checked: false,
};
if (value) {
props.checked = value.includes(option.value);
}
return (
<div className="advads-radio-list--item" key={option.value}>
<input
{...props}
onChange={() => onChange(option.value)}
/>
<label htmlFor={checkboxId}>
<span>{option.label}</span>
</label>
</div>
);
})}
</div>
);
}

View File

@@ -0,0 +1,49 @@
/**
* External Dependencies
*/
import clx from 'classnames';
export default function RadioList({
id,
onChange,
options,
value = '',
isButton = false,
className = '',
}) {
const wrapClassName = clx(
'advads-radio-list',
{ 'is-button': isButton },
className
);
return (
<div className={wrapClassName}>
{options.map((option) => {
const radioId = `radio-${option.value}-${id}`;
const props = {
type: 'radio',
id: radioId,
name: id,
value: option.value,
};
if (value) {
props.checked = value === option.value;
}
return (
<div className="advads-radio-list--item" key={option.value}>
<input
{...props}
onChange={() => onChange(option.value)}
/>
<label htmlFor={radioId}>
<span>{option.label}</span>
</label>
</div>
);
})}
</div>
);
}

View File

@@ -0,0 +1,108 @@
/* eslint-disable import/no-extraneous-dependencies */
import {
cloneElement,
memo,
useMemo,
useRef,
useState,
Children,
} from '@wordpress/element';
/**
* Internal Dependencies
*/
import WizardContext from './wizardContext';
const Wizard = memo(
({ header, footer, children, wrapper: Wrapper, startIndex = 1 }) => {
const [activeStep, setActiveStep] = useState(startIndex - 1);
const [isLoading, setIsLoading] = useState(false);
const hasNextStep = useRef(true);
const hasPreviousStep = useRef(false);
const nextStepHandler = useRef(() => {});
const stepCount = Children.toArray(children).length;
hasNextStep.current = activeStep < stepCount - 1;
hasPreviousStep.current = activeStep > 0;
const goToNextStep = useRef(() => {
if (hasNextStep.current) {
setActiveStep((newActiveStep) => newActiveStep + 1);
}
});
const goToPreviousStep = useRef(() => {
if (hasPreviousStep.current) {
nextStepHandler.current = null;
setActiveStep((newActiveStep) => newActiveStep - 1);
}
});
const goToStep = useRef((stepIndex) => {
if (stepIndex >= 0 && stepIndex < stepCount) {
nextStepHandler.current = null;
setActiveStep(stepIndex);
}
});
// Callback to attach the step handler
const handleStep = useRef((handler) => {
nextStepHandler.current = handler;
});
const doNextStep = useRef(async () => {
if (hasNextStep.current && nextStepHandler.current) {
try {
setIsLoading(true);
await nextStepHandler.current();
setIsLoading(false);
nextStepHandler.current = null;
goToNextStep.current();
} catch (error) {
setIsLoading(false);
throw error;
}
} else {
goToNextStep.current();
}
});
const wizardValue = useMemo(
() => ({
nextStep: doNextStep.current,
previousStep: goToPreviousStep.current,
handleStep: handleStep.current,
isLoading,
activeStep,
stepCount,
isFirstStep: !hasPreviousStep.current,
isLastStep: !hasNextStep.current,
goToStep: goToStep.current,
}),
[activeStep, stepCount, isLoading]
);
const activeStepContent = useMemo(() => {
const reactChildren = Children.toArray(children);
return reactChildren[activeStep];
}, [activeStep, children]);
const enhancedActiveStepContent = useMemo(
() =>
Wrapper
? cloneElement(Wrapper, { children: activeStepContent })
: activeStepContent,
[Wrapper, activeStepContent]
);
return (
<WizardContext.Provider value={wizardValue}>
{header}
{enhancedActiveStepContent}
{footer}
</WizardContext.Provider>
);
}
);
export default Wizard;

View File

@@ -0,0 +1,2 @@
export { default as useWizard } from './useWizard';
export { default as Wizard } from './Wizard';

View File

@@ -0,0 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */
import { useContext } from '@wordpress/element';
/**
* Internal Dependencies
*/
import WizardContext from './wizardContext';
export default function useWizard() {
const context = useContext(WizardContext);
if (!context) {
throw Error('Wrap your step with `Wizard`');
}
return context;
}

View File

@@ -0,0 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies */
import { createContext } from '@wordpress/element';
const WizardContext = createContext({
isLoading: false,
isFirstStep: true,
isLastStep: false,
stepCount: 0,
activeStep: 0,
});
export default WizardContext;

View File

@@ -0,0 +1,4 @@
export default {
spinnerHTML:
'<span class="spinner advads-ad-parameters-spinner advads-spinner"></span>',
};

View File

@@ -0,0 +1,24 @@
export default function AdSense(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={60}
height={60}
overflow="visible"
{...props}
>
<path
fill="#66acc7"
d="M35.861 21.053c2.025-3.465.823-7.894-2.684-9.895s-7.996-.814-10.021 2.652a9.303 9.303 0 0 0-.251.475l-6.845 11.71a8.885 8.885 0 0 0-.421.719L8.531 38.98l12.703 7.12 7.074-12.163a6.89 6.89 0 0 0 .419-.72l6.846-11.712c.1-.145.198-.296.288-.452"
/>
<path
fill="#0074a2"
d="M21.293 46.063c-2.013 3.522-6.555 4.817-10.042 2.786s-4.728-6.443-2.711-9.964 6.516-4.822 10.003-2.791 4.764 6.449 2.75 9.969"
/>
<path
fill="#3390b5"
d="M48.706 23.372a7.263 7.263 0 0 0-9.907 2.648l-7.253 12.53a7.23 7.23 0 0 0 2.636 9.873c.007.003.013.008.019.012a7.265 7.265 0 0 0 9.909-2.648l7.252-12.531a7.226 7.226 0 0 0-2.64-9.873"
/>
</svg>
);
}

View File

@@ -0,0 +1,15 @@
export default function Checkmark(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={18}
height={20}
fill="#0074a2"
overflow="visible"
viewBox="-0.191 0 18 20"
{...props}
>
<path d="M8.809 20c-.192 0-.384-.049-.556-.149L.556 15.408A1.11 1.11 0 0 1 0 14.446v-8.89a1.11 1.11 0 0 1 .555-.962L8.252.15a1.11 1.11 0 0 1 1.111 0l1.076.62a1.11 1.11 0 0 1 .407 1.518 1.11 1.11 0 0 1-1.517.407l-.52-.299-6.587 3.802v7.606l6.587 3.802 6.587-3.802V9.747a1.111 1.111 0 1 1 2.221 0v4.699a1.11 1.11 0 0 1-.555.962l-7.698 4.443c-.172.1-.364.149-.555.149zm-.002-6.111a1.65 1.65 0 0 1-.906-.269l-3.293-2.138a1.665 1.665 0 1 1 1.815-2.795l1.922 1.247L14.571.733a1.666 1.666 0 0 1 2.314-.446 1.67 1.67 0 0 1 .446 2.314l-7.143 10.556a1.663 1.663 0 0 1-1.381.732z" />
</svg>
);
}

View File

@@ -0,0 +1,14 @@
export default function Code(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={60}
height={60}
fill="#0074a2"
overflow="visible"
{...props}
>
<path d="M19.632 37.137c-.301 0-.606-.077-.889-.241l-9.279-5.351A1.79 1.79 0 0 1 8.572 30c0-.638.342-1.226.893-1.545l9.279-5.355a1.784 1.784 0 0 1 2.438.656 1.78 1.78 0 0 1-.652 2.434L13.926 30l6.604 3.806a1.787 1.787 0 0 1 .652 2.438 1.78 1.78 0 0 1-1.55.893zm20.731 0a1.776 1.776 0 0 1-1.545-.894 1.78 1.78 0 0 1 .651-2.438l6.6-3.806-6.6-3.81a1.774 1.774 0 0 1-.651-2.434 1.783 1.783 0 0 1 2.438-.656l9.278 5.355c.552.319.894.907.894 1.545s-.342 1.226-.894 1.545l-9.278 5.351a1.754 1.754 0 0 1-.893.242zm-15.276 3.144a1.82 1.82 0 0 1-.894-.237 1.786 1.786 0 0 1-.651-2.438l9.821-16.995a1.784 1.784 0 0 1 2.438-.656 1.79 1.79 0 0 1 .656 2.438l-9.821 16.994a1.78 1.78 0 0 1-1.549.894z" />
</svg>
);
}

View File

@@ -0,0 +1,15 @@
export default function Image(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={60}
height={60}
fill="#0074a2"
overflow="visible"
{...props}
>
<path d="M25.706 33.212a3.68 3.68 0 0 1-3.192 1.842 3.683 3.683 0 0 1-3.192-1.841l-1.073-1.863a3.69 3.69 0 0 0-3.195-1.843 3.68 3.68 0 0 0-3.192 1.843l-3.774 6.536a3.682 3.682 0 0 0 .002 3.687 3.68 3.68 0 0 0 3.192 1.843H48.67a3.686 3.686 0 0 0 3.192-1.843 3.69 3.69 0 0 0 .001-3.687L40.625 18.427a3.68 3.68 0 0 0-3.191-1.842 3.685 3.685 0 0 0-3.193 1.842l-8.535 14.785z" />
<circle cx={15.055} cy={20.273} r={3.686} />
</svg>
);
}

View File

@@ -0,0 +1,305 @@
export default function UpgradeBox(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
overflow="visible"
viewBox="-0.227 -0.137 93 116"
{...props}
>
<style>
{
'.B{fill:#cc3000}.C{fill:#ff6c40}.D{fill:#8c2100}.E{fill:#47456d}.F{fill:#1b193a}.G{fill:#2f2c54}.H{fill:#d9d9d9}'
}
</style>
<g fill="#0089bf">
<path d="M65.194 115.726c-.363 0-.704-.089-1.007-.264l-4.926-2.843-.004-.002-4.924-2.842-5.454-3.149c-.69-.398-1.086-1.202-1.085-2.207 0-.859.283-1.821.796-2.709l10.007-17.334c1.153-1.994 3.082-2.959 4.4-2.202l5.346 3.087.103.055 4.93 2.848 4.93 2.85c.477.273.816.738.979 1.341.274 1.018.019 2.351-.684 3.567L68.586 113.26a5.94 5.94 0 0 1-1.383 1.65 4.543 4.543 0 0 1-.445.318l-.003.001c-.3.187-.608.325-.916.407-.22.061-.437.09-.645.09zm-43.6-50.869-5.146-2.971v-4.784l.002-.056a.803.803 0 0 0-.209-.54c-.105-.136-.193-.229-.347-.244l-5.513-3.183.001-.962a7.19 7.19 0 0 0 1.985-.757c1.241-.717 1.924-1.718 1.924-2.819 0-1.537-1.655-2.846-2.604-3.406v-2.138l.77-2.401a2 2 0 0 0 .098-.616c0-.822-.516-1.591-1.417-2.11-1.55-.896-4.156-.896-5.709 0-.9.52-1.417 1.289-1.417 2.11a1.99 1.99 0 0 0 .098.616l.77 2.401v2.138c-.949.56-2.604 1.87-2.604 3.406 0 1.102.684 2.103 1.925 2.819.579.334 1.254.59 1.986.757l.001.962-5.666 3.272H.521c-.023.013-.045.028-.067.043H.453l-.013.009a.805.805 0 0 0-.264.351l-.004.012-.007.021-.002.006-.001.002-.003.009a.84.84 0 0 0-.035.21v.001l-.003 8.523a.8.8 0 0 0 .4.693l7.364 4.251 2.759 1.595a.795.795 0 0 0 .799.001l10.152-5.847a.8.8 0 0 0 .4-.693.8.8 0 0 0-.401-.681zm70.553 14.172-7.365-4.252a.84.84 0 0 0-.095-.047c-1.603-.957-2.432-2.393-3.309-3.912l-1.346-2.142 7.264-4.19a.8.8 0 0 0 0-1.386L76.59 56.919v-4.161l8.285-4.784a.8.8 0 0 0 .293-1.093L76.715 32.24l8.454-14.64a.8.8 0 0 0-.293-1.093L60.327 2.333a.8.8 0 0 0-1.093.293L57.45 5.715c-1.523-1.608-3.174-2.942-4.878-3.926C49.035-.253 45.67-.558 43.094.931L34.92 5.65l-.008.004-.07.041a6.998 6.998 0 0 0-2.257 2.165l-14.976 8.646a.8.8 0 0 0-.293 1.093l8.454 14.642-8.453 14.641a.8.8 0 0 0 .293 1.093l8.282 4.782V60.594a.8.8 0 0 0 .4.686l7.394 4.269-8.606 4.975c-1.432.828-2.007.509-2.862.016l-.023-.014c-.945-.546-2.243-1.294-4.482 0-2.027 1.17-3.017 2.885-3.974 4.543-.879 1.522-1.71 2.961-3.318 3.917a.896.896 0 0 0-.082.041L.4 84.766a.8.8 0 0 0 .4 1.493h6.564v3.453a.8.8 0 0 0 1.2.693l9.858-5.692a.937.937 0 0 0 .09-.044l9.544-5.51c.017.299.085.589.201.865l-1.92 1.108-.042.023a.797.797 0 0 0-.388.556.521.521 0 0 0-.012.299v8.342a.8.8 0 0 0 .4.693l7.364 4.251 2.759 1.595a.795.795 0 0 0 .799.001l10.152-5.847a.8.8 0 0 0 0-1.386l-5.146-2.971V85.24l8.976 5.183.003.001c.892.514 2.105-.045 2.829-1.295s.597-2.583-.292-3.098l-12.274-7.085a1.486 1.486 0 0 0-1.114-.142l-.286-.165V76.18c0-1.135-.746-2.14-1.952-2.832l4.535-2.622 8.19 4.729.023.013.016.006 8.28 4.778a.8.8 0 0 0 .8 0l8.179-4.718 8.453 4.88a.854.854 0 0 0 .134.062l7.256 4.19a.8.8 0 0 0 1.2-.693V80.52h6.564a.8.8 0 0 0 .773-.593.796.796 0 0 0-.369-.898zM36.644 5.783l-.003.001.003-.001" />
</g>
<path
fill="#992400"
d="M54.731 105.257 64.74 87.923c.912-1.581 2.394-2.436 3.307-1.91s.912 2.238 0 3.819l-10.012 17.34c-.915 1.583-2.396 2.437-3.308 1.907s-.913-2.233.004-3.822z"
/>
<path
d="M68.046 89.833c-.913 1.582-2.394 2.437-3.306 1.908s-.913-2.24 0-3.823 2.393-2.435 3.306-1.908.912 2.242 0 3.823z"
className="B"
/>
<path
fill="#992400"
d="m62.964 110.014 5.007-8.667-3.306-1.91-5.01 8.67c-.912 1.581-.909 3.288 0 3.817.916.528 2.397-.327 3.309-1.91z"
/>
<path
fill="#e54717"
d="m69.669 90.766-5.006 8.669 3.306 1.908 5.004-8.664c.907-1.582.912-3.293 0-3.824s-2.391.329-3.304 1.911z"
/>
<path
d="M67.958 101.364c-.912 1.583-2.396 2.441-3.303 1.909-.918-.524-.92-2.232-.003-3.818.909-1.579 2.396-2.438 3.306-1.908.915.527.914 2.236 0 3.817z"
className="B"
/>
<path
fill="#e54717"
d="m64.587 110.954 10.008-17.341c.915-1.582 2.396-2.434 3.308-1.907s.916 2.234.003 3.815L67.893 112.86c-.913 1.583-2.394 2.437-3.306 1.909s-.914-2.235 0-3.815z"
/>
<path
d="M66.243 114.604c-1.293.746-2.341.142-2.341-1.35s1.048-3.309 2.341-4.055c1.285-.739 2.336-.138 2.336 1.357s-1.051 3.306-2.336 4.048z"
className="B"
/>
<path
fill="#4c1200"
d="M59.656 111.923c-.909-.529-.912-2.236 0-3.817l-1.621-.934c-.915 1.583-2.396 2.437-3.308 1.907l4.929 2.844z"
/>
<path
fill="#801e00"
d="m63.042 98.499-5.008 8.674 1.621.934 5.01-8.67-1.623-.938z"
/>
<path
fill="#b22a00"
d="m69.669 90.766-5.006 8.669-1.621-.937 5.004-8.666 1.623.934z"
/>
<path
fill="#ff9d80"
d="M72.973 88.856c-.911-.527-2.392.328-3.304 1.91l-1.623-.934c.912-1.581.912-3.295 0-3.823l4.927 2.847z"
/>
<path fill="#fcbc05" d="m64.587 110.954 10.008-17.341" />
<path
fill="#ff9d80"
d="M72.973 88.856c.912.531.907 2.242 0 3.824l1.622.933c.915-1.582 2.396-2.434 3.308-1.907l-4.93-2.85z"
/>
<path
fill="#b22a00"
d="m64.587 110.954 10.008-17.341-1.622-.933-10.009 17.334 1.623.94z"
/>
<path
fill="#661800"
d="M64.587 114.77c-.914-.527-.914-2.235 0-3.816l-1.623-.94c-.912 1.583-2.394 2.438-3.309 1.91l4.932 2.846z"
/>
<path
fill="#ff6333"
d="M54.731 105.257 64.74 87.923l-5.452-3.149-10.008 17.335 5.451 3.148z"
/>
<path
fill="#4c1200"
d="M49.28 102.109c-.918 1.589-.918 3.296-.004 3.823l5.451 3.147c-.913-.526-.913-2.233.004-3.822l-5.451-3.148z"
/>
<path
fill="#ff8059"
d="M64.74 87.923c.912-1.581 2.394-2.436 3.307-1.91l-5.451-3.147c-.914-.526-2.396.328-3.308 1.908l5.452 3.149z"
/>
<g fill="#005273">
<path d="m51.25 74.764 8.312 4.797 27.334-15.768-11.097-6.406v3.203zm-18.409.707-14.729 8.504-7.365-4.252 14.729-8.504z" />
<path d="m62.264 71.219 14.729 8.504 7.365-4.252-14.729-8.504z" />
</g>
<path
d="m51.243 18.068 24.548 14.173-.001 28.346L51.24 74.76 26.692 60.588l.003-28.346z"
className="F"
/>
<path
d="M75.791 32.241 51.243 18.068l-.001 28.347z"
className="E"
/>
<g className="G">
<path d="M26.692 32.242v28.346L51.24 74.76l.002-28.345zm57.785-15.043-8.684 15.042-24.549-14.174 8.684-15.042z" />
<path d="m42.558 3.026 8.684 15.042-24.549 14.174L18.009 17.2zm17.366 58.431L51.24 46.415l24.549-14.174 8.684 15.042z" />
</g>
<path
d="m18.009 47.284 8.684-15.042 24.549 14.174-8.684 15.042z"
className="E"
/>
<path
d="M.8 85.459h7.365l-.001 4.252 9.936-5.737-7.363-4.252z"
className="C"
/>
<path
d="m42.65 69.801-7.364-4.253-9.81 5.671 7.365 4.252z"
className="B"
/>
<path
d="M91.747 79.722h-7.364v4.253l-7.363-4.253 7.362-4.252z"
className="C"
/>
<g className="D">
<path d="m59.834 69.801 7.364-4.253 2.446 1.419-7.365 4.252z" />
<path d="M69.64 66.967c1.839 1.062 2.76.532 3.679.002l-7.365 4.252c-.919.53-1.84 1.06-3.679-.002l7.365-4.252z" />
</g>
<path
d="M84.369 75.471c-1.842-1.063-2.763-2.659-3.684-4.253L73.32 75.47c.921 1.595 1.842 3.19 3.684 4.253l7.365-4.252z"
className="B"
/>
<path
d="M80.685 71.217c-.92-1.594-1.84-3.188-3.681-4.25l-7.365 4.252c1.841 1.062 2.761 2.656 3.681 4.25l7.365-4.252z"
className="D"
/>
<path
d="M77.004 66.967c-1.843-1.064-2.764-.53-3.686.002l-7.365 4.252c.922-.532 1.843-1.066 3.686-.002l7.365-4.252zm-51.528 4.252c-1.839 1.062-2.76.532-3.679.002l7.365 4.252c.919.53 1.84 1.06 3.679-.002l-7.365-4.252zm-14.729 8.504c1.842-1.063 2.763-2.659 3.684-4.253l7.365 4.252c-.921 1.595-1.842 3.19-3.684 4.253l-7.365-4.252z"
className="B"
/>
<path
d="M14.431 75.469c.92-1.594 1.84-3.188 3.681-4.25l7.365 4.252c-1.841 1.062-2.761 2.656-3.681 4.25l-7.365-4.252z"
className="D"
/>
<path
d="M18.111 71.219c1.843-1.064 2.764-.53 3.686.002l7.365 4.252c-.922-.532-1.843-1.066-3.686-.002l-7.365-4.252z"
className="C"
/>
<path
d="M35.313 6.347c-2.221 1.284-3.595 4.034-3.595 7.947 0 7.828 5.491 17.343 12.272 21.258 3.388 1.956 6.456 2.14 8.678.858l8.182-4.725c2.222-1.283 3.595-4.034 3.595-7.947s-1.373-8.248-3.593-12.093-5.29-7.206-8.679-9.163-6.458-2.141-8.678-.858l-8.182 4.723z"
className="D"
/>
<path
d="M43.99 35.551c6.777 3.912 12.272.738 12.273-7.09S50.768 11.118 43.991 7.206s-12.272-.739-12.273 7.087 5.491 17.343 12.272 21.258z"
className="B"
/>
<path
d="M52.668 16.366c2.221 3.847 3.595 8.183 3.595 12.095s-1.374 6.665-3.595 7.949l8.182-4.725c2.222-1.283 3.595-4.034 3.595-7.947s-1.373-8.248-3.593-12.093l-8.184 4.721z"
className="D"
/>
<path
d="M60.852 11.645c-2.221-3.848-5.29-7.206-8.679-9.163s-6.458-2.141-8.678-.858l-8.182 4.723c2.221-1.283 5.289-1.098 8.678.859s6.456 5.314 8.677 9.16l8.184-4.721z"
className="C"
/>
<path
fill="#f2f2f2"
d="m44.931 13.326.934.539-.922 5.912 2.855 1.645.042.025a1.04 1.04 0 0 1 .464.806c.001.128-.104.282-.104.282l-5.132 6.92-.949-.551.946-5.912-2.872-1.653-.045-.027c-.257-.147-.467-.509-.466-.805 0-.121.041-.205.099-.259l5.15-6.922z"
/>
<path
d="m45.258 13.515-4.658 6.26-.818.472 5.149-6.921z"
className="H"
/>
<path
fill="#bfbfbf"
d="M40.6 19.775a.34.34 0 0 0-.1.259 1.03 1.03 0 0 0 .466.805l-.818.472c-.257-.147-.467-.509-.466-.805 0-.121.041-.205.099-.259l.819-.472z"
/>
<path
fill="#a6a6a6"
d="m40.967 20.839 2.916 1.681-.817.472-2.918-1.68z"
/>
<path
d="m43.883 22.52-.945 5.911-.818.473.946-5.912z"
className="H"
/>
<path
fill="#a6a6a6"
d="m42.938 28.431.622.362-.492.662-.948-.551z"
/>
<path
d="m15.649 57.046-7.364-4.252L.92 57.046l7.364 4.252z"
className="E"
/>
<path
d="M.92 57.046.917 65.55l7.365 4.251.001-8.503z"
className="G"
/>
<path
d="m8.284 69.802 7.364-4.252-.001-8.502-7.363 4.25z"
className="F"
/>
<path
fill="#8c8c8c"
d="M9.578 54.292c-.03.165-.155.325-.374.451-.254.146-.587.22-.92.221v2.333a.62.62 0 0 0 .306-.074.269.269 0 0 0 .115-.118l.873-2.813z"
/>
<g fill="#b2b2b2">
<path d="M8.284 54.963c.333 0 .666-.074.92-.221.219-.126.344-.286.374-.451l.007-5.75c0 .192-.126.385-.381.532s-.586.22-.918.22l-.002 5.67zM6.99 54.292c.03.165.155.325.374.451.254.146.587.22.92.221v2.333a.62.62 0 0 1-.306-.074.269.269 0 0 1-.115-.118l-.873-2.813z" />
</g>
<path
d="M8.284 54.963c-.333 0-.666-.074-.92-.221-.219-.126-.344-.286-.374-.451l-.007-5.75c0 .192.126.385.381.532s.586.22.918.22l.002 5.67z"
className="H"
/>
<g className="D">
<path d="M8.283 44.376c-.719 0-1.37-.168-1.841-.44s-.763-.648-.762-1.063l-.808-2.52c.131.404.473.766.957 1.045.628.363 1.496.587 2.455.587v2.391z" />
<path d="M8.283 41.985c.959 0 1.827-.224 2.455-.587.483-.279.825-.641.957-1.045l-.808 2.52c0 .415-.292.791-.762 1.063s-1.122.44-1.841.44v-2.391z" />
</g>
<path
d="M8.283 41.985c-.959 0-1.827-.224-2.455-.587-.483-.279-.825-.641-.957-1.045a1.18 1.18 0 0 1-.06-.372c0-.554.389-1.055 1.017-1.417s1.497-.587 2.455-.587 1.827.224 2.455.587 1.017.864 1.017 1.417c0 .127-.021.251-.06.372-.131.404-.473.766-.957 1.045-.628.363-1.495.587-2.455.587z"
className="C"
/>
<path
d="M8.283 44.376c-.719 0-1.37-.168-1.841-.44s-.763-.648-.762-1.063v2.749c0 .415.292.791.762 1.063s1.123.44 1.841.44v-2.749z"
className="B"
/>
<path
d="M8.283 44.376c.719 0 1.37-.168 1.841-.44s.763-.648.762-1.063v2.749c0 .415-.292.791-.762 1.063s-1.122.44-1.841.44v-2.749z"
className="D"
/>
<path
d="M8.283 51.548c1.439 0 2.74-.336 3.682-.88s1.525-1.296 1.525-2.126c0-1.417-2.188-2.786-2.604-2.92 0 .415-.292.791-.762 1.063s-1.122.44-1.841.44v4.423z"
className="B"
/>
<path
d="M8.283 47.125c-.719 0-1.37-.168-1.841-.44s-.763-.648-.762-1.063c-.416.135-2.604 1.503-2.604 2.92 0 .831.583 1.583 1.525 2.126s2.245.88 3.682.88v-4.423z"
className="C"
/>
<path
d="m41.426 81.846-7.364-4.252-7.364 4.252 7.363 4.252z"
className="E"
/>
<path
d="M26.695 81.846v8.504l7.364 4.251.001-8.503z"
className="G"
/>
<path
d="m34.061 94.602 7.364-4.252-.001-8.502-7.363 4.25z"
className="F"
/>
<g className="C">
<path d="M36.578 82.317c.072.072.231.097.356.055s.167-.134.096-.206-.231-.096-.356-.055-.168.135-.096.206zm.186.342c.013.083.14.144.283.137s.249-.08.235-.163-.138-.145-.281-.137-.249.08-.237.163zm.286.432c.037-.058-.015-.124-.116-.145s-.214.009-.25.067.015.124.116.145.214-.009.25-.067zm-.89-1.037c.117.048.28.03.362-.037s.054-.162-.063-.21-.28-.031-.363.038-.054.16.064.209zm-.196-.29c.031-.102-.088-.2-.265-.218s-.346.05-.376.152.087.2.263.218.347-.05.378-.152zM36.353 82.392c-.324-.323-1.04-.435-1.601-.248-.421.141-.635.408-.577.67.002.017 0 .025-.003.042-.029.249-.405.436-.835.418a1.29 1.29 0 0 0-.563.05c-.372.125-.5.4-.285.616s.694.29 1.067.165l2.368-.789c.561-.186.752-.6.429-.924zm-1.161-3.75-1.367 1.367c-.216.215-.088.491.286.616s.852.051 1.067-.165c.102-.103.128-.218.085-.325-.029-.249.295-.466.726-.482l.073-.002c.413.03.827-.075 1.082-.276l-1.626-.939a.978.978 0 0 0-.326.206zm-5.634 3.875c-.125-.042-.168-.134-.096-.206s.231-.097.355-.055.168.133.096.205-.231.097-.355.056zm-.592-.108c-.143-.008-.249-.081-.236-.163s.139-.144.281-.137.25.08.237.163-.139.144-.282.137zm-.749-.165c.102-.022.215.008.251.067s-.016.123-.117.145-.213-.009-.25-.068.016-.123.116-.144zm1.798.514c-.083-.068-.054-.162.063-.209s.28-.031.362.037.055.162-.063.209-.28.031-.362-.037zM30.516 82.871c.177-.018.346.051.377.153s-.087.2-.264.217-.346-.05-.378-.152.088-.2.265-.218zm-.66.7c.324-.324.133-.737-.429-.924a1.905 1.905 0 0 0-1.274.041l1.626.939c.024-.02.055-.035.077-.056z" />
</g>
<g className="B">
<path d="M32.044 88.206c.098.087.2.078.226-.021s-.032-.249-.13-.335-.199-.078-.226.019.033.25.13.337zm.39.458c.077.121.193.176.259.124s.056-.192-.022-.312-.194-.177-.26-.124-.055.191.023.312zm.517.565c-.032-.098-.114-.18-.184-.183s-.099.075-.066.173.114.181.183.184.099-.076.067-.174zm-1.344-1.298c.1.037.167-.035.149-.161s-.113-.258-.214-.296-.166.034-.149.16.113.259.214.297zm-.348-.378c-.073-.163-.218-.274-.322-.251s-.129.175-.056.338.217.275.32.25.131-.175.058-.337zM31.997 88.382c-.441-.391-.896-.351-1.015.092-.089.331.037.794.291 1.171.017.024.022.037.036.064.2.381.175.771-.056.868-.112.018-.2.098-.238.236-.078.295.096.746.391 1.008s.598.233.677-.061l.501-1.868c.118-.44-.145-1.117-.587-1.51zm-3.829-5.291.5 2.446c.079.385.382.763.677.842s.47-.171.391-.556a1.292 1.292 0 0 0-.238-.512c-.229-.364-.256-.783-.055-.933.014-.011.02-.017.035-.023.232-.075.349-.352.302-.729l-1.628-.938c-.017.118-.015.253.016.403z" />
</g>
<ellipse
cx={52.472}
cy={88.225}
className="D"
rx={1.003}
ry={1.737}
transform="rotate(30.014 52.472 88.218)"
/>
<path
d="M38.357 76.536a1.47 1.47 0 0 0 .043-.357c0-1.385-1.943-2.507-4.341-2.507s-4.34 1.121-4.339 2.505c0 .122.012.242.041.359.296-1.219 2.106-2.154 4.298-2.156s4.003.938 4.298 2.156z"
className="B"
/>
<path
fill="#33a1cc"
d="M38.358 78.662c.028-.116.042-.236.042-.358 0-1.384-1.943-2.506-4.341-2.506s-4.339 1.121-4.339 2.506a1.52 1.52 0 0 0 .041.357c.296-1.217 2.107-2.153 4.298-2.154s4.003.937 4.299 2.155z"
/>
<path
fill="#66b8d9"
d="M29.761 76.536c.296-1.219 2.106-2.154 4.298-2.156s4.003.938 4.298 2.156l.043 1.768c0-1.384-1.943-2.506-4.341-2.506s-4.339 1.121-4.339 2.506l.041-1.768z"
/>
<path
d="M28.852 76.178c0 1.66 2.331 3.006 5.207 3.007s5.207-1.347 5.208-3.007-2.331-3.007-5.208-3.007-5.207 1.346-5.207 3.007zm5.207-2.506c2.397 0 4.341 1.122 4.341 2.507a1.48 1.48 0 0 1-.044.357c-.302 1.214-2.11 2.147-4.297 2.147s-3.994-.935-4.295-2.147a1.54 1.54 0 0 1-.044-.359c-.001-1.384 1.943-2.504 4.339-2.505z"
className="C"
/>
<path
d="M34.059 82.019c-2.876 0-5.207-1.346-5.207-3.006v-2.835c0 1.66 2.331 3.006 5.207 3.007v2.834z"
className="B"
/>
<path
d="M34.059 79.185c2.877 0 5.207-1.347 5.208-3.007v2.835c-.001 1.66-2.331 3.006-5.208 3.006v-2.834z"
className="D"
/>
<path
d="M37.093 79.347c-.359.622-.359 1.295 0 1.504l2.455 1.417c-.359-.208-.359-.881 0-1.504l-2.455-1.417z"
className="B"
/>
<path
d="M39.548 80.764c.359-.622.942-.959 1.302-.751l-2.455-1.418c-.359-.207-.942.129-1.302.752l2.455 1.417z"
className="C"
/>
<path
fill="#6c6a8a"
d="M37.638 77.597c-.781-.657-2.093-1.09-3.579-1.09s-2.798.432-3.579 1.09c.783.656 2.095 1.087 3.579 1.087s2.796-.431 3.579-1.087z"
/>
<path
d="M39.33 80.638c.48-.83 1.257-1.279 1.736-1.002l12.274 7.086c-.479-.276-1.256.172-1.736 1.002L39.33 80.638z"
className="C"
/>
<path
d="M51.604 87.724c-.479.831-.479 1.729 0 2.006L39.33 82.643c-.479-.277-.479-1.175 0-2.005l12.274 7.086z"
className="B"
/>
<path
fill="#005273"
d="m8.282 69.801 2.76 1.595 10.152-5.846-5.548-3.203v3.203zm25.777 24.8 2.76 1.596 10.152-5.847-5.548-3.203v3.203z"
/>
</svg>
);
}

View File

@@ -0,0 +1,4 @@
<?php
/**
* Silence is golden.
*/

View File

@@ -0,0 +1,91 @@
/**
* External Dependencies
*/
import jQuery from 'jquery';
import classnames from 'classnames';
/**
* WordPress Dependencies
*/
import { useContext } from '@wordpress/element';
/**
* Internal Dependencies
*/
import { OneClickContext } from './store/context';
import { noticeSuccess, noticeError, removeAllNotices } from './Notices';
export default function AddonRow() {
const { addonRow } = advancedAds.oneclick;
const { isConnected, toggleMetabox, disconnect } = useContext(OneClickContext);
const buttonRow = classnames('cta', {
primary: !isConnected,
secondary: isConnected,
});
const onDisconnect = () => {
jQuery.ajax({
url: advancedAds.endpoints.ajaxUrl,
type: 'POST',
data: {
action: 'pubguru_disconnect',
nonce: advancedAds.oneclick.security,
},
success: (response) => {
if (response.success) {
noticeSuccess(
response.data.message
);
disconnect();
} else {
noticeError(
response.data.message
);
}
},
error: (error) => {
noticeError(
'Error disconnecting: ' + error.statusText
);
},
})
};
return (
<div className="single-item add-on js-pubguru-connect">
<div className="item-details">
<div className="icon">
<img src={addonRow.icon} alt="" />
</div>
<span></span>
<div className="name">{addonRow.title}</div>
<span></span>
<div className="description">{addonRow.content}</div>
<span></span>
<div className={buttonRow}>
{isConnected ? (
<button
className="button"
onClick={onDisconnect}
>
<i className="dashicons dashicons-dismiss"></i>
{addonRow.disconnect}
</button>
) : (
<button
className="button"
onClick={() => {
removeAllNotices();
toggleMetabox(true);
}}
>
<i className="dashicons dashicons-plus"></i>
{addonRow.connect}
</button>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
/**
* WordPress Dependencies
*/
import { createPortal } from '@wordpress/element';
/**
* Internal Dependencies
*/
import AddonRow from './AddonRow';
import Metabox from './Metabox';
import Notices from './Notices';
import StoreProvider from './store/context';
function App() {
return (
<StoreProvider>
{createPortal(
<AddonRow />,
document.getElementById('advads-oneclick-addon-row')
)}
<div className="mb-4">
<Notices />
</div>
<Metabox />
</StoreProvider>
);
}
export default App;

View File

@@ -0,0 +1,63 @@
/**
* WordPress Dependencies
*/
import { useContext } from '@wordpress/element';
import { OneClickContext } from './store/context';
/**
* Internal Dependencies
*/
import Step1 from './Step1';
import Step2 from './Step2';
import Step3 from './Step3';
import StepWrapper from './StepWrapper';
export default function Metabox() {
const { showMetabox, currentStep } = useContext(OneClickContext);
const { metabox } = advancedAds.oneclick;
if (!showMetabox) {
return null;
}
return (
<div id="advads-m2-connect" className="postbox position-full">
<h2 className="hndle">
<span>{metabox.title}</span>
</h2>
<div className="inside">
{1 === currentStep && (
<StepWrapper title={advancedAds.oneclick.step1.title}>
<Step1 />
</StepWrapper>
)}
{2 === currentStep && (
<StepWrapper title={advancedAds.oneclick.step2.title}>
<Step2 />
</StepWrapper>
)}
{3 === currentStep && (
<StepWrapper>
<Step3 />
</StepWrapper>
)}
<footer>
<a
href={metabox.visitLink}
target="_blank"
rel="noreferrer"
>
{metabox.visitText}
<span className="screen-reader-text">
{' '}
(opens in a new tab)
</span>
<span
aria-hidden="true"
className="dashicons dashicons-external"
></span>
</a>
</footer>
</div>
</div>
);
}

View File

@@ -0,0 +1,235 @@
/**
* External Dependencies
*/
import jQuery from 'jquery';
import classnames from 'classnames';
/**
* WordPress Dependencies
*/
import Select from './Select2';
import { useContext, useEffect, useRef, useState } from '@wordpress/element';
/**
* Internal Dependencies
*/
import { OneClickContext } from './store/context';
const selectOptions = {
minimumInputLength: 3,
dropdownParent: '#modal-id',
ajax: {
url: advancedAds.endpoints.ajaxUrl,
dataType: 'json',
delay: 250,
data: (params) => {
return {
q: params.term,
action: 'search_posts',
security: advancedAds.oneclick.security,
};
},
processResults(data) {
return {
results: data,
};
},
}
};
export default function Modal({ open, onClose }) {
const dialogRef = useRef(null);
const [updating, setUpdating] = useState(false);
const [saved, setSaved] = useState(false);
const { modal } = advancedAds.oneclick;
const { selectedMethod, selectedPage, selectedPageTitle, setMethod, setPage } = useContext(OneClickContext);
const [localMethod, setLocalMethod] = useState(selectedMethod);
const [localPage, setLocalPage] = useState(selectedPage);
useEffect(() => {
if (open) {
dialogRef.current.showModal();
} else {
dialogRef.current.close();
}
}, [open]);
const selectData = selectedPage ? [{ value: selectedPage, label: selectedPageTitle }] : [];
const isDisabled = 'final' === localMethod || ! localPage;
const updatePreview = () => {
setUpdating(true);
setSaved(true);
jQuery.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'update_oneclick_preview',
security: advancedAds.oneclick.security,
method: localMethod,
page: localPage,
},
}).complete(() => {
setUpdating(false);
setMethod(localMethod);
setPage(localPage);
});
}
const finalImport = () => {
setSaved(true);
jQuery.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'update_oneclick_preview',
security: advancedAds.oneclick.security,
method: localMethod,
},
}).complete(() => {
setUpdating(false);
setMethod(localMethod);
setPage(0);
});
}
const updateAndClose = () => {
if (saved) {
setSaved(false);
onClose();
return;
}
if ('page' === localMethod) {
updatePreview();
} else if ('final' === localMethod) {
finalImport();
}
setSaved(false);
onClose();
}
const finalTextClass = classnames('ml-7', {
'import-active': 'final' === localMethod,
'text-[#a7aaad]': 'final' !== localMethod,
});
const finalButtonClass = classnames('button button-primary advads-modal-close-action', {
'button-primary': 'final' !== localMethod,
'!bg-[#cc3000] !border-[#cc3000] !shadow-none': 'final' === localMethod,
});
return (
<dialog id="modal-id" className="advads-modal" ref={dialogRef}>
<a
href="#close"
className="advads-modal-close-background"
onClick={onClose}
>
{advancedAds.oneclick.btnClose}
</a>
<div className="advads-modal-content">
<div className="advads-modal-header">
<a
href="#close"
className="advads-modal-close"
title={advancedAds.oneclick.btnCancel}
onClick={onClose}
>
&times;
</a>
<h3>{modal.title}</h3>
</div>
<div className="advads-modal-body">
<div className="flex gap-x-8">
<div>
<strong>{modal.labelImport}</strong>
</div>
<div>
<div className="mb-5">
<label htmlFor="specific-page">
<input
type="radio"
name="import-methods"
id="specific-page"
value="page"
checked={'page' === localMethod}
onChange={() => setLocalMethod('page')}
/>
<span className="pl-1">
{modal.labelSpecificPage}
</span>
</label>
<div className="ml-7 mt-6">
<div>
<Select
defaultValue={localPage}
data={selectData}
options={selectOptions}
style={{ width: '100%' }}
disabled={'final' === localMethod}
onChange={(value) => setLocalPage(value.target.value)}
/>
</div>
<p className="buttons-set">
<button className="button button-primary" disabled={isDisabled} onClick={updatePreview}>
{modal.btnUpdate}
</button>
<a href={`${advancedAds.siteInfo.homeUrl}/?p=${selectedPage}`} target="_blank" className="button button-secondary !flex items-center gap-x-1" disabled={isDisabled}>
<span>{modal.btnGoto}</span>
<span className="dashicons dashicons-external"></span>
</a>
{updating && <img src={advancedAds.oneclick.spinner} alt="" className="h-[11px]" />}
</p>
</div>
</div>
<div>
<label htmlFor="final-import">
<input
type="radio"
name="import-methods"
id="final-import"
value="final"
checked={'final' === localMethod}
onChange={() => setLocalMethod('final')}
/>
<span className="pl-1">
{modal.labelFinalImport}
</span>
</label>
<div
className={finalTextClass}
dangerouslySetInnerHTML={{
__html: modal.descFinalImport,
}}
/>
</div>
</div>
</div>
</div>
<div className="advads-modal-footer">
<div className="tablenav bottom">
<a
href="#close"
className="button button-secondary advads-modal-close"
onClick={onClose}
>
{advancedAds.oneclick.btnCancel}
</a>
<button
type="submit"
form=""
className={finalButtonClass}
onClick={updateAndClose}
>
{'final' === localMethod ? modal.btnFinal : modal.btnSave}
</button>
</div>
</div>
</div>
</dialog>
);
}

View File

@@ -0,0 +1,93 @@
/**
* External Dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { dispatch, useSelect, useDispatch } from '@wordpress/data';
export function removeAllNotices() {
dispatch(noticesStore).removeAllNotices();
}
export function noticeSuccess(message, options) {
dispatch(noticesStore).createSuccessNotice(message, options);
}
export function noticeError(message, options) {
dispatch(noticesStore).createErrorNotice(message, options);
}
function Notice({ status, isDismissible, actions, onRemove, children, type }) {
const wrapperClasses = classnames(
'flex items-center justify-between notice notice-alt !px-3',
`notice-${status}`,
{ 'is-dismissible': isDismissible }
);
useEffect(() => {
if ('timeout' !== type) {
return;
}
const timeId = setTimeout(() => {
onRemove();
}, 5000);
// Cleanup function to clear the timeout
return () => clearTimeout(timeId);
}, []); // Empty array means it will only run on mount
return (
<div className={wrapperClasses}>
<div className="py-3" dangerouslySetInnerHTML={{ __html: children }} />
{actions.map((action, index) => {
return (
<button
key={index}
className="button button-primary !ml-auto !mr-2"
onClick={(event) => action.onClick(event, onRemove)}
>
{action.label}
</button>
)
})}
{ isDismissible && (
<button
className="button-link !no-underline"
onClick={ onRemove }
>
<span className="dashicons dashicons-no-alt"></span>
</button>
) }
</div>
)
}
/**
* Renders the notices
*
* @return {React.ReactNode} The rendered component.
*/
export default function Notices() {
const notices = useSelect(
( select ) => select( noticesStore ).getNotices(),
[]
);
const { removeNotice } = useDispatch( noticesStore );
return (
<>
{notices.map( ( notice ) => {
return (
<Notice key={notice.id} onRemove={() => removeNotice(notice.id)} {...notice}>
{notice.content}
</Notice>
)
})}
</>
);
}

View File

@@ -0,0 +1,135 @@
/**
* External Dependencies
*/
import 'select2';
import jQuery from 'jquery';
import shallowEqualFuzzy from 'shallow-equal-fuzzy';
/**
* WordPress Dependencies
*/
import { useEffect, useRef, useState, forwardRef } from '@wordpress/element';
const namespace = 'react-select2';
const Select2 = forwardRef((props, ref) => {
const {
defaultValue = '',
value: propValue,
data = [],
events = [
[`change.${namespace}`, 'onChange'],
[`select2:open.${namespace}`, 'onOpen'],
[`select2:close.${namespace}`, 'onClose'],
[`select2:select.${namespace}`, 'onSelect'],
[`select2:unselect.${namespace}`, 'onUnselect'],
],
options = {},
multiple = false,
onChange,
onOpen,
onClose,
onSelect,
onUnselect,
...rest
} = props;
const selectRef = ref || useRef(null);
const [internalValue, setInternalValue] = useState(propValue || defaultValue);
useEffect(() => {
const $el = jQuery(selectRef.current);
// Initialize select2
$el.select2(prepareOptions(options));
attachEventHandlers($el);
// Set initial value
updateSelect2Value($el, internalValue);
return () => {
// Cleanup: destroy select2 instance
detachEventHandlers($el);
$el.select2('destroy');
};
}, []);
useEffect(() => {
const $el = jQuery(selectRef.current);
// Update select2 options if they change
$el.select2(prepareOptions(options));
// Update value if propValue changes
if (propValue !== undefined && !fuzzyValuesEqual($el.val(), propValue)) {
updateSelect2Value($el, propValue);
}
}, [propValue, options]);
const prepareOptions = options => {
const opt = { ...options };
if (typeof opt.dropdownParent === 'string') {
opt.dropdownParent = jQuery(opt.dropdownParent);
}
return opt;
};
const attachEventHandlers = $el => {
const handlers = { onChange, onOpen, onClose, onSelect, onUnselect };
events.forEach(([event, handlerName]) => {
if (handlers[handlerName]) {
$el.on(event, handlers[handlerName]);
}
});
};
const detachEventHandlers = $el => {
events.forEach(([event]) => {
$el.off(event);
});
};
const updateSelect2Value = ($el, value) => {
$el.off(`change.${namespace}`).val(value).trigger('change');
if (onChange) {
$el.on(`change.${namespace}`, onChange);
}
};
const fuzzyValuesEqual = (currentValue, newValue) => {
return (currentValue === null && newValue === '') || shallowEqualFuzzy(currentValue, newValue);
};
const makeOption = item => {
if (typeof item === 'object') {
const { value, label, ...itemParams } = item;
return (
<option key={`option-${value}`} value={value} {...itemParams}>
{label}
</option>
);
}
return (
<option key={`option-${item}`} value={item}>
{item}
</option>
);
};
return (
<select ref={selectRef} {...rest}>
{data.map((item, index) =>
item.children ? (
<optgroup key={`optgroup-${index}`} label={item.label} {...item}>
{item.children.map(child => makeOption(child))}
</optgroup>
) : (
makeOption(item)
)
)}
</select>
);
});
export default Select2;

View File

@@ -0,0 +1,160 @@
/**
* External Dependencies
*/
import jQuery from 'jquery';
/**
* WordPress Dependencies
*/
import { useContext } from '@wordpress/element';
/**
* Internal Dependencies
*/
import { OneClickContext } from './store/context';
import { noticeSuccess, noticeError } from './Notices';
import actions from './store/actions';
function updateSetting(module, status) {
return jQuery.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'pubguru_module_change',
security: advancedAds.oneclick.security,
module,
status,
},
});
}
function backupAdsTxt() {
return jQuery.ajax({
url: ajaxurl,
method: 'POST',
data: {
action: 'pubguru_backup_ads_txt',
security: advancedAds.oneclick.security,
},
});
}
function Setting({ id, label, className, option, disabled = false, children }) {
const { settings, updateSettings } = useContext(OneClickContext);
const checked = settings[option] ?? false;
const name = id.replace(/-/g, '_').replace('pubguru_', '');
const onChange = (event) => {
const status = event.target.checked
updateSetting(name, status)
.done((response) => {
if ( response.data.notice && '' !== response.data.notice ) {
noticeError(
response.data.notice,
response.data.action ? {
actions: [
{
label: response.data.action,
onClick: (event, remove) => {
event.target.disabled = true;
backupAdsTxt()
.done((response) => {
remove();
event.target.disabled = false;
if ( response.success ) {
noticeSuccess(response.data);
} else {
noticeError(response.data);
}
})
.error((response) => {
event.target.disabled = false;
noticeError('Error: ' + response.statusText);
});
},
},
]
} : null
);
}
updateSettings(option, status);
});
};
return (
<div className={className}>
<label htmlFor={id} className="advads-ui-switch">
<input
type="checkbox"
id={id}
checked={checked}
onChange={onChange}
disabled={disabled}
/>
<div></div>
<span dangerouslySetInnerHTML={{ __html: label }} />
{children}
</label>
</div>
);
}
export default function Settings() {
const { settings } = advancedAds.oneclick;
const { settings: options, selectedMethod } = useContext(OneClickContext);
const disabled = 'page' === selectedMethod;
let headerBiddingLabel = settings.headerBidding;
if (disabled) {
headerBiddingLabel += ' &nbsp;<em class="muted">' + settings.onlyPreview + '</em>'
}
return (
<div className="mb-8">
<div className="subheader inline">{settings.title}</div>
<div className="advads-ui-switch-list mt-6">
<Setting
id="pubguru-header-bidding"
label={headerBiddingLabel}
option="headerBidding"
disabled={disabled}
/>
{options.headerBidding && (
<Setting
id="pubguru-header-bidding-at-body"
label={settings.scriptLocation}
className="ml-4"
option="headerBiddingAtBody"
/>
)}
<Setting
id="pubguru-ads-txt"
label={settings.adsTxt}
option="adsTxt"
/>
<Setting
id="pubguru-traffic-cop"
label={settings.trafficCop}
option="trafficCop"
>
{settings.hasTrafficCop && (<span className="pg-tc-trail">
{settings.trafficCopTrial}
</span>)}
</Setting>
<Setting
id="pubguru-tag-conversion"
className="hidden"
label={settings.activateTags}
option="tagConversion"
/>
</div>
<p dangerouslySetInnerHTML={{ __html: settings.help }}></p>
</div>
);
}

View File

@@ -0,0 +1,53 @@
/**
* WordPress Dependencies
*/
import { useContext, useState } from '@wordpress/element';
/**
* Internal Dependencies
*/
import { OneClickContext } from './store/context';
export default function Step1() {
const [state, setState] = useState(false);
const { setStep, toggleMetabox } = useContext(OneClickContext);
const { step1 } = advancedAds.oneclick;
const onCancel = () => {
setState(false);
toggleMetabox(false);
setStep(1);
};
const onAgree = () => {
setStep(2);
};
return (
<div className="mt-6 mb-8">
<p dangerouslySetInnerHTML={{ __html: step1.content }}></p>
<p>
<label htmlFor="consent">
<input
type="checkbox"
id="consent"
onClick={() => setState(!state)}
/>
<span>{step1.agreeText}</span>
</label>
</p>
<p className="buttons-set">
<button
className="button button-primary"
disabled={!state}
onClick={onAgree}
>
{step1.btnAgree}
</button>
<button className="button" onClick={onCancel}>
{advancedAds.oneclick.btnCancel}
</button>
</p>
</div>
);
}

View File

@@ -0,0 +1,169 @@
/**
* External Dependencies
*/
import jQuery from 'jquery';
/**
* WordPress Dependencies
*/
import { decodeEntities } from '@wordpress/html-entities';
import { useContext, useEffect, useState } from '@wordpress/element';
/**
* Internal Dependencies
*/
import { OneClickContext } from './store/context';
function Loader() {
const { step2 } = advancedAds.oneclick;
return (
<div className="mt-6 mb-8">
<p>{step2.loading}</p>
<p>
<img src={advancedAds.oneclick.spinner} alt="" />
</p>
</div>
);
}
function DomainNotFound({ domain, onCancel, setDomain, setFetched }) {
const { step2 } = advancedAds.oneclick;
return (
<div className="mt-6 mb-8">
<p className="step-error">
{String.format(
decodeEntities(step2.notRegistered),
advancedAds.oneclick.siteDomain
)}
</p>
<p dangerouslySetInnerHTML={{ __html: step2.content }}></p>
<p>
<strong>{step2.inputLabel}</strong>{' '}
<input
type="text"
value={domain}
onChange={(event) => setDomain(event.target.value)}
/>
</p>
<p className="buttons-set">
<button
className="button button-primary"
disabled={'' === domain}
onClick={() => setFetched(false)}
>
{advancedAds.oneclick.btnContinue}
</button>
<button className="button" onClick={onCancel}>
{advancedAds.oneclick.btnCancel}
</button>
</p>
</div>
);
}
function ServerError({ error, onCancel, setFetched }) {
const { step2 } = advancedAds.oneclick;
return (
<div className="mt-6 mb-8">
<p className="step-error">
{String.format(decodeEntities(step2.serverError), error)}
</p>
<p dangerouslySetInnerHTML={{ __html: step2.serverContent }}></p>
<p className="buttons-set">
<button
className="button button-primary"
onClick={() => setFetched(false)}
>
{advancedAds.oneclick.btnRetry}
</button>
<button className="button" onClick={onCancel}>
{advancedAds.oneclick.btnCancel}
</button>
</p>
</div>
);
}
export default function Step2() {
const [fetched, setFetched] = useState(false);
const [domain, setDomain] = useState('');
const [error, setError] = useState('');
const { setStep, toggleMetabox, connected } = useContext(OneClickContext);
const onCancel = () => {
setDomain('');
setFetched(false);
toggleMetabox(false);
setStep(1);
};
useEffect(() => {
if (!fetched) {
jQuery
.ajax({
type: 'POST',
url: ajaxurl,
data: {
action: 'pubguru_connect',
nonce: advancedAds.oneclick.security,
testDomain: domain,
},
dataType: 'json',
})
.done(function (response) {
if (!response.success) {
if ('connect_error' === response.code) {
setFetched('server-error');
setError(response.message);
}
if ('domain_not_found' === response.code) {
setFetched('error');
}
return;
}
advancedAds.oneclick.options.connectedDomain =
'' !== domain
? domain
: advancedAds.oneclick.siteDomain;
connected();
})
.fail(function (jqXHR) {
setFetched('server-error');
setError(jqXHR.statusText);
});
}
}, [fetched]);
if (!fetched) {
return <Loader />;
}
if (fetched === 'error') {
return (
<DomainNotFound
domain={domain}
onCancel={onCancel}
setDomain={setDomain}
setFetched={setFetched}
/>
);
}
if (fetched === 'server-error') {
return (
<ServerError
error={error}
onCancel={onCancel}
setFetched={setFetched}
/>
);
}
return 'unknow error';
}

View File

@@ -0,0 +1,70 @@
/**
* WordPress Dependencies
*/
import { decodeEntities } from '@wordpress/html-entities';
import { useContext, useState } from '@wordpress/element';
/**
* Internal Dependencies
*/
import Modal from './Modal';
import Settings from './Settings';
import { OneClickContext } from './store/context';
export default function Step3() {
const { step3 } = advancedAds.oneclick;
const [open, toggleModal] = useState(false);
const { selectedMethod, selectedPage } = useContext(OneClickContext);
const onClose = () => toggleModal(false);
return (
<>
<div className="mt-6 mb-8">
<p>
{String.format(
decodeEntities(step3.yourDomain),
advancedAds.oneclick.options.connectedDomain
)}
</p>
{'final' === selectedMethod && (
<p
dangerouslySetInnerHTML={{ __html: step3.finalContent }}
></p>
)}
</div>
{'page' === selectedMethod && (
<div className="mt-6 mb-8">
<div className="subheader inline">{step3.title}</div>
{!selectedPage ? (
<div className="mt-6"
dangerouslySetInnerHTML={{ __html: step3.importContent }}
></div>
) : (
<p
dangerouslySetInnerHTML={{ __html: step3.previewContent }}
></p>
)}
<p className="buttons-set">
<button
className="button button-primary"
onClick={() => toggleModal(true)}
>
{step3.btnImport}
</button>
{selectedPage > 0 && (
<a href={`${advancedAds.siteInfo.homeUrl}/?p=${selectedPage}`} target="_blank" className="button button-secondary !flex items-center gap-x-1">
<span>{advancedAds.oneclick.modal.btnGoto}</span>
<span className="dashicons dashicons-external"></span>
</a>
)}
</p>
</div>
)}
<Modal open={open} onClose={onClose} />
<Settings />
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function StepWrapper({ title, children }) {
return (
<div>
{title && <div className="subheader">{title}</div>}
{children}
</div>
);
}

View File

@@ -0,0 +1,41 @@
/**
* WordPress Dependencies
*/
import domReady from '@wordpress/dom-ready';
import { createRoot, createElement, render } from '@wordpress/element';
/**
* Internal Dependencies
*/
import App from './App';
if (!String.format) {
String.format = function (format) {
const args = Array.prototype.slice.call(arguments, 1);
return format.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] !== 'undefined' ? args[number] : match;
});
};
}
/**
* Init
*/
domReady(() => {
const domNode = document.getElementById('advads-oneclick-app');
if (!domNode) {
return;
}
// add a div at the end of the element with id=advanced-ads-addon-box
const div = document.createElement('div');
div.id = 'advads-oneclick-addon-row';
document.getElementById('advanced-ads-addon-box').appendChild(div);
const uiElement = createElement(App);
if (createRoot) {
createRoot(domNode).render(uiElement);
} else {
render(uiElement, domNode);
}
});

View File

@@ -0,0 +1,43 @@
export default function actions(state, dispatch) {
function disconnect() {
dispatch({ type: 'DISCONNECT' });
}
function connected() {
dispatch({ type: 'CONNECTED' });
}
function toggleMetabox(mbState) {
dispatch({ type: 'TOGGLE_METABOX', value: mbState });
}
function setStep(step) {
dispatch({ type: 'SET_STEP', value: step });
}
function setMethod(method) {
dispatch({ type: 'SET_METHOD', value: method });
}
function setPage(page) {
dispatch({ type: 'SET_PAGE', value: page });
}
function updateSettings(name, status) {
dispatch({
type: 'UPDATE_SETTINGS',
name,
value: status,
});
}
return {
connected,
disconnect,
setMethod,
setPage,
setStep,
toggleMetabox,
updateSettings,
};
}

View File

@@ -0,0 +1,30 @@
/**
* WordPress Dependencies
*/
import { createContext, useReducer } from '@wordpress/element';
/**
* Internal Dependencies
*/
import initialState from './initial';
import reducers from './reducers';
import actions from './actions';
// Create the context
export const OneClickContext = createContext(initialState());
export default function ({ children }) {
const [state, dispatch] = useReducer(reducers, { ...initialState() });
const store = {
...state,
dispatch,
...actions(state, dispatch),
};
return (
<OneClickContext.Provider value={store}>
{children}
</OneClickContext.Provider>
);
}

View File

@@ -0,0 +1,17 @@
export default function initialState() {
const { oneclick } = advancedAds;
const { options } = oneclick;
return {
count: 0,
isConnected: oneclick.isConnected,
showMetabox: oneclick.isConnected,
currentStep: oneclick.isConnected ? 3 : 1,
settings: { ...advancedAds.oneclick.options },
// Methods
selectedMethod: options.selectedMethod,
selectedPage: options.selectedPage,
selectedPageTitle: options.selectedPageTitle,
};
}

View File

@@ -0,0 +1,53 @@
export default function reducers(state, action) {
switch (action.type) {
case 'SET_COUNT':
return {
...state,
count: action.value,
};
case 'TOGGLE_METABOX':
return {
...state,
showMetabox: action.value,
};
case 'SET_STEP':
return {
...state,
currentStep: action.value,
};
case 'UPDATE_SETTINGS':
return {
...state,
settings: {
...state.settings,
[action.name]: action.value,
},
};
case 'SET_METHOD':
return {
...state,
selectedMethod: action.value,
};
case 'SET_PAGE':
return {
...state,
selectedPage: action.value,
};
case 'DISCONNECT':
return {
...state,
isConnected: false,
showMetabox: false,
currentStep: 1,
};
case 'CONNECTED':
return {
...state,
isConnected: true,
showMetabox: true,
currentStep: 3,
};
default:
return state;
}
}

View File

@@ -0,0 +1,130 @@
/**
* External Dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal Dependencies
*/
import Header from './Header';
import Footer from './Footer';
import StepWrapper from './StepWrapper';
import { Wizard } from '@components/wizard';
import Step1 from './steps/Step1';
import GoogleAdsense from './steps/GoogleAdsense';
import BannerAd from './steps/BannerAd';
import CodeAd from './steps/CodeAd';
import Congrats from './steps/Congrats';
let initDone = false;
function initialState() {
const initial = {
startIndex: 1,
taskOption: 'google_adsense',
googleAdsPlacement: 'manual',
autoAdsOptions: [],
adsenseData: false,
};
const params = new URLSearchParams(document.location.search);
const wizardData = advancedAds.wizard.adsenseData;
if (
'adsense' === params.get('route') &&
'advanced-ads-onboarding' === params.get('page')
) {
initial.startIndex = 2;
initial.taskOption = 'google_adsense';
}
if (
'image' === params.get('route') &&
'advanced-ads-onboarding' === params.get('page')
) {
initial.startIndex = 2;
initial.taskOption = 'ad_image';
}
if (!Array.isArray(wizardData.accounts)) {
const adsenseId = wizardData['adsense-id'];
initial.adsenseData = {
account: {
id: adsenseId,
name: wizardData.accounts[adsenseId].details.name,
},
};
}
if (!initDone) {
if (wizardData.amp) {
initial.autoAdsOptions.push('enableAmp');
}
if (wizardData['page-level-enabled']) {
initial.autoAdsOptions.push('enable');
}
initDone = true;
}
return initial;
}
export default function App() {
const [options, setOptions] = useState({
...initialState(),
});
const handleOptions = (key, value) => {
const newOptions = {
...options,
[key]: value,
};
setOptions(newOptions);
};
return (
<div className="advads-onboarding advads-onboarding-frame">
<div className="absolute top-0 max-w-3xl w-full py-20">
<Wizard
header={<Header />}
footer={<Footer />}
startIndex={options.startIndex}
>
<StepWrapper>
<Step1 options={options} setOptions={handleOptions} />
</StepWrapper>
{'google_adsense' === options.taskOption && (
<StepWrapper className="pb-4">
<GoogleAdsense
options={options}
setOptions={handleOptions}
/>
</StepWrapper>
)}
{'ad_image' === options.taskOption && (
<StepWrapper className="pb-4">
<BannerAd
options={options}
setOptions={handleOptions}
/>
</StepWrapper>
)}
{'ad_code' === options.taskOption && (
<StepWrapper className="pb-4">
<CodeAd
options={options}
setOptions={handleOptions}
/>
</StepWrapper>
)}
<StepWrapper>
<Congrats
options={options}
setOptions={handleOptions}
/>
</StepWrapper>
</Wizard>
</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import { adminUrl } from '@utilities';
import { useWizard } from '@components/wizard';
export default function Footer() {
const { isLastStep } = useWizard();
if (isLastStep) {
return null;
}
return (
<div className="text-center">
<a
href={adminUrl('admin.php?page=advanced-ads')}
className="no-underline text-base border-0 border-b-2 pb-0.5 border-solid border-gray-800 text-gray-800"
>
{wizard.exitLabel}
</a>
</div>
);
}

View File

@@ -0,0 +1,43 @@
/**
* External Dependencies
*/
import clx from 'classnames';
/**
* Internal Dependencies
*/
import { useWizard } from '@components/wizard';
import { assetsUrl } from '@utilities';
export default function Header() {
const { stepCount, activeStep } = useWizard();
const steps = Array.apply(null, { length: stepCount });
return (
<div className="text-center">
<img
className="w-8/12 m-auto"
src={assetsUrl('assets/img/advancedads-full-logo.svg')}
alt=""
/>
<div className="advads-wizard-progress">
{steps.map((step, index) => {
const isActive = activeStep === index;
const stepClassName = clx(
'advads-wizard-progress--item',
activeStep > index ? 'is-done' : '',
isActive ? 'is-active' : ''
);
return (
<div className={stepClassName} key={`step-${index}`}>
<div className="advads-wizard-progress--count">
{isActive ? `Step ${index + 1}` : index + 1}
</div>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import { useWizard } from '@components/wizard';
import Divider from '@components/Divider';
export default function StepFooter({
isEnabled,
enableText,
disableText,
onNext,
}) {
const { previousStep, nextStep } = useWizard();
const handleNext = async () => {
if (onNext) {
onNext();
}
nextStep();
};
return (
<>
<Divider className="mb-4" />
<div className="flex items-center justify-between">
<div>
<button
onClick={previousStep}
className="button-onboarding !bg-white !border-gray-100 !text-gray-600"
>
{wizard.btnGoBack}
</button>
</div>
<div>
{isEnabled ? (
<button
className="button-onboarding"
onClick={handleNext}
>
{enableText}
</button>
) : (
<button className="button-onboarding" disabled={true}>
{disableText}
</button>
)}
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,9 @@
export default function StepWrapper({ children, className = '' }) {
return (
<div
className={`bg-white mt-4 mb-8 p-8 border-solid border-gray-200 rounded-sm ${className}`}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,25 @@
/**
* WordPress Dependencies
*/
import domReady from '@wordpress/dom-ready';
import { createRoot, createElement, render } from '@wordpress/element';
/**
* Internal Dependencies
*/
import App from './App';
/**
* Init
*/
domReady(() => {
// Render App.
const domElement = document.getElementById('advads-onboarding-wizard');
const uiElement = createElement(App);
if (createRoot) {
createRoot(domElement).render(uiElement);
} else {
render(uiElement, domElement);
}
});

View File

@@ -0,0 +1,110 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
import { useEffect } from '@wordpress/element';
/**
* Internal Dependencies
*/
import StepFooter from '../StepFooter';
export default function BannerAd({ options, setOptions }) {
let fileFrame = null;
const handleUpload = (event) => {
event.preventDefault();
if (fileFrame) {
fileFrame.uploader.uploader.param('post_id', 0);
fileFrame.open();
return;
}
fileFrame = wp.media.frames.file_frame = wp.media({
title: wizard.bannerAd.mediaFrameTitle,
button: {
text: wizard.bannerAd.mediaFrameButton,
},
multiple: false,
});
fileFrame.on('select', () => {
const attachment = fileFrame
.state()
.get('selection')
.first()
.toJSON();
setOptions('adImage', attachment);
});
fileFrame.open();
};
useEffect(() => {
if (options.adImage) {
const fileName = options.adImage.url.split('/').pop();
const maxWidth = 250;
const textP = document.createElement('p');
textP.style.visibility = 'hidden';
textP.style.whiteSpace = 'nowrap';
textP.style.position = 'absolute';
textP.innerText = fileName;
document.body.appendChild(textP);
const isTextLong = textP.offsetWidth > maxWidth;
document.body.removeChild(textP);
setOptions('isTextLong', isTextLong);
}
}, [options.adImage]);
return (
<>
<h1 className={`!mt-0 ${options.adImage ? 'text-gray-300' : ''}`}>
{wizard.stepTitles.adImage}
</h1>
{options.adImage ? (
<div className="space-y-4">
<div className="flex items-center gap-5">
<button
className="button-onboarding button-onboarding--gray"
onClick={handleUpload}
>
{wizard.bannerAd.mediaBtnReplace}
</button>
<div
className={`file-name-rtl m-0 ${options.isTextLong ? 'truncate' : ''}`}
>
<p>{options.adImage.url.split('/').pop()}</p>
</div>
<span className="dashicons dashicons-yes-alt flex items-center justify-center text-4xl w-9 h-9 text-primary"></span>
</div>
<div>
<h2>{wizard.bannerAd.stepHeading}</h2>
<input
type="url"
name="ad_image_url"
id="ad_image_url"
className="advads-input-text"
placeholder={wizard.bannerAd.inputPlaceholder}
onChange={(event) =>
setOptions('adImageUrl', event.target.value)
}
/>
</div>
</div>
) : (
<button className="button-onboarding" onClick={handleUpload}>
{wizard.bannerAd.mediaBtnUpload}
</button>
)}
<StepFooter
isEnabled={options.adImage}
enableText={wizard.bannerAd.footerEnableText}
disableText={wizard.bannerAd.footerDisableText}
onNext={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,39 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import StepFooter from '../StepFooter';
export default function CodeAd({ options, setOptions }) {
return (
<>
<h1 className={`!mt-0 ${options.adCode ? 'text-gray-300' : ''}`}>
{wizard.stepTitles.adCode}
</h1>
<div className="space-y-4">
<div>
<textarea
name="ad_code_code"
id="ad_code_code"
className="w-full p-4 text-base"
rows={6}
placeholder={wizard.codeAd.inputPlaceholder}
onChange={(event) =>
setOptions('adCode', event.target.value)
}
/>
</div>
</div>
<StepFooter
isEnabled={options.adCode}
enableText={wizard.codeAd.footerEnableText}
disableText={wizard.codeAd.footerDisableText}
onNext={() => {}}
/>
</>
);
}

View File

@@ -0,0 +1,269 @@
/**
* External Dependencies
*/
import { useState } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import { adminUrl } from '@utilities';
import Divider from '@components/Divider';
import Checkmark from '../../icons/Checkmark';
import Upgradebox from '../../icons/UpgradeBox';
function ListItem({ title = '', text, icon = false }) {
return (
<div className="flex w-full gap-x-3 items-center">
{icon && (
<div className="mt-2">
<Checkmark />
</div>
)}
<div className={`grow ${icon ? '' : 'ml-7'}`}>
<strong>{title}</strong> {text}
</div>
</div>
);
}
function Newsletter({ email, setEmail }) {
return (
<>
<h1 className="!mt-0">{wizard.newsletter.title}</h1>
<div className="advads-admin-notice">
<div className="advads-notice-box_wrapper flex gap-7 items-center">
<input
type="email"
id="newsletter_email"
className="advads-input-text"
placeholder={wizard.newsletter.inputPlaceholder}
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ minWidth: '65%', maxWidth: '65%' }}
/>
<div>
<button
className="button-onboarding advads-notices-button-subscribe"
data-notice="nl_free_addons"
>
{wizard.newsletter.btnLabel}
</button>
</div>
</div>
</div>
</>
);
}
function Loader() {
return (
/* eslint-disable jsx-a11y/anchor-is-valid */
<a
href="#"
className="button-onboarding button-onboarding--gray disabled"
>
{wizard.loading}
</a>
/* eslint-enable jsx-a11y/anchor-is-valid */
);
}
function CongratsHeader(options, result) {
let title, para;
title = wizard.stepTitles.congrats.default;
para = wizard.congrats.stepHeading;
if (options.taskOption === 'google_adsense') {
title =
options.googleAdsPlacement === 'auto_ads'
? wizard.stepTitles.congrats.adsenseAuto
: wizard.stepTitles.congrats.adsenseManual;
para =
options.googleAdsPlacement === 'auto_ads'
? wizard.congrats.adsenseAuto.stepHeading
: wizard.congrats.adsenseManual.stepHeading;
}
return (
<>
<h1 className="!mt-0">{title}</h1>
<div className="congrats-flex">
<p
className="text-justify min-w m-0"
dangerouslySetInnerHTML={{
__html: para,
}}
/>
{getEditButton(options, result)}
</div>
{getLiveHeading(options, result)}
</>
);
}
function getEditButton(options, result) {
if (options.taskOption === 'google_adsense' && result?.success) {
if (
options.googleAdsPlacement === 'auto_ads' &&
result.adsenseAccount
) {
return (
<div>
<a
href={result.adsenseAccount}
className="button-onboarding button-onboarding--gray"
target="_blank"
rel="noreferrer"
>
{wizard.congrats.adsenseAuto.btnAccount}
</a>
</div>
);
}
if (options.googleAdsPlacement === 'manual' && result.itemEditLink) {
return (
<div>
<a
href={result.itemEditLink}
className="button-onboarding !bg-red-600 !border-red-700 !text-white"
>
{wizard.congrats.adsenseManual.btnEditItem}
</a>
</div>
);
}
}
if (result?.success && result.itemEditLink) {
return (
<div>
<a
href={result.itemEditLink}
className="button-onboarding button-onboarding--gray"
>
{wizard.congrats.btnEditItem}
</a>
</div>
);
}
return null;
}
function getLiveHeading(options, result) {
if (
options.taskOption === 'google_adsense' &&
options.googleAdsPlacement === 'auto_ads'
) {
return null;
}
if (
options.taskOption === 'google_adsense' &&
options.googleAdsPlacement === 'manual'
) {
return (
<div className="congrats-flex mt-7">
<p
className="m-0"
dangerouslySetInnerHTML={{
__html: wizard.congrats.adsenseManual.liveHeading,
}}
/>
</div>
);
}
return (
<div className="congrats-flex mt-7">
<p
className="m-0"
dangerouslySetInnerHTML={{
__html: wizard.congrats.liveHeading,
}}
/>
<div className="mr-3">
{result && result.success && '' !== result.postLink ? (
<a href={result.postLink} className="button-onboarding">
{wizard.congrats.btnLiveAd}
</a>
) : (
<Loader />
)}
</div>
</div>
);
}
export default function Congrats({ options }) {
const [result, setResult] = useState(null);
const [userEmail, setUserEmail] = useState('');
if (result === null) {
apiFetch({
path: '/advanced-ads/v1/onboarding',
method: 'POST',
data: options,
}).then((response) => {
setResult(response);
});
}
if (userEmail === '') {
apiFetch({
path: '/advanced-ads/v1/user-email',
method: 'GET',
}).then((email) => {
setUserEmail(email);
});
}
return (
<>
{CongratsHeader(options, result)}
<Divider />
<Newsletter email={userEmail} setEmail={setUserEmail} />
<Divider />
<h1 className="!mt-0">{wizard.congrats.upgradeHeading}</h1>
<div className="congrats-flex items-center gap-x-12">
<p className="text-justify m-0">
{wizard.congrats.upgradeText}
</p>
<a
href="https://wpadvancedads.com/add-ons/all-access/?utm_source=advanced-ads&utm_medium=link&utm_campaign=wizard-upgrade"
className="button-onboarding !bg-red-600 !border-red-700 !text-white text-center"
target="_blank"
rel="noreferrer"
>
{wizard.congrats.btnUpgrade}
</a>
</div>
<div className="flex gap-x-12 items-center">
<div className="space-y-2 mt-4 text-lg tracking-wide grow">
{wizard.congrats.upgradePoints.map((point, index) => (
<ListItem key={`point-${index}`} {...point} />
))}
</div>
<div>
<Upgradebox className="w-40" />
</div>
</div>
<Divider />
<div className="text-right">
<a
href={adminUrl('admin.php?page=advanced-ads')}
className="button-onboarding button-onboarding--gray"
>
{wizard.congrats.btnDashboard}
</a>
</div>
</>
);
}

View File

@@ -0,0 +1,209 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import RadioList from '@components/inputs/RadioList';
import CheckboxList from '@components/inputs/CheckboxList';
import ErrorMessage from '@components/ErrorMessage';
import {
authUrl,
hasAuthCode,
submitCode,
getAccountDetails,
getErrorMessage,
} from '../../utilities';
import StepFooter from '../StepFooter';
import Preloader from '../../components/Preloader';
import SelectAccount from '../../components/SelectAccount';
export default function GoogleAdsense({ options, setOptions }) {
if (!options.adsenseData.account) {
if (options.adsenseData.phase) {
switch (options.adsenseData.phase) {
case 'error':
// Something went south, abort.
return <ErrorMessage message={options.adsenseData.error} />;
case 'select':
// Ask the user to choose one account from the network account.
return (
<SelectAccount
accounts={options.adsenseData.accountsList}
tokenData={options.adsenseData.tokenData}
done={(response) => {
setOptions('adsenseData', {
account: response.account,
});
}}
fail={(response) => {
let message = getErrorMessage(response);
if (!message) {
message = wizard.googleAd.errors.notSaved;
}
setOptions('adsenseData', {
phase: 'error',
error: message,
});
}}
/>
);
default:
}
}
if (hasAuthCode()) {
// Submit the authorization code from Google's oAuth2 page.
submitCode()
.done(function (response) {
// Got the refresh token, get account info.
getAccountDetails(response.token_data)
.done(function (accDetails) {
if (accDetails.account) {
// Standard account.
setOptions('adsenseData', {
account: accDetails.account,
});
}
if (accDetails.details) {
// Network account, show the list of child accounts to choose from.
setOptions('adsenseData', {
phase: 'select',
accountsList: accDetails.details,
tokenData: accDetails.token_data,
});
}
})
.fail(function (detailsRes) {
// Error while getting account details.
let message = getErrorMessage(detailsRes);
if (!message) {
message = wizard.googleAd.errors.notFetched;
}
setOptions('adsenseData', {
phase: 'error',
error: message,
});
});
})
.fail(function (response) {
// Error while requesting the refresh (permanent) token.
let message = getErrorMessage(response);
if (!message) {
message = wizard.googleAd.errors.notAuthorized;
}
setOptions('adsenseData', {
phase: 'error',
error: message,
});
});
return <Preloader />;
}
return (
<>
<h1 className="!mt-0">{wizard.googleAd.stepHeading}</h1>
<div className="mt-8 flex gap-x-8 justify-start items-center font-medium">
<a
className="button button-hero !text-base !px-3 !py-4"
href={advancedAds.wizard.newAccountLink}
target="_blank"
rel="noopener noreferrer"
>
{wizard.googleAd.btnSignup}
</a>
<a
className="button-onboarding !text-base !px-3 !py-4"
href={authUrl()}
>
{wizard.googleAd.btnConnect}
</a>
</div>
<StepFooter
isEnabled={false}
enableText={wizard.googleAd.footerProcessText}
disableText={wizard.googleAd.footerDisableText}
/>
</>
);
}
const enableText = (() => {
switch (options.googleAdsPlacement) {
case 'auto_ads':
return wizard.googleAd.footerEnableText.autoAds;
case 'manual':
return wizard.googleAd.footerEnableText.manual;
default:
return wizard.googleAd.footerDisableText;
}
})();
return (
<>
<h1 className="!mt-0 !text-gray-300">
{wizard.googleAd.stepHeading}
</h1>
<div className="space-y-4">
<p>
{wizard.googleAd.labelConnected}{' '}
{options.adsenseData.account.id}
<br />
{wizard.googleAd.labelAccount}{' '}
{options.adsenseData.account.name}
</p>
<div>
<h2
className={
'auto_ads' === options.googleAdsPlacement
? '!text-gray-300'
: null
}
>
{wizard.googleAd.labelAdsPlacement}
</h2>
<RadioList
id="google_ads_placement"
options={wizard.googleAd.adsPlacement}
value={options.googleAdsPlacement}
onChange={(value) =>
setOptions('googleAdsPlacement', value)
}
/>
</div>
{options.googleAdsPlacement &&
'auto_ads' === options.googleAdsPlacement && (
<div>
<h2>{wizard.googleAd.labelAutoAds}</h2>
<CheckboxList
id="auto_ads"
options={wizard.googleAd.autoAdsOptions}
value={options.autoAdsOptions}
onChange={(value) => {
const newOptions = [
...options.autoAdsOptions,
];
const index = newOptions.indexOf(value);
if (index > -1) {
newOptions.splice(index, 1);
} else {
newOptions.push(value);
}
setOptions('autoAdsOptions', newOptions);
}}
/>
</div>
)}
</div>
<StepFooter
isEnabled={options.googleAdsPlacement}
enableText={enableText}
disableText={wizard.googleAd.footerDisableText}
/>
</>
);
}

View File

@@ -0,0 +1,81 @@
/**
* External Dependencies
*/
import { wizard } from '@advancedAds/i18n';
/**
* Internal Dependencies
*/
import { useWizard } from '@components/wizard';
import RadioList from '@components/inputs/RadioList';
import IconAdSense from '../../icons/AdSense';
import IconCode from '../../icons/Code';
import IconImage from '../../icons/Image';
export default function Step1({ setOptions }) {
const { nextStep } = useWizard();
const taskOptions = [
{
label: (
<>
<IconAdSense />
<span>{wizard.firstStep.taskAdSense}</span>
</>
),
value: 'google_adsense',
},
{
label: (
<>
<IconImage />
<span>{wizard.firstStep.taskImage}</span>
</>
),
value: 'ad_image',
},
{
label: (
<>
<IconCode />
<span>{wizard.firstStep.taskCode}</span>
</>
),
value: 'ad_code',
},
];
return (
<>
<p className="font-medium mt-0">{wizard.firstStep.stepHeading}</p>
<p className="mt-4 font-medium">
<label className="advads-input-radio" htmlFor="agreement">
<input
type="checkbox"
name="agreement"
id="agreement"
onChange={(event) =>
setOptions('agreement', event.target.value)
}
/>
<span
dangerouslySetInnerHTML={{
__html: wizard.firstStep.agreementText,
}}
/>
</label>
</p>
<h2>{wizard.firstStep.inputTitle}</h2>
<RadioList
id="task"
className="!mb-0"
isButton
options={taskOptions}
onChange={(value) => {
setOptions('taskOption', value);
nextStep();
}}
/>
</>
);
}

View File

@@ -0,0 +1,59 @@
export function authUrl() {
const { wizard } = advancedAds;
const params = new URLSearchParams({
client_id: wizard.clientId,
redirect_uri: wizard.redirectUri,
state: wizard.state,
access_type: 'offline',
include_granted_scopes: 'true',
prompt: 'consent',
response_type: 'code',
}).toString();
return `${wizard.authUrl}&${params}`;
}
export function hasAuthCode() {
const params = new URLSearchParams(document.location.search);
return (
params.get('code') &&
'adsense' === params.get('route') &&
params.get('nonce')
);
}
export function submitCode() {
const params = new URLSearchParams(document.location.search);
return wp.ajax.post('advads_gadsense_mapi_confirm_code', {
nonce: params.get('nonce'),
code: params.get('code'),
});
}
export function getAccountDetails(tokenData) {
const params = new URLSearchParams(document.location.search);
return wp.ajax.post('advads_gadsense_mapi_get_details', {
nonce: params.get('nonce'),
token_data: tokenData,
});
}
export function getErrorMessage(response) {
let message = response.statusText;
try {
message = response.responseJSON.data.error;
} catch (e) {
try {
message = response.responseJSON.data.msg;
} catch (ee) {
try {
message = response.responseJSON.data.raw;
} catch (eee) {
try {
message = response.responseJSON.data.error_msg;
} catch (eeee) {}
}
}
}
return message;
}

View File

@@ -0,0 +1,13 @@
export function adminUrl(url) {
const {
endpoints: { adminUrl },
} = advancedAds;
return adminUrl + url;
}
export function assetsUrl(file) {
const {
endpoints: { assetsUrl },
} = advancedAds;
return assetsUrl + file;
}

View File

@@ -0,0 +1,2 @@
export * from './endpoints';
export * from './adsense';