/
home
/
altere25
/
.trash
/
wp-to-sanity-migration.10
/
/home/altere25/.trash/wp-to-sanity-migration.10
mkdir
upload
Name
Size
Mode
Actions
acf-json\.gitkeep
0
0644
edit
dl
rm
assets\css\admin.css
12113
0644
edit
dl
rm
assets\js\admin.js
52894
0644
edit
dl
rm
includes\class-ad-admin.php
10358
0644
edit
dl
rm
includes\class-ad-ai-client.php
10724
0644
edit
dl
rm
includes\class-ad-auth.php
9189
0644
edit
dl
rm
includes\class-ad-cli.php
8671
0644
edit
dl
rm
includes\class-ad-extractor.php
17237
0644
edit
dl
rm
includes\class-ad-mcp.php
6429
0644
edit
dl
rm
includes\class-ad-plugin-core.php
10252
0644
edit
dl
rm
includes\class-ad-progress.php
8022
0644
edit
dl
rm
includes\class-ad-rest.php
43443
0644
edit
dl
rm
includes\class-ad-uninstall.php
1966
0644
edit
dl
rm
includes\class-ad-webhook.php
24172
0644
edit
dl
rm
languages\.gitkeep
0
0644
edit
dl
rm
templates\admin\wizard.php
29414
0644
edit
dl
rm
wp-to-sanity-migration\acf-json\.gitkeep
0
0644
edit
dl
rm
wp-to-sanity-migration\assets\
0
0644
edit
dl
rm
wp-to-sanity-migration\assets\css\admin.css
12113
0644
edit
dl
rm
wp-to-sanity-migration\assets\js\admin.js
52894
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-admin.php
10358
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-ai-client.php
10724
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-auth.php
9189
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-cli.php
8671
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-extractor.php
17237
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-mcp.php
6429
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-plugin-core.php
10252
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-progress.php
8022
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-rest.php
43443
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-uninstall.php
1966
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-webhook.php
24172
0644
edit
dl
rm
wp-to-sanity-migration\languages\.gitkeep
0
0644
edit
dl
rm
wp-to-sanity-migration\package.json
220
0644
edit
dl
rm
wp-to-sanity-migration\readme.txt
8174
0644
edit
dl
rm
wp-to-sanity-migration\templates\
0
0644
edit
dl
rm
wp-to-sanity-migration\templates\admin\wizard.php
29414
0644
edit
dl
rm
wp-to-sanity-migration\uninstall.php
365
0644
edit
dl
rm
wp-to-sanity-migration\wp-to-sanity-migration.php
4418
0644
edit
dl
rm
Edit:
/home/altere25/.trash/wp-to-sanity-migration.10/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, '"').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 = '<table class="widefat" style="margin-bottom:1em;">'; html += '<thead><tr><th>Item</th><th>Qty</th><th>Price</th><th>Total</th></tr></thead><tbody>'; (quote.line_items || []).forEach(function(item) { html += '<tr><td>' + (item.label || item.key) + '</td><td>' + item.quantity + '</td><td>' + centsToUsd(item.unit_price_cents) + '</td><td>' + centsToUsd(item.total_cents) + '</td></tr>'; }); html += '</tbody></table>'; if (quote.add_ons && quote.add_ons.length) { html += '<h4>Add-ons</h4><ul>'; quote.add_ons.forEach(function(a) { html += '<li>' + (a.label || a.key) + ' — ' + centsToUsd(a.total_cents) + '</li>'; }); html += '</ul>'; } html += '<p><strong>Subtotal: ' + centsToUsd(quote.subtotal_cents) + '</strong></p>'; html += '<p style="font-size:1.2em;"><strong>Total: ' + centsToUsd(quote.total_cents) + '</strong></p>'; html += '<p>Upfront (50%): <strong>' + centsToUsd(quote.upfront_50_cents) + '</strong> · Final (50%): ' + centsToUsd(quote.final_50_cents) + '</p>'; if (quote.estimated_days) { html += '<p class="description">Estimated time: ' + quote.estimated_days + ' days</p>'; } if (data.deterministic) { html += '<p class="description">Note: using estimated pricing (AI pricing model unavailable).</p>'; } var quoteId = data.quote_id || ''; // T&C checkbox — checkout disabled until the customer agrees. html += '<p style="margin-top:1.5em;"><label><input type="checkbox" id="ad-tc-agree" /> ' + 'I agree to the <a href="' + escapeHtml(W.tcUrl || '#') + '" target="_blank" rel="noopener">Terms & Conditions</a></label></p>'; html += '<button type="button" class="button button-primary" id="ad-checkout-trigger" ' + 'disabled>Continue to payment</button>'; $('#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($('<div>').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('<tr class="ad-empty-row"><td colspan="5"><span class="description">No migrations yet.</span></td></tr>'); return; } var rows = ''; for (var i = 0; i < recent.length; i++) { var r = recent[i]; rows += '<tr>' + '<td>' + (r.post_id || '') + '</td>' + '<td>' + (r.action || '') + '</td>' + '<td><span class="status-badge ' + (r.status === 'success' ? 'success' : (r.status === 'failed' ? 'error' : 'pending')) + '">' + (r.status || '') + '</span></td>' + '<td><code>' + (r.sanity_id || '') + '</code></td>' + '<td>' + (r.updated_at || r.created_at || '') + '</td>' + '</tr>'; } $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('<li><code>' + $('<div>').text(p).html() + '</code></li>'); }); $('#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('<tr class="ad-empty-row"><td colspan="6"><span class="description">No logs found.</span></td></tr>'); } else { var rows = ''; for (var i = 0; i < logs.length; i++) { var log = logs[i]; rows += '<tr>' + '<td>' + (log.id || '') + '</td>' + '<td>' + (log.post_id || '') + '</td>' + '<td>' + (log.action || '') + '</td>' + '<td><span class="status-badge ' + (log.status === 'success' ? 'success' : (log.status === 'failed' ? 'error' : 'pending')) + '">' + (log.status || '') + '</span></td>' + '<td><code>' + (log.sanity_id || '') + '</code></td>' + '<td>' + (log.created_at || '') + '</td>' + '</tr>'; } $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('<button type="button" class="button" data-paged="' + (current - 1) + '">« Prev</button> '); } $pag.append('<span class="ad-log-page-info">Page ' + current + ' of ' + totalPages + '</span>'); if (current < totalPages) { $pag.append(' <button type="button" class="button" data-paged="' + (current + 1) + '">Next »</button>'); } $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);
Save
cmd:
run