/home/altere25/.trash/wp-to-sanity-migration.12
Edit: /home/altere25/.trash/wp-to-sanity-migration.12/assets\js\admin.js (52894B)
/**
* Altered Digital Migration — unified wizard admin JS
*
* 10-step wizard state machine:
* 1. Login 6. Connect Sanity
* 2. Analyze 7. Content settings
* 3. AI 8. Migration
* 4. Quote 9. Approve
* 5. Pay 10. Next Steps (Claude / Schema / Log)
*
* Merged modules (formerly separate files):
* - ADProgress class (preserved — polls Worker /v2/migrations)
* - ClaudeConnect (from claude-connect.js)
* - SchemaPreview (from schema-preview.php inline script)
* - MigrationLog (new — GET /logs with pagination)
*
* @package AlteredDigitalMigration
*/
(function($) {
'use strict';
var W = window.wpToSanity = window.wpToSanity || {};
// -----------------------------------------------------------------
// Wizard state machine
// -----------------------------------------------------------------
/** Current wizard state (populated from GET /wizard-state on load). */
var wizardState = {};
/** Minimal HTML escaper for interpolating server-provided strings into HTML. */
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
/**
* GET /wizard-state on page load, determine which panel to show.
* Runtime-inferred flags drive the initial panel selection.
*/
function loadWizardState() {
$.ajax({
url: W.restUrl + 'wizard-state',
method: 'GET',
headers: { 'X-WP-Nonce': W.restNonce },
success: function(resp) {
if (resp && resp.success && resp.data) {
wizardState = resp.data.state || {};
var settings = resp.data.settings || {};
// Pre-fill content settings from server-provided values.
prefillContentSettings(settings);
determineInitialPanel();
}
},
error: function() {
// Default to step 1 if state can't be loaded.
showPanel(1);
}
});
}
/**
* Determine which panel to show based on runtime-inferred + stored state.
* Handles the payment-return flow (?payment=final_success|final_cancelled).
*/
function determineInitialPanel() {
// ── Payment-return flow ──
var params = new URLSearchParams(window.location.search);
var paymentFlag = params.get('payment');
var quoteIdParam = params.get('quote_id');
if (paymentFlag === 'final_success' && quoteIdParam) {
wizardState.quote_id = wizardState.quote_id || quoteIdParam;
showVerifyingPayment(quoteIdParam);
return;
}
if (paymentFlag === 'final_cancelled' && quoteIdParam) {
wizardState.quote_id = wizardState.quote_id || quoteIdParam;
// Clean the URL so a refresh doesn't keep showing "cancelled".
history.replaceState(null, '', window.location.pathname);
showPanel(9);
$('#ad-approve-status').text('Payment cancelled — you can retry when ready.').css('color', '#d63638');
return;
}
if (wizardState.approved) {
showStatusDashboard();
return;
}
if (!wizardState.logged_in) {
showPanel(1);
return;
}
if (!wizardState.analysis_id) {
showPanel(2);
return;
}
if (!wizardState.quote_id) {
showPanel(4);
return;
}
if (!wizardState.sanity_connected) {
showPanel(6);
return;
}
if (!wizardState.content_configured) {
showPanel(7);
return;
}
if (!wizardState.migration_started) {
showPanel(8);
return;
}
// Migration started but not approved → step 9.
showPanel(9);
}
/**
* Show the "verifying payment" panel and poll the AI service for the
* final payment status. The Stripe webhook may not have landed yet
* when the customer is redirected back, so we retry a few times.
*/
function showVerifyingPayment(quoteId) {
showPanel('9b');
var $status = $('#ad-payment-required-status').text('Verifying payment with Stripe…');
// Wire the retry button to re-run the poll.
$('#ad-retry-promote-btn').off('click').on('click', function() {
if (quoteId) {
startPoll();
} else {
$status.text('No quote on file — return to step 9 to retry approval.')
.css('color', '#d63638');
}
});
var attempts = 0;
var maxAttempts = 8;
function startPoll() {
attempts = 0;
$status.text('Verifying payment with Stripe…').css('color', '');
poll();
}
function poll() {
attempts++;
$.ajax({
url: W.restUrl + 'payment-status',
method: 'GET',
headers: { 'X-WP-Nonce': W.restNonce },
data: { quote_id: quoteId },
success: function(resp) {
if (resp && resp.success && resp.data && resp.data.paid_full) {
saveWizardState({ approved: true, paid_full: true });
wizardState.approved = true;
wizardState.paid_full = true;
$status.text('Payment confirmed! Unlocking Next Steps…').css('color', '#00a32a');
// Clean the URL.
history.replaceState(null, '', window.location.pathname);
setTimeout(function() { showStatusDashboard(); }, 1500);
} else if (attempts < maxAttempts) {
$status.text('Verifying payment… (attempt ' + attempts + '/' + maxAttempts + ')');
setTimeout(poll, 3000);
} else {
$status.text('Payment not yet confirmed. The webhook may still be processing — click "Retry final payment" to check again.')
.css('color', '#d63638');
}
},
error: function() {
if (attempts < maxAttempts) {
setTimeout(poll, 3000);
} else {
$status.text('Could not verify payment. Click "Retry final payment" to try again.')
.css('color', '#d63638');
}
}
});
}
startPoll();
}
/**
* Show a specific panel, update the step indicator.
* Exposed as W.showPanel for dashboard quick-action onclick handlers.
*/
W.showPanel = function(n) {
showPanel(n);
};
function showPanel(n) {
$('.ad-wizard-panel').attr('hidden', true);
var $panel = $('.ad-wizard-panel[data-panel="' + n + '"]');
if (!$panel.length) { return; }
$panel.removeAttr('hidden');
// Step indicator: mark active + completed.
$('.ad-wizard-steps li').removeClass('active completed');
$('.ad-wizard-steps li').each(function() {
var step = parseInt($(this).data('step'), 10);
if (step < n) {
$(this).addClass('completed');
}
if (step === n) {
$(this).addClass('active');
}
});
// Lazy-init step 10 tabs + modules.
if (n === 10) {
initNextSteps();
}
// Start ADProgress polling when showing migration panel or dashboard.
if (n === 8 || n === 'dashboard') {
ensureProgressPolling();
}
}
/**
* POST /wizard-state with a partial update after a step completes.
*/
function saveWizardState(partial) {
$.ajax({
url: W.restUrl + 'wizard-state',
method: 'POST',
headers: {
'X-WP-Nonce': W.restNonce,
'Content-Type': 'application/json'
},
data: JSON.stringify(partial),
success: function(resp) {
if (resp && resp.success && resp.data) {
wizardState = $.extend(wizardState, resp.data);
}
}
});
}
function prefillContentSettings(settings) {
if (!settings) { return; }
// Uncheck all first, then re-check the selected ones.
$('input[name="ad_content_post_types[]"]').prop('checked', false);
if (settings.post_types) {
settings.post_types.forEach(function(pt) {
$('input[name="ad_content_post_types[]"][value="' + pt + '"]').prop('checked', true);
});
}
$('input[name="ad_content_taxonomies[]"]').prop('checked', false);
if (settings.taxonomies) {
settings.taxonomies.forEach(function(tax) {
$('input[name="ad_content_taxonomies[]"][value="' + tax + '"]').prop('checked', true);
});
}
if (typeof settings.include_drafts !== 'undefined') {
$('#ad-include-drafts').prop('checked', settings.include_drafts);
}
if (typeof settings.auto_sync !== 'undefined') {
$('#ad-auto-sync').prop('checked', settings.auto_sync);
}
}
// -----------------------------------------------------------------
// Step 1: Login
// -----------------------------------------------------------------
function initLoginStep() {
$('#ad-login-form').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $status = $('#ad-login-status').removeAttr('hidden')
.text('Signing in…').css('color', '#555');
$.ajax({
url: W.restUrl + 'auth/login',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
data: {
email: $form.find('input[name="email"]').val(),
password: $form.find('input[name="password"]').val()
},
success: function(resp) {
if (resp && resp.success) {
wizardState.logged_in = true;
showPanel(2);
} else {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error
: (resp && resp.error) ? resp.error : 'Login failed.';
$status.text(msg).css('color', '#d63638');
}
},
error: function(xhr) {
var msg = 'Login request failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
else if (body && body.message) { msg = body.message; }
else if (body && body.error) { msg = body.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$status.text(msg).css('color', '#d63638');
}
});
});
// Signup toggle (same form, hits /auth/signup).
$('#ad-signup-toggle').on('click', function(e) {
e.preventDefault();
var $btn = $('#ad-login-form button[type="submit"]');
if ($btn.data('mode') === 'signup') {
$btn.text('Sign in').data('mode', 'login');
$('#ad-signup-toggle').text('Create an account');
} else {
$btn.text('Create account').data('mode', 'signup');
$('#ad-signup-toggle').text('Already have an account? Sign in');
}
});
}
// -----------------------------------------------------------------
// Steps 2-5: Analyze → Progress → Quote → Payment
// -----------------------------------------------------------------
function initAnalyzeStep() {
$('#ad-analyze-trigger').on('click', function() {
var $btn = $(this).prop('disabled', true).text('Analyzing…');
showPanel(3);
$('#ad-analyze-stage').text('Gathering site data…');
// Step 1: POST /analyze → returns analysis_id.
$.ajax({
url: W.restUrl + 'analyze',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
success: function(resp) {
if (!resp || !resp.success) {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error
: (resp && resp.error) ? resp.error : 'Analysis failed.';
$('#ad-analyze-stage').text('Error: ' + msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Analyze my site');
return;
}
var analysisId = resp.data.analysis_id;
var analysisJson = resp.data.analysis;
saveWizardState({ analysis_id: analysisId });
wizardState.analysis_id = analysisId;
$('#ad-analyze-fill').css('width', '60%');
$('#ad-analyze-stage').text('Generating quote…');
// Step 2: POST /quote with the analysis_id + analysis_json
// (inline, since Vercel serverless doesn't share in-memory cache).
$.ajax({
url: W.restUrl + 'quote',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
data: { analysis_id: analysisId, analysis_json: analysisJson, add_ons: [] },
success: function(qresp) {
if (!qresp || !qresp.success) {
var msg = (qresp && qresp.data && qresp.data.error) ? qresp.data.error
: (qresp && qresp.error) ? qresp.error : 'Quote failed.';
$('#ad-analyze-stage').text('Error: ' + msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Analyze my site');
return;
}
var quoteId = qresp.data.quote_id || '';
saveWizardState({ quote_id: quoteId });
wizardState.quote_id = quoteId;
$('#ad-analyze-fill').css('width', '100%');
$('#ad-analyze-stage').text('Done!');
renderQuote(qresp.data);
showPanel(4);
},
error: function(xhr) {
var msg = 'Quote request failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
else if (body && body.error) { msg = body.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$('#ad-analyze-stage').text('Error: ' + msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Analyze my site');
}
});
},
error: function(xhr) {
var msg = 'Analysis request failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
else if (body && body.error) { msg = body.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$('#ad-analyze-stage').text('Error: ' + msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Analyze my site');
}
});
});
}
function centsToUsd(cents) {
return '$' + (cents / 100).toFixed(2);
}
function renderQuote(data) {
var quote = data.quote || {};
var html = '
';
html += '| Item | Qty | Price | Total |
';
(quote.line_items || []).forEach(function(item) {
html += '| ' + (item.label || item.key) + ' | ' +
item.quantity + ' | ' + centsToUsd(item.unit_price_cents) +
' | ' + centsToUsd(item.total_cents) + ' |
';
});
html += '
';
if (quote.add_ons && quote.add_ons.length) {
html += '
Add-ons
';
quote.add_ons.forEach(function(a) {
html += '- ' + (a.label || a.key) + ' — ' + centsToUsd(a.total_cents) + '
';
});
html += '
';
}
html += '
Subtotal: ' + centsToUsd(quote.subtotal_cents) + '
';
html += '
Total: ' + centsToUsd(quote.total_cents) + '
';
html += '
Upfront (50%): ' + centsToUsd(quote.upfront_50_cents) +
' · Final (50%): ' + centsToUsd(quote.final_50_cents) + '
';
if (quote.estimated_days) {
html += '
Estimated time: ' + quote.estimated_days + ' days
';
}
if (data.deterministic) {
html += '
Note: using estimated pricing (AI pricing model unavailable).
';
}
var quoteId = data.quote_id || '';
// T&C checkbox — checkout disabled until the customer agrees.
html += '
';
html += '
';
$('#ad-quote-body').html(html);
// Enable checkout only when T&C checkbox is checked.
var $tcBox = $('#ad-tc-agree');
var $cbtn0 = $('#ad-checkout-trigger');
$tcBox.on('change', function() {
$cbtn0.prop('disabled', !$tcBox.prop('checked') || !quoteId);
});
// Wire checkout button → /checkout → redirect to Stripe.
$('#ad-checkout-trigger').on('click', function() {
if (!$tcBox.prop('checked')) { return; }
var $cbtn = $(this).prop('disabled', true).text('Redirecting…');
$.ajax({
url: W.restUrl + 'checkout',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
data: { quote_id: quoteId },
success: function(cresp) {
if (cresp && cresp.success && cresp.data && cresp.data.checkout_url) {
window.location.href = cresp.data.checkout_url;
} else {
var msg = (cresp && cresp.data && cresp.data.error) ? cresp.data.error
: 'Checkout failed.';
$cbtn.prop('disabled', false).text('Continue to payment');
alert(msg);
}
},
error: function(xhr) {
var msg = 'Checkout failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
else if (body && body.error) { msg = body.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$cbtn.prop('disabled', false).text('Continue to payment');
alert(msg);
}
});
});
}
// -----------------------------------------------------------------
// Step 6: Connect Sanity (POST /connect/v2)
// -----------------------------------------------------------------
function initConnectSanityStep() {
$('#ad-connect-sanity-form').on('submit', function(e) {
e.preventDefault();
var $status = $('#ad-connect-sanity-status').removeAttr('hidden')
.text('Connecting…').css('color', '#555');
var $btn = $('#ad-connect-sanity-btn').prop('disabled', true);
// Extract account_id from the Supabase JWT (sub claim).
var accountId = '';
try {
// AD_Auth stores the access token server-side; we read it
// from the wizard state if present, otherwise leave blank.
// The Worker can resolve account_id from the JWT itself.
accountId = wizardState.account_id || '';
} catch (ex) { /* leave blank */ }
$.ajax({
url: W.restUrl + 'connect/v2',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
data: {
service_url: '', // pre-configured by network admin; Worker reads from settings
admin_token: '', // same
account_id: accountId,
sanity_project_id: $('#ad-sanity-project-id').val(),
sanity_api_token: $('#ad-sanity-api-token').val(),
sanity_dataset: $('#ad-sanity-dataset').val() || 'production'
},
success: function(resp) {
$btn.prop('disabled', false);
if (resp && resp.success) {
$status.text('Connected! Webhook URL: ' + (resp.data && resp.data.webhook_url ? resp.data.webhook_url : 'configured'))
.css('color', '#00a32a');
saveWizardState({ sanity_connected: true });
wizardState.sanity_connected = true;
setTimeout(function() { showPanel(7); }, 1200);
} else {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error : 'Connection failed.';
$status.text(msg).css('color', '#d63638');
}
},
error: function(xhr) {
$btn.prop('disabled', false);
var msg = 'Connection failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
else if (body && body.error) { msg = body.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$status.text(msg).css('color', '#d63638');
}
});
});
}
// -----------------------------------------------------------------
// Step 7: Content settings (POST /settings)
// -----------------------------------------------------------------
function initContentSettingsStep() {
$('#ad-save-settings-btn').on('click', function() {
var $btn = $(this).prop('disabled', true).text('Saving…');
var $status = $('#ad-save-settings-status').text('');
var postTypes = [];
$('input[name="ad_content_post_types[]"]:checked').each(function() {
postTypes.push($(this).val());
});
var taxonomies = [];
$('input[name="ad_content_taxonomies[]"]:checked').each(function() {
taxonomies.push($(this).val());
});
var includeDrafts = $('#ad-include-drafts').is(':checked');
var autoSync = $('#ad-auto-sync').is(':checked');
$.ajax({
url: W.restUrl + 'settings',
method: 'POST',
headers: {
'X-WP-Nonce': W.restNonce,
'Content-Type': 'application/json'
},
data: JSON.stringify({
post_types: postTypes,
taxonomies: taxonomies,
include_drafts: includeDrafts,
auto_sync: autoSync
}),
success: function(resp) {
$btn.prop('disabled', false).text('Save & Continue');
if (resp && resp.success) {
$status.text('Saved!').css('color', '#00a32a');
saveWizardState({ content_configured: true });
wizardState.content_configured = true;
setTimeout(function() { showPanel(8); }, 800);
} else {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error : 'Save failed.';
$status.text(msg).css('color', '#d63638');
}
},
error: function(xhr) {
$btn.prop('disabled', false).text('Save & Continue');
var msg = 'Save failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$status.text(msg).css('color', '#d63638');
}
});
});
}
// -----------------------------------------------------------------
// Step 8: Migration (POST /export/full → ADProgress polling)
// -----------------------------------------------------------------
function initMigrationStep() {
$('#ad-start-migration-btn').on('click', function() {
var $btn = $(this).prop('disabled', true).text('Starting…');
var $status = $('#ad-migration-status').text('');
$.ajax({
url: W.restUrl + 'export/full',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
success: function(resp) {
if (resp && resp.success) {
var queued = (resp.data && resp.data.queued) ? resp.data.queued : 0;
$status.text(queued + ' items queued.').css('color', '#00a32a');
saveWizardState({ migration_started: true });
wizardState.migration_started = true;
$btn.text('Migration Started').prop('disabled', true);
ensureProgressPolling();
// Show continue-to-approval button.
$('#ad-continue-approval-wrap').removeAttr('hidden');
} else {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error : 'Failed to start.';
$status.text(msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Start Full Migration');
}
},
error: function(xhr) {
var msg = 'Failed to start migration';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$status.text(msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Start Full Migration');
}
});
});
// Continue to approval button.
$('#ad-continue-approval-btn').on('click', function() {
showPanel(9);
});
}
// -----------------------------------------------------------------
// Step 9: Approve (POST /approve)
// -----------------------------------------------------------------
function initApproveStep() {
$('#ad-approve-btn').on('click', function() {
var $btn = $(this).prop('disabled', true).text('Processing…');
var $status = $('#ad-approve-status').text('');
var quoteId = wizardState.quote_id || '';
$.ajax({
url: W.restUrl + 'approve',
method: 'POST',
headers: { 'X-WP-Nonce': W.restNonce },
data: { quote_id: quoteId },
success: function(resp) {
if (resp && resp.success && resp.data) {
// QA gate: the AI service returns passed=false on QA
// failure with an HTTP 200. Check it before treating
// this as a true approval.
if (resp.data.passed === false) {
var failed = resp.data.failed_checks || [];
var lines = ['QA checks failed:'];
for (var i = 0; i < failed.length; i++) {
lines.push('• ' + failed[i].name + (failed[i].detail ? ' — ' + failed[i].detail : ''));
}
$status.html($('
').text(lines.join('\n')).css({ 'white-space': 'pre-wrap', color: '#d63638' }));
$btn.prop('disabled', false).text('Approve & Pay Final 50%');
return;
}
// QA passed. If there's a checkout_url, redirect to
// Stripe — do NOT set approved:true yet (the webhook
// may not land before the user returns).
if (resp.data.checkout_url) {
$status.text('QA passed. Redirecting to final payment…').css('color', '#00a32a');
window.location.href = resp.data.checkout_url;
return;
}
// No checkout_url (zero-cost final 50) — already paid.
saveWizardState({ approved: true, paid_full: true });
wizardState.approved = true;
wizardState.paid_full = true;
$status.text('Approved — final payment complete.').css('color', '#00a32a');
setTimeout(function() { showStatusDashboard(); }, 1500);
} else {
var msg = (resp && resp.data && resp.data.error) ? resp.data.error : 'Approval failed.';
$status.text(msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Approve & Pay Final 50%');
}
},
error: function(xhr) {
var msg = 'Approval failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
} catch (ex) {
if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; }
}
$status.text(msg).css('color', '#d63638');
$btn.prop('disabled', false).text('Approve & Pay Final 50%');
}
});
});
}
// -----------------------------------------------------------------
// Status Dashboard
// -----------------------------------------------------------------
function showStatusDashboard() {
// Hide all step panels, show the dashboard.
$('.ad-wizard-panel').attr('hidden', true);
$('#ad-status-dashboard').removeAttr('hidden');
// Mark all steps completed.
$('.ad-wizard-steps li').removeClass('active').addClass('completed');
// Show a "final payment due" notice when the migration is approved
// but the final 50% hasn't been paid. Content is held in a staging
// dataset server-side until payment — this is a service-side gate,
// not a plugin-side lock. All plugin features remain accessible.
var $notice = $('#ad-final-payment-notice');
if (wizardState.approved && !wizardState.paid_full) {
$notice.removeAttr('hidden');
} else {
$notice.attr('hidden', true);
}
ensureProgressPolling();
}
// -----------------------------------------------------------------
// ADProgress — real-time migration progress (preserved from v2)
// -----------------------------------------------------------------
function ADProgress() {
this.pollMs = 5000;
this.timer = null;
}
ADProgress.prototype.start = function() {
var self = this;
this.tick();
this.timer = setInterval(function() { self.tick(); }, this.pollMs);
};
ADProgress.prototype.stop = function() {
if (this.timer) { clearInterval(this.timer); this.timer = null; }
};
ADProgress.prototype.tick = function() {
if (!W || !W.restUrl) { return; }
var self = this;
$.ajax({
url: W.restUrl + 'progress',
method: 'GET',
headers: { 'X-WP-Nonce': W.restNonce },
success: function(resp) {
if (resp && resp.success && resp.data) {
self.render(resp.data);
}
},
error: function() { /* Worker unreachable — leave last-known state. */ }
});
};
ADProgress.prototype.render = function(data) {
var stats = data.stats || {};
var total = parseInt(stats.total || 0, 10);
var success = parseInt(stats.success || 0, 10);
var pending = parseInt(stats.pending || 0, 10);
var failed = parseInt(stats.failed || 0, 10);
// Update both the migration panel (step 8) and dashboard IDs.
$('#ad-stat-total, #ad-dash-total, #ad-approve-total').text(total);
$('#ad-stat-success, #ad-dash-success, #ad-approve-success').text(success);
$('#ad-stat-pending, #ad-dash-pending, #ad-approve-pending').text(pending);
$('#ad-stat-failed, #ad-dash-failed, #ad-approve-failed').text(failed);
var pct = total > 0 ? Math.round((success / total) * 100) : 0;
$('#ad-progress-fill, #ad-dash-progress-fill').css('width', pct + '%');
$('#ad-progress-pct, #ad-dash-progress-pct').text(pct + '%');
var recent = data.recent || [];
renderProgressTable('#ad-progress-table tbody, #ad-dash-progress-table tbody', recent);
// Show continue-to-approval when migration is complete.
if (success > 0 && pending === 0) {
$('#ad-continue-approval-wrap').removeAttr('hidden');
}
};
function renderProgressTable(selector, recent) {
var $tbody = $(selector);
if (!$tbody.length) { return; }
if (!recent.length) {
$tbody.html('
| No migrations yet. |
');
return;
}
var rows = '';
for (var i = 0; i < recent.length; i++) {
var r = recent[i];
rows += '
' +
'| ' + (r.post_id || '') + ' | ' +
'' + (r.action || '') + ' | ' +
'' + (r.status || '') + ' | ' +
'' + (r.sanity_id || '') + ' | ' +
'' + (r.updated_at || r.created_at || '') + ' | ' +
'
';
}
$tbody.html(rows);
}
var progressInstance = null;
function ensureProgressPolling() {
if (!progressInstance) {
progressInstance = new ADProgress();
progressInstance.start();
}
}
W.ADProgress = ADProgress;
// -----------------------------------------------------------------
// Step 10: Next Steps — tabs + lazy-init modules
// -----------------------------------------------------------------
var nextStepsInitialized = false;
function initNextSteps() {
if (nextStepsInitialized) { return; }
nextStepsInitialized = true;
// Tab switching.
$('.ad-tab').on('click', function() {
var tab = $(this).data('tab');
$('.ad-tab').removeClass('active');
$(this).addClass('active');
$('.ad-tab-content').attr('hidden', true);
$('.ad-tab-content[data-tab-content="' + tab + '"]').removeAttr('hidden');
});
// Lazy-init each module on first tab activation.
ClaudeConnect.init();
SchemaPreview.init();
MigrationLog.init();
}
// -----------------------------------------------------------------
// ClaudeConnect module (merged from claude-connect.js)
// -----------------------------------------------------------------
var ClaudeConnect = {
initialized: false,
init: function() {
if (this.initialized) { return; }
this.initialized = true;
if (!$('#ad-cc-mint').length) { return; } // not rendered (no tenant)
var self = this;
// On load: try fetch_config first.
$('#ad-cc-loading').removeClass('ad-cc-hidden');
$.ajax({
url: W.restUrl + 'mcp/config',
method: 'GET',
beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', W.restNonce); }
}).done(function(resp) {
$('#ad-cc-loading').addClass('ad-cc-hidden');
if (resp && resp.success && resp.data) {
self.renderConfig(resp.data);
} else {
$('#ad-cc-mint').removeClass('ad-cc-hidden');
}
}).fail(function(jqXHR) {
$('#ad-cc-loading').addClass('ad-cc-hidden');
if (jqXHR.status === 404) {
$('#ad-cc-mint').removeClass('ad-cc-hidden');
} else {
var msg = (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.error) || 'Worker unreachable.';
self.setError(msg);
}
});
// Mint button.
$(document).on('click', '#ad-cc-mint-btn', function() {
var token = $('#ad-cc-token-input').val();
if (!token) { return; }
var $btn = $(this);
$btn.prop('disabled', true).text('Connecting…');
$.ajax({
url: W.restUrl + 'mcp/token',
method: 'POST',
beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', W.restNonce); },
data: { mcp_token: token }
}).done(function(resp) {
$btn.prop('disabled', false).text('Connect Claude');
if (resp && resp.success && resp.data) {
$('#ad-cc-token-input').val('');
self.renderConfig(resp.data);
} else {
var msg = (resp && resp.data && resp.data.error) || 'Could not store token.';
self.setError(msg);
}
}).fail(function(jqXHR) {
$btn.prop('disabled', false).text('Connect Claude');
var msg = (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.error) || 'Worker unreachable.';
self.setError(msg);
});
});
// Copy config.
$(document).on('click', '#ad-cc-copy-btn', function() {
var text = $('#ad-cc-snippet').text();
var $msg = $('#ad-cc-copy-msg').text('');
if (!text) { return; }
var done = function() {
$msg.text('Copied!').addClass('ad-cc-ok');
setTimeout(function() { $msg.text('').removeClass('ad-cc-ok'); }, 2500);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, function() { self.fallbackCopy(text, done); });
} else {
self.fallbackCopy(text, done);
}
});
// Revoke.
$(document).on('click', '#ad-cc-revoke-btn', function() {
if (!window.confirm('Revoke the Claude connection? Claude will lose access to your Sanity project. Migration continues unaffected.')) {
return;
}
var $msg = $('#ad-cc-revoke-msg').text('');
$.ajax({
url: W.restUrl + 'mcp/revoke',
method: 'POST',
beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', W.restNonce); }
}).done(function(resp) {
if (resp && resp.success) {
$('#ad-cc-config, #ad-cc-prompts').addClass('ad-cc-hidden');
$('#ad-cc-mint').removeClass('ad-cc-hidden');
$msg.text('Connection revoked.').addClass('ad-cc-ok');
setTimeout(function() { $msg.text('').removeClass('ad-cc-ok'); }, 3000);
} else {
$msg.text((resp && resp.data && resp.data.error) || 'Could not revoke.').addClass('ad-cc-err');
}
}).fail(function(jqXHR) {
$msg.text((jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.error) || 'Worker unreachable.').addClass('ad-cc-err');
});
});
},
renderConfig: function(data) {
if (!data || !data.config_snippet) {
this.setError('Worker returned an incomplete config.');
return;
}
$('#ad-cc-snippet').text(data.config_snippet);
if (data.claude_deep_link) {
$('#ad-cc-deeplink').attr('href', data.claude_deep_link);
}
if (data.starter_prompts && data.starter_prompts.length) {
var $list = $('#ad-cc-prompt-list').empty();
data.starter_prompts.forEach(function(p) {
$list.append('
' + $('').text(p).html() + '');
});
$('#ad-cc-prompts').removeClass('ad-cc-hidden');
}
$('#ad-cc-mint').addClass('ad-cc-hidden');
$('#ad-cc-config').removeClass('ad-cc-hidden');
},
setError: function(msg) {
$('#ad-cc-error .ad-cc-error-text').text(msg || 'Something went wrong.');
$('#ad-cc-error').removeClass('ad-cc-hidden');
},
fallbackCopy: function(text, done) {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); done(); } catch (e) { /* noop */ }
document.body.removeChild(ta);
}
};
// -----------------------------------------------------------------
// SchemaPreview module (merged from schema-preview.php inline script)
// -----------------------------------------------------------------
var SchemaPreview = {
initialized: false,
init: function() {
if (this.initialized) { return; }
this.initialized = true;
var btn = document.getElementById('ad-schema-generate');
if (!btn) { return; }
var self = this;
btn.addEventListener('click', function() {
btn.disabled = true;
self.setStatus('Generating…');
$('#ad-schema-rationale').addClass('ad-hidden');
document.getElementById('ad-schema-files').innerHTML = '';
$.ajax({
url: W.restUrl + 'schema-generate',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': W.restNonce
},
data: JSON.stringify({}),
success: function(json) {
btn.disabled = false;
if (!json || !json.success) {
self.setStatus((json && json.data && json.data.error) ? json.data.error : 'Generation failed', true);
return;
}
var data = (json && json.data) || {};
self.setStatus(data.deterministic ? 'AI unavailable — generated deterministic scaffold.' : 'Generated.');
if (data.rationale) {
$('#ad-schema-rationale').text(data.rationale).removeClass('ad-hidden');
}
self.renderFiles(data.schemas || []);
},
error: function(xhr) {
btn.disabled = false;
var msg = 'Request failed';
try {
var body = JSON.parse(xhr.responseText);
if (body && body.data && body.data.error) { msg = body.data.error; }
} catch (e) { if (xhr.status) { msg += ' (HTTP ' + xhr.status + ')'; } }
self.setStatus(msg, true);
}
});
});
},
setStatus: function(msg, err) {
var $s = $('#ad-schema-status');
$s.text(msg);
$s.toggleClass('ad-err', !!err);
},
renderFiles: function(schemas) {
var filesEl = document.getElementById('ad-schema-files');
var tmpl = document.getElementById('ad-schema-file-tmpl');
if (!filesEl || !tmpl) { return; }
var self = this;
schemas.forEach(function(schema) {
var node = tmpl.content.cloneNode(true);
node.querySelector('.ad-schema-filename').textContent = schema.filename;
node.querySelector('.ad-schema-code code').textContent = schema.code;
node.querySelector('.ad-schema-copy').addEventListener('click', function() {
navigator.clipboard.writeText(schema.code).then(function() {
self.setStatus('Copied ' + schema.filename);
}).catch(function() { self.setStatus('Copy failed', true); });
});
node.querySelector('.ad-schema-download').addEventListener('click', function() {
var blob = new Blob([schema.code], { type: 'text/plain' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = schema.filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
});
filesEl.appendChild(node);
});
}
};
// -----------------------------------------------------------------
// MigrationLog module (new — GET /logs with pagination)
// -----------------------------------------------------------------
var MigrationLog = {
initialized: false,
currentPaged: 1,
init: function() {
if (this.initialized) { return; }
this.initialized = true;
var self = this;
$('#ad-log-refresh').on('click', function() {
self.load(1);
});
$('#ad-log-status-filter').on('change', function() {
self.load(1);
});
// Lazy-load on first tab activation is handled by initNextSteps.
// Also load immediately in case the log tab is the default.
self.load(1);
},
load: function(paged) {
this.currentPaged = paged || 1;
var status = $('#ad-log-status-filter').val() || 'all';
var self = this;
$.ajax({
url: W.restUrl + 'logs',
method: 'GET',
headers: { 'X-WP-Nonce': W.restNonce },
data: { paged: this.currentPaged, status: status },
success: function(resp) {
if (resp && resp.success && resp.data) {
self.render(resp.data);
}
},
error: function() { /* leave loading state */ }
});
},
render: function(data) {
var $tbody = $('#ad-log-table tbody');
if (!$tbody.length) { return; }
var logs = data.logs || [];
if (!logs.length) {
$tbody.html('
| No logs found. |
');
} else {
var rows = '';
for (var i = 0; i < logs.length; i++) {
var log = logs[i];
rows += '' +
'| ' + (log.id || '') + ' | ' +
'' + (log.post_id || '') + ' | ' +
'' + (log.action || '') + ' | ' +
'' + (log.status || '') + ' | ' +
'' + (log.sanity_id || '') + ' | ' +
'' + (log.created_at || '') + ' | ' +
'
';
}
$tbody.html(rows);
}
// Pagination controls.
var $pag = $('#ad-log-pagination').empty();
var totalPages = parseInt(data.total_pages || 1, 10);
var current = parseInt(data.paged || 1, 10);
if (totalPages > 1) {
var self = this;
if (current > 1) {
$pag.append(' ');
}
$pag.append('Page ' + current + ' of ' + totalPages + '');
if (current < totalPages) {
$pag.append(' ');
}
$pag.find('button').on('click', function() {
self.load(parseInt($(this).data('paged'), 10));
});
}
}
};
// -----------------------------------------------------------------
// Init
// -----------------------------------------------------------------
$(function() {
if (!$('.ad-wizard-wrap').length) { return; }
initLoginStep();
initAnalyzeStep();
initConnectSanityStep();
initContentSettingsStep();
initMigrationStep();
initApproveStep();
loadWizardState();
});
})(jQuery);