Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.

Commit f3c43d9

Browse files
authored
Merge pull request #3 from frak-id/feat/webhook-setup-configuration
feat: add webhook setup and configuration feature
2 parents d24d1ba + b99d7f2 commit f3c43d9

9 files changed

Lines changed: 892 additions & 75 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
dist/
22
test/db_data
33
test/plugins
4-
test/wordpress
4+
test/wordpress
5+
vendor/
6+
composer.lock

admin/class-frak-admin.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ private function __construct() {
1515
add_action('admin_menu', array($this, 'add_admin_menu'));
1616
add_action('admin_init', array($this, 'register_settings'));
1717
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
18+
19+
// Add AJAX handlers for webhook operations
20+
add_action('wp_ajax_frak_generate_webhook_secret', array($this, 'ajax_generate_webhook_secret'));
21+
add_action('wp_ajax_frak_test_webhook', array($this, 'ajax_test_webhook'));
22+
add_action('wp_ajax_frak_clear_webhook_logs', array($this, 'ajax_clear_webhook_logs'));
23+
add_action('wp_ajax_frak_check_webhook_status', array($this, 'ajax_check_webhook_status'));
1824
}
1925

2026
public function add_admin_menu() {
@@ -39,6 +45,7 @@ public function register_settings() {
3945
register_setting('frak_settings', 'frak_enable_floating_button');
4046
register_setting('frak_settings', 'frak_show_reward');
4147
register_setting('frak_settings', 'frak_button_classname');
48+
register_setting('frak_settings', 'frak_webhook_secret');
4249
}
4350

4451
public function enqueue_scripts($hook) {
@@ -47,6 +54,29 @@ public function enqueue_scripts($hook) {
4754
}
4855

4956
wp_enqueue_code_editor(array('type' => 'text/javascript'));
57+
wp_enqueue_script('frak-admin', plugin_dir_url(dirname(__FILE__)) . 'admin/js/admin.js', array('jquery'), '1.0', true);
58+
59+
// Get logo URL for autofill
60+
$logo_url = '';
61+
$site_icon_id = get_option('site_icon');
62+
if ($site_icon_id) {
63+
$logo_url = wp_get_attachment_image_url($site_icon_id, 'full');
64+
}
65+
if (!$logo_url) {
66+
$custom_logo_id = get_theme_mod('custom_logo');
67+
if ($custom_logo_id) {
68+
$logo_url = wp_get_attachment_image_url($custom_logo_id, 'full');
69+
}
70+
}
71+
72+
wp_localize_script('frak-admin', 'frak_ajax', array(
73+
'ajax_url' => admin_url('admin-ajax.php'),
74+
'nonce' => wp_create_nonce('frak_ajax_nonce'),
75+
'site_info' => array(
76+
'name' => get_bloginfo('name'),
77+
'logo_url' => $logo_url
78+
)
79+
));
5080

5181
wp_add_inline_style('wp-admin', '
5282
.frak-links {
@@ -63,6 +93,51 @@ public function enqueue_scripts($hook) {
6393
.frak-links a:hover {
6494
text-decoration: underline;
6595
}
96+
.frak-webhook-status {
97+
display: inline-block;
98+
padding: 3px 8px;
99+
border-radius: 3px;
100+
font-size: 12px;
101+
font-weight: 600;
102+
}
103+
.frak-webhook-status.status-active {
104+
background: #d4edda;
105+
color: #155724;
106+
}
107+
.frak-webhook-status.status-inactive {
108+
background: #f8d7da;
109+
color: #721c24;
110+
}
111+
.frak-stats-grid {
112+
display: grid;
113+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
114+
gap: 15px;
115+
margin: 20px 0;
116+
}
117+
.frak-stat-box {
118+
background: #fff;
119+
border: 1px solid #ccd0d4;
120+
padding: 15px;
121+
text-align: center;
122+
}
123+
.frak-stat-box h3 {
124+
margin: 0;
125+
font-size: 24px;
126+
}
127+
.frak-stat-box p {
128+
margin: 5px 0 0;
129+
color: #666;
130+
}
131+
.frak-webhook-logs table {
132+
margin-top: 20px;
133+
}
134+
.frak-webhook-section {
135+
margin-top: 30px;
136+
padding: 20px;
137+
background: #fff;
138+
border: 1px solid #ccd0d4;
139+
box-shadow: 0 1px 1px rgba(0,0,0,.04);
140+
}
66141
');
67142
}
68143

@@ -180,4 +255,71 @@ private function get_default_config($app_name, $logo_url) {
180255
};
181256
JS;
182257
}
258+
259+
// AJAX Handlers
260+
public function ajax_generate_webhook_secret() {
261+
check_ajax_referer('frak_ajax_nonce', 'nonce');
262+
263+
if (!current_user_can('manage_options')) {
264+
wp_die('Unauthorized');
265+
}
266+
267+
$secret = wp_generate_password(32, false);
268+
update_option('frak_webhook_secret', $secret);
269+
270+
wp_send_json_success(array(
271+
'secret' => $secret,
272+
'message' => __('Webhook secret generated successfully', 'frak')
273+
));
274+
}
275+
276+
public function ajax_test_webhook() {
277+
check_ajax_referer('frak_ajax_nonce', 'nonce');
278+
279+
if (!current_user_can('manage_options')) {
280+
wp_die('Unauthorized');
281+
}
282+
283+
$result = Frak_Webhook_Helper::test_webhook();
284+
285+
if ($result['success']) {
286+
wp_send_json_success(array(
287+
'message' => sprintf(__('Webhook test successful (%dms)', 'frak'), $result['execution_time']),
288+
'details' => $result
289+
));
290+
} else {
291+
wp_send_json_error(array(
292+
'message' => __('Webhook test failed: ', 'frak') . $result['error'],
293+
'details' => $result
294+
));
295+
}
296+
}
297+
298+
public function ajax_clear_webhook_logs() {
299+
check_ajax_referer('frak_ajax_nonce', 'nonce');
300+
301+
if (!current_user_can('manage_options')) {
302+
wp_die('Unauthorized');
303+
}
304+
305+
Frak_Webhook_Helper::clear_webhook_logs();
306+
307+
wp_send_json_success(array(
308+
'message' => __('Webhook logs cleared successfully', 'frak')
309+
));
310+
}
311+
312+
public function ajax_check_webhook_status() {
313+
check_ajax_referer('frak_ajax_nonce', 'nonce');
314+
315+
if (!current_user_can('manage_options')) {
316+
wp_die('Unauthorized');
317+
}
318+
319+
$status = Frak_Webhook_Helper::get_webhook_status();
320+
321+
wp_send_json_success(array(
322+
'status' => $status
323+
));
324+
}
183325
}

admin/js/admin.js

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
jQuery(document).ready(function($) {
2+
var editor;
3+
4+
// Initialize code editor if available
5+
if (typeof wp.codeEditor !== 'undefined' && $('#frak_custom_config').length) {
6+
var editorSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {};
7+
editorSettings.codemirror = _.extend({}, editorSettings.codemirror, {
8+
indentUnit: 4,
9+
tabSize: 4,
10+
mode: 'javascript',
11+
lineNumbers: true,
12+
matchBrackets: true,
13+
autoCloseBrackets: true,
14+
extraKeys: {"Ctrl-Space": "autocomplete"},
15+
theme: 'default'
16+
});
17+
editor = wp.codeEditor.initialize($('#frak_custom_config'), editorSettings);
18+
}
19+
20+
// Update config function
21+
function updateConfig() {
22+
if (!editor) return;
23+
24+
var appName = $('#frak_app_name').val();
25+
var logoUrl = $('#frak_logo_url').val();
26+
var currentConfig = editor.codemirror.getValue();
27+
28+
currentConfig = currentConfig.replace(
29+
/(metadata:\s*{\s*name:\s*")[^"]*(")/,
30+
'$1' + appName + '$2'
31+
);
32+
33+
currentConfig = currentConfig.replace(
34+
/(logoUrl:\s*")[^"]*(")/,
35+
'$1' + logoUrl + '$2'
36+
);
37+
38+
editor.codemirror.setValue(currentConfig);
39+
}
40+
41+
// Toggle floating button settings
42+
function toggleFloatingButtonSettings() {
43+
var enabled = $('#frak_enable_floating_button').is(':checked');
44+
$('#frak_show_reward, #frak_button_classname').prop('disabled', !enabled);
45+
}
46+
47+
// Autofill functionality
48+
$('#autofill_app_name').on('click', function() {
49+
if (frak_ajax.site_info.name) {
50+
$('#frak_app_name').val(frak_ajax.site_info.name).trigger('input');
51+
}
52+
});
53+
54+
$('#autofill_logo_url').on('click', function() {
55+
if (frak_ajax.site_info.logo_url) {
56+
$('#frak_logo_url').val(frak_ajax.site_info.logo_url).trigger('input');
57+
}
58+
});
59+
60+
// Bind events
61+
$('#frak_app_name, #frak_logo_url').on('input', updateConfig);
62+
$('#frak_enable_floating_button').on('change', toggleFloatingButtonSettings);
63+
toggleFloatingButtonSettings();
64+
65+
// Generate webhook secret
66+
$('#generate-webhook-secret').on('click', function(e) {
67+
e.preventDefault();
68+
69+
if (!confirm('Are you sure you want to regenerate the webhook secret? This will break the integration if you have already configured it on Frak.')) {
70+
return;
71+
}
72+
73+
$.post(frak_ajax.ajax_url, {
74+
action: 'frak_generate_webhook_secret',
75+
nonce: frak_ajax.nonce
76+
}, function(response) {
77+
if (response.success) {
78+
$('#frak_webhook_secret').val(response.data.secret);
79+
showNotice(response.data.message, 'success');
80+
} else {
81+
showNotice('Error generating webhook secret', 'error');
82+
}
83+
});
84+
});
85+
86+
// Test webhook
87+
$('#test-webhook').on('click', function(e) {
88+
e.preventDefault();
89+
90+
var $button = $(this);
91+
$button.prop('disabled', true).text('Testing...');
92+
93+
$.post(frak_ajax.ajax_url, {
94+
action: 'frak_test_webhook',
95+
nonce: frak_ajax.nonce
96+
}, function(response) {
97+
if (response.success) {
98+
showNotice(response.data.message, 'success');
99+
} else {
100+
showNotice(response.data.message, 'error');
101+
}
102+
}).always(function() {
103+
$button.prop('disabled', false).text('Test Webhook');
104+
});
105+
});
106+
107+
// Clear webhook logs
108+
$('#clear-webhook-logs').on('click', function(e) {
109+
e.preventDefault();
110+
111+
if (!confirm('Are you sure you want to clear all webhook logs?')) {
112+
return;
113+
}
114+
115+
$.post(frak_ajax.ajax_url, {
116+
action: 'frak_clear_webhook_logs',
117+
nonce: frak_ajax.nonce
118+
}, function(response) {
119+
if (response.success) {
120+
showNotice(response.data.message, 'success');
121+
setTimeout(function() {
122+
location.reload();
123+
}, 1000);
124+
}
125+
});
126+
});
127+
128+
// Open webhook setup popup
129+
$('#open-webhook-popup').on('click', function(e) {
130+
e.preventDefault();
131+
132+
var productId = $(this).data('product-id');
133+
var webhookSecret = $('#frak_webhook_secret').val();
134+
135+
if (!webhookSecret) {
136+
alert('Please generate a webhook secret first');
137+
return;
138+
}
139+
140+
var createUrl = new URL('https://business.frak.id');
141+
createUrl.pathname = '/embedded/purchase-tracker';
142+
createUrl.searchParams.append('pid', productId);
143+
createUrl.searchParams.append('s', webhookSecret);
144+
createUrl.searchParams.append('p', 'custom');
145+
146+
var openedWindow = window.open(
147+
createUrl.href,
148+
'frak-business',
149+
'menubar=no,status=no,scrollbars=no,fullscreen=no,width=500,height=800'
150+
);
151+
152+
if (openedWindow) {
153+
openedWindow.focus();
154+
155+
// Check when window is closed and refresh status
156+
var timer = setInterval(function() {
157+
if (openedWindow.closed) {
158+
clearInterval(timer);
159+
setTimeout(function() {
160+
checkWebhookStatus();
161+
}, 1000);
162+
}
163+
}, 500);
164+
}
165+
});
166+
167+
// Check webhook status
168+
function checkWebhookStatus() {
169+
$.post(frak_ajax.ajax_url, {
170+
action: 'frak_check_webhook_status',
171+
nonce: frak_ajax.nonce
172+
}, function(response) {
173+
if (response.success) {
174+
var $status = $('.frak-webhook-status');
175+
if (response.data.status) {
176+
$status.removeClass('status-inactive').addClass('status-active').text('Active');
177+
showNotice('Webhook is now active!', 'success');
178+
} else {
179+
$status.removeClass('status-active').addClass('status-inactive').text('Inactive');
180+
}
181+
}
182+
});
183+
}
184+
185+
// Show admin notice
186+
function showNotice(message, type) {
187+
var $notice = $('<div class="notice notice-' + type + ' is-dismissible"><p>' + message + '</p></div>');
188+
$('.wrap h1').after($notice);
189+
190+
// Auto dismiss after 5 seconds
191+
setTimeout(function() {
192+
$notice.fadeOut(function() {
193+
$(this).remove();
194+
});
195+
}, 5000);
196+
}
197+
});

0 commit comments

Comments
 (0)