payload-wordpress-migrator 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,16 @@
1
1
  "use client";
2
2
  import { useState, useEffect } from 'react';
3
3
 
4
- // Persist config without sensitive credentials (password never stored in localStorage)
5
- const persistConfig = (config)=>{
6
- const { wpPassword, ...safeConfig } = config;
7
- localStorage.setItem('wp-site-config', JSON.stringify(safeConfig));
4
+ const persistScanResult = (config)=>{
5
+ localStorage.setItem('wp-scan-result', JSON.stringify(config));
6
+ };
7
+ const loadScanResult = ()=>{
8
+ try {
9
+ const saved = localStorage.getItem('wp-scan-result');
10
+ return saved ? JSON.parse(saved) : null;
11
+ } catch {
12
+ return null;
13
+ }
8
14
  };
9
15
  const useMigrationDashboard = (initialSummary)=>{
10
16
  const [summary, setSummary] = useState(initialSummary);
@@ -13,17 +19,8 @@ const useMigrationDashboard = (initialSummary)=>{
13
19
  const [siteConfig, setSiteConfig] = useState({
14
20
  connectionStatus: 'not-scanned',
15
21
  discoveredContent: [],
16
- siteName: '',
17
- totalItems: 0,
18
- wpPassword: '',
19
- wpSiteUrl: '',
20
- wpUsername: ''
21
- });
22
- const [configState, setConfigState] = useState({
23
- isEditing: true,
24
- isSaved: false
22
+ totalItems: 0
25
23
  });
26
- const [showSiteConfig, setShowSiteConfig] = useState(true);
27
24
  const addLog = (message)=>{
28
25
  const timestamp = new Date().toLocaleTimeString();
29
26
  const logEntry = `[${timestamp}] ${message}`;
@@ -32,12 +29,8 @@ const useMigrationDashboard = (initialSummary)=>{
32
29
  logEntry
33
30
  ]);
34
31
  };
35
- const clearLogs = ()=>{
36
- setLogs([]);
37
- };
38
32
  const refreshDashboard = async ()=>{
39
33
  try {
40
- console.trace('Call stack:');
41
34
  const response = await fetch('/api/wordpress/migration-summary?refresh=true');
42
35
  const newSummary = await response.json();
43
36
  setSummary(newSummary);
@@ -47,26 +40,11 @@ const useMigrationDashboard = (initialSummary)=>{
47
40
  };
48
41
  // Load initial data on mount
49
42
  useEffect(()=>{
50
- const loadInitialData = async ()=>{
51
- await refreshDashboard();
52
- };
53
- const loadSavedConfig = ()=>{
54
- try {
55
- const savedConfig = localStorage.getItem('wp-site-config');
56
- if (savedConfig) {
57
- const parsed = JSON.parse(savedConfig);
58
- setSiteConfig(parsed);
59
- setConfigState({
60
- isEditing: false,
61
- isSaved: true
62
- });
63
- }
64
- } catch (error) {
65
- console.error('Failed to load saved config:', error);
66
- }
67
- };
68
- void loadInitialData();
69
- loadSavedConfig();
43
+ void refreshDashboard();
44
+ const saved = loadScanResult();
45
+ if (saved) {
46
+ setSiteConfig(saved);
47
+ }
70
48
  }, []);
71
49
  // Smart periodic refresh - only when jobs are active
72
50
  useEffect(()=>{
@@ -84,20 +62,6 @@ const useMigrationDashboard = (initialSummary)=>{
84
62
  }, [
85
63
  summary.activeJobs
86
64
  ]);
87
- // Auto-close site config when scan is successful AND config is saved
88
- useEffect(()=>{
89
- if (siteConfig.connectionStatus === 'scanned' && configState.isSaved && !configState.isEditing && siteConfig.discoveredContent && siteConfig.discoveredContent.length > 0) {
90
- const timer = setTimeout(()=>{
91
- setShowSiteConfig(false);
92
- }, 5000);
93
- return ()=>clearTimeout(timer);
94
- }
95
- }, [
96
- siteConfig.connectionStatus,
97
- configState.isSaved,
98
- configState.isEditing,
99
- siteConfig.discoveredContent
100
- ]);
101
65
  // Only refresh on significant page visibility changes
102
66
  useEffect(()=>{
103
67
  let lastVisibilityChange = Date.now();
@@ -123,7 +87,7 @@ const useMigrationDashboard = (initialSummary)=>{
123
87
  if (event.type?.startsWith('migration-job-')) {
124
88
  void refreshDashboard();
125
89
  }
126
- } catch (error) {
90
+ } catch {
127
91
  // Ignore parsing errors
128
92
  }
129
93
  }
@@ -131,27 +95,6 @@ const useMigrationDashboard = (initialSummary)=>{
131
95
  window.addEventListener('storage', handleStorageChange);
132
96
  return ()=>window.removeEventListener('storage', handleStorageChange);
133
97
  }, []);
134
- const validateSiteConfig = ()=>{
135
- const { siteName, wpPassword, wpSiteUrl, wpUsername } = siteConfig;
136
- if (!siteName.trim() || !wpSiteUrl.trim() || !wpUsername.trim() || !wpPassword.trim()) {
137
- alert('Please fill in all WordPress site configuration fields');
138
- return false;
139
- }
140
- return true;
141
- };
142
- const requireSiteConfig = (actionVerb)=>{
143
- if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
144
- alert(`Please save a valid WordPress site configuration and scan for content before ${actionVerb} migration jobs.`);
145
- return false;
146
- }
147
- return true;
148
- };
149
- const getSiteCredentials = ()=>({
150
- siteName: siteConfig.siteName,
151
- wpPassword: siteConfig.wpPassword,
152
- wpSiteUrl: siteConfig.wpSiteUrl,
153
- wpUsername: siteConfig.wpUsername
154
- });
155
98
  const getJobName = (jobId)=>{
156
99
  const job = summary.recentJobs?.find((j)=>j.id === jobId);
157
100
  return job?.jobName || `Job ${jobId}`;
@@ -160,16 +103,14 @@ const useMigrationDashboard = (initialSummary)=>{
160
103
  const startMigrationJob = async (jobId)=>{
161
104
  try {
162
105
  setLoading(true);
163
- if (!requireSiteConfig('starting')) return;
164
106
  await refreshDashboard();
165
- clearLogs();
107
+ setLogs([]);
166
108
  const jobName = getJobName(jobId);
167
109
  addLog(`Migration "${jobName}" started`);
168
110
  const response = await fetch(`/api/wordpress/migration-jobs`, {
169
111
  body: JSON.stringify({
170
112
  action: 'start',
171
- jobId,
172
- siteConfig: getSiteCredentials()
113
+ jobId
173
114
  }),
174
115
  headers: {
175
116
  'Content-Type': 'application/json'
@@ -209,7 +150,6 @@ const useMigrationDashboard = (initialSummary)=>{
209
150
  const resumeMigrationJob = async (jobId)=>{
210
151
  try {
211
152
  setLoading(true);
212
- if (!requireSiteConfig('resuming')) return;
213
153
  const jobName = getJobName(jobId);
214
154
  addLog(`Migration "${jobName}" resumed`);
215
155
  const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {
@@ -223,8 +163,7 @@ const useMigrationDashboard = (initialSummary)=>{
223
163
  const startResponse = await fetch(`/api/wordpress/migration-jobs`, {
224
164
  body: JSON.stringify({
225
165
  action: 'resume',
226
- jobId,
227
- siteConfig: getSiteCredentials()
166
+ jobId
228
167
  }),
229
168
  headers: {
230
169
  'Content-Type': 'application/json'
@@ -247,14 +186,12 @@ const useMigrationDashboard = (initialSummary)=>{
247
186
  const retryMigrationJob = async (jobId)=>{
248
187
  try {
249
188
  setLoading(true);
250
- if (!requireSiteConfig('retrying')) return;
251
189
  const jobName = getJobName(jobId);
252
190
  addLog(`Retrying migration "${jobName}"`);
253
191
  const response = await fetch(`/api/wordpress/migration-jobs`, {
254
192
  body: JSON.stringify({
255
193
  action: 'retry',
256
- jobId,
257
- siteConfig: getSiteCredentials()
194
+ jobId
258
195
  }),
259
196
  headers: {
260
197
  'Content-Type': 'application/json'
@@ -277,18 +214,14 @@ const useMigrationDashboard = (initialSummary)=>{
277
214
  const updateMigrationJob = async (jobId)=>{
278
215
  try {
279
216
  setLoading(true);
280
- if (!requireSiteConfig('updating')) return;
281
217
  const jobName = getJobName(jobId);
282
218
  const confirmed = confirm(`Are you sure you want to update the "${jobName}" migration? This will update existing items with new field mappings without re-processing all content.`);
283
- if (!confirmed) {
284
- return;
285
- }
219
+ if (!confirmed) return;
286
220
  addLog(`Updating migration "${jobName}" with new field mappings`);
287
221
  const response = await fetch(`/api/wordpress/migration-jobs`, {
288
222
  body: JSON.stringify({
289
223
  action: 'update',
290
- jobId,
291
- siteConfig: getSiteCredentials()
224
+ jobId
292
225
  }),
293
226
  headers: {
294
227
  'Content-Type': 'application/json'
@@ -313,18 +246,14 @@ const useMigrationDashboard = (initialSummary)=>{
313
246
  const restartMigrationJob = async (jobId)=>{
314
247
  try {
315
248
  setLoading(true);
316
- if (!requireSiteConfig('restarting')) return;
317
249
  const jobName = getJobName(jobId);
318
250
  const confirmed = confirm(`Are you sure you want to restart the "${jobName}" migration? This will re-process ALL items, including those that were already successfully migrated.`);
319
- if (!confirmed) {
320
- return;
321
- }
251
+ if (!confirmed) return;
322
252
  addLog(`Restarting migration "${jobName}"`);
323
253
  const response = await fetch(`/api/wordpress/migration-jobs`, {
324
254
  body: JSON.stringify({
325
255
  action: 'restart',
326
- jobId,
327
- siteConfig: getSiteCredentials()
256
+ jobId
328
257
  }),
329
258
  headers: {
330
259
  'Content-Type': 'application/json'
@@ -349,9 +278,7 @@ const useMigrationDashboard = (initialSummary)=>{
349
278
  setLoading(true);
350
279
  const jobName = getJobName(jobId);
351
280
  const confirmed = confirm(`Are you sure you want to rollback the "${jobName}" migration? This will DELETE all items that were migrated by this job.`);
352
- if (!confirmed) {
353
- return;
354
- }
281
+ if (!confirmed) return;
355
282
  addLog(`Rolling back migration "${jobName}"`);
356
283
  const response = await fetch(`/api/wordpress/migration-jobs`, {
357
284
  body: JSON.stringify({
@@ -396,22 +323,14 @@ const useMigrationDashboard = (initialSummary)=>{
396
323
  return rollbackMigrationJob(jobId);
397
324
  }
398
325
  };
399
- // --- Config handlers ---
326
+ // --- Content discovery ---
400
327
  const scanWordPressContent = async ()=>{
401
- if (!validateSiteConfig()) {
402
- return;
403
- }
404
328
  setSiteConfig((prev)=>({
405
329
  ...prev,
406
330
  connectionStatus: 'scanning'
407
331
  }));
408
332
  try {
409
333
  const response = await fetch('/api/wordpress/discover-content', {
410
- body: JSON.stringify({
411
- wpPassword: siteConfig.wpPassword,
412
- wpSiteUrl: siteConfig.wpSiteUrl,
413
- wpUsername: siteConfig.wpUsername
414
- }),
415
334
  headers: {
416
335
  'Content-Type': 'application/json'
417
336
  },
@@ -422,112 +341,37 @@ const useMigrationDashboard = (initialSummary)=>{
422
341
  const contentTypes = result.contentTypes || [];
423
342
  const totalItems = result.totalItems || 0;
424
343
  const updatedConfig = {
425
- ...siteConfig,
426
344
  connectionStatus: 'scanned',
427
345
  discoveredContent: contentTypes,
428
346
  totalItems
429
347
  };
430
348
  setSiteConfig(updatedConfig);
431
- try {
432
- persistConfig(updatedConfig);
433
- } catch (error) {
434
- console.error('Failed to save discovered content to localStorage:', error);
435
- }
349
+ persistScanResult(updatedConfig);
436
350
  if (contentTypes.length === 0) {
437
- alert('WordPress scan completed, but no content was found. This could be due to:\n\n' + ' Empty WordPress site (no posts, pages, media, etc.)\n' + ' Insufficient user permissions\n' + ' WordPress REST API restrictions\n\n' + 'Please check your WordPress site and user permissions.');
351
+ alert('WordPress scan completed, but no content was found. This could be due to:\n\n' + '- Empty WordPress site\n' + '- Insufficient user permissions\n' + '- WordPress REST API restrictions\n\n' + 'Please check your WordPress site and user permissions.');
438
352
  }
439
353
  } else {
440
354
  const updatedConfig = {
441
- ...siteConfig,
442
355
  connectionStatus: 'failed',
443
356
  discoveredContent: [],
444
357
  totalItems: 0
445
358
  };
446
359
  setSiteConfig(updatedConfig);
447
- try {
448
- persistConfig(updatedConfig);
449
- } catch (error) {
450
- console.error('Failed to save failed scan state to localStorage:', error);
451
- }
360
+ persistScanResult(updatedConfig);
452
361
  alert(`WordPress scan failed: ${result.error || 'Unknown error'}`);
453
362
  }
454
363
  } catch (error) {
455
364
  console.error('Content scan failed:', error);
456
365
  const updatedConfig = {
457
- ...siteConfig,
458
366
  connectionStatus: 'failed',
459
367
  discoveredContent: [],
460
368
  totalItems: 0
461
369
  };
462
370
  setSiteConfig(updatedConfig);
463
- try {
464
- persistConfig(updatedConfig);
465
- } catch (storageError) {
466
- console.error('Failed to save network error state to localStorage:', storageError);
467
- }
371
+ persistScanResult(updatedConfig);
468
372
  alert('WordPress scan failed: Network error');
469
373
  }
470
374
  };
471
- const handleSiteConfigChange = (field, value)=>{
472
- const connectionFields = [
473
- 'wpSiteUrl',
474
- 'wpUsername',
475
- 'wpPassword'
476
- ];
477
- const shouldReset = connectionFields.includes(field);
478
- setSiteConfig((prev)=>({
479
- ...prev,
480
- [field]: value,
481
- ...shouldReset && {
482
- connectionStatus: 'not-scanned',
483
- discoveredContent: [],
484
- totalItems: 0
485
- }
486
- }));
487
- };
488
- const handleSaveConfig = async ()=>{
489
- if (!validateSiteConfig()) {
490
- return;
491
- }
492
- try {
493
- persistConfig(siteConfig);
494
- setConfigState({
495
- isEditing: false,
496
- isSaved: true
497
- });
498
- await scanWordPressContent();
499
- } catch (error) {
500
- console.error('Failed to save configuration:', error);
501
- alert('Failed to save configuration. Please try again.');
502
- }
503
- };
504
- const handleEditConfig = ()=>{
505
- setConfigState({
506
- isEditing: true,
507
- isSaved: true
508
- });
509
- };
510
- const handleCancelEdit = ()=>{
511
- try {
512
- const savedConfig = localStorage.getItem('wp-site-config');
513
- if (savedConfig) {
514
- const parsed = JSON.parse(savedConfig);
515
- setSiteConfig((prev)=>({
516
- ...prev,
517
- ...parsed,
518
- connectionStatus: parsed.connectionStatus || prev.connectionStatus || 'not-scanned',
519
- discoveredContent: parsed.discoveredContent || prev.discoveredContent || [],
520
- totalItems: parsed.totalItems || prev.totalItems || 0
521
- }));
522
- }
523
- setConfigState({
524
- isEditing: false,
525
- isSaved: true
526
- });
527
- } catch (error) {
528
- console.error('Failed to reload saved configuration:', error);
529
- }
530
- };
531
375
  // Combine client-side action logs with server-side migration logs
532
376
  const getAllLogs = ()=>{
533
377
  const combinedLogs = [];
@@ -562,19 +406,13 @@ const useMigrationDashboard = (initialSummary)=>{
562
406
  return combinedLogs.sort((a, b)=>b.timestamp.getTime() - a.timestamp.getTime()).slice(0, 100).map((log)=>log.message);
563
407
  };
564
408
  return {
565
- configState,
566
409
  loading,
567
- showSiteConfig,
568
410
  siteConfig,
569
411
  summary,
570
- // Config actions
571
- handleCancelEdit,
572
- handleEditConfig,
573
- handleSaveConfig,
574
- handleSiteConfigChange,
575
- setShowSiteConfig,
576
412
  // Job actions
577
413
  handleJobAction,
414
+ // Content discovery
415
+ scanWordPressContent,
578
416
  // Utilities
579
417
  getAllLogs
580
418
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useMigrationDashboard.js","sources":["../../../src/components/dashboard/useMigrationDashboard.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\n\nimport type { JobActionType, MigrationSummary, SiteConfigState, WordPressSiteConfig } from './types'\n\n// Persist config without sensitive credentials (password never stored in localStorage)\nconst persistConfig = (config: WordPressSiteConfig) => {\n const { wpPassword, ...safeConfig } = config\n localStorage.setItem('wp-site-config', JSON.stringify(safeConfig))\n}\n\nexport const useMigrationDashboard = (initialSummary: MigrationSummary) => {\n const [summary, setSummary] = useState<MigrationSummary>(initialSummary)\n const [loading, setLoading] = useState(false)\n const [logs, setLogs] = useState<string[]>([])\n\n const [siteConfig, setSiteConfig] = useState<WordPressSiteConfig>({\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n siteName: '',\n totalItems: 0,\n wpPassword: '',\n wpSiteUrl: '',\n wpUsername: '',\n })\n const [configState, setConfigState] = useState<SiteConfigState>({\n isEditing: true,\n isSaved: false,\n })\n const [showSiteConfig, setShowSiteConfig] = useState(true)\n\n const addLog = (message: string) => {\n const timestamp = new Date().toLocaleTimeString()\n const logEntry = `[${timestamp}] ${message}`\n setLogs((prev) => [...prev, logEntry])\n }\n\n const clearLogs = () => {\n setLogs([])\n }\n\n const refreshDashboard = async () => {\n try {\n console.trace('Call stack:')\n const response = await fetch('/api/wordpress/migration-summary?refresh=true')\n const newSummary = await response.json()\n setSummary(newSummary)\n } catch (error) {\n console.error('Failed to refresh dashboard:', error)\n }\n }\n\n // Load initial data on mount\n useEffect(() => {\n const loadInitialData = async () => {\n await refreshDashboard()\n }\n\n const loadSavedConfig = () => {\n try {\n const savedConfig = localStorage.getItem('wp-site-config')\n if (savedConfig) {\n const parsed = JSON.parse(savedConfig)\n setSiteConfig(parsed)\n setConfigState({ isEditing: false, isSaved: true })\n }\n } catch (error) {\n console.error('Failed to load saved config:', error)\n }\n }\n\n void loadInitialData()\n loadSavedConfig()\n }, [])\n\n // Smart periodic refresh - only when jobs are active\n useEffect(() => {\n let refreshInterval: NodeJS.Timeout | null = null\n\n if (summary.activeJobs > 0) {\n refreshInterval = setInterval(() => {\n void refreshDashboard()\n }, 15000)\n }\n\n return () => {\n if (refreshInterval) {\n clearInterval(refreshInterval)\n }\n }\n }, [summary.activeJobs])\n\n // Auto-close site config when scan is successful AND config is saved\n useEffect(() => {\n if (\n siteConfig.connectionStatus === 'scanned' &&\n configState.isSaved &&\n !configState.isEditing &&\n siteConfig.discoveredContent &&\n siteConfig.discoveredContent.length > 0\n ) {\n const timer = setTimeout(() => {\n setShowSiteConfig(false)\n }, 5000)\n\n return () => clearTimeout(timer)\n }\n }, [\n siteConfig.connectionStatus,\n configState.isSaved,\n configState.isEditing,\n siteConfig.discoveredContent,\n ])\n\n // Only refresh on significant page visibility changes\n useEffect(() => {\n let lastVisibilityChange = Date.now()\n\n const handleVisibilityChange = () => {\n if (!document.hidden && Date.now() - lastVisibilityChange > 30000) {\n void refreshDashboard()\n }\n\n if (document.hidden) {\n lastVisibilityChange = Date.now()\n }\n }\n\n document.addEventListener('visibilitychange', handleVisibilityChange)\n\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange)\n }\n }, [])\n\n // Listen for localStorage events to immediately refresh when jobs are changed\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === 'migration-event' && e.newValue) {\n try {\n const event = JSON.parse(e.newValue)\n if (event.type?.startsWith('migration-job-')) {\n void refreshDashboard()\n }\n } catch (error) {\n // Ignore parsing errors\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => window.removeEventListener('storage', handleStorageChange)\n }, [])\n\n const validateSiteConfig = () => {\n const { siteName, wpPassword, wpSiteUrl, wpUsername } = siteConfig\n if (!siteName.trim() || !wpSiteUrl.trim() || !wpUsername.trim() || !wpPassword.trim()) {\n alert('Please fill in all WordPress site configuration fields')\n return false\n }\n return true\n }\n\n const requireSiteConfig = (actionVerb: string): boolean => {\n if (\n !configState.isSaved ||\n !validateSiteConfig() ||\n siteConfig.connectionStatus !== 'scanned'\n ) {\n alert(\n `Please save a valid WordPress site configuration and scan for content before ${actionVerb} migration jobs.`,\n )\n return false\n }\n return true\n }\n\n const getSiteCredentials = () => ({\n siteName: siteConfig.siteName,\n wpPassword: siteConfig.wpPassword,\n wpSiteUrl: siteConfig.wpSiteUrl,\n wpUsername: siteConfig.wpUsername,\n })\n\n const getJobName = (jobId: string) => {\n const job = summary.recentJobs?.find((j) => j.id === jobId)\n return job?.jobName || `Job ${jobId}`\n }\n\n // --- Job action handlers ---\n\n const startMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('starting')) return\n\n await refreshDashboard()\n clearLogs()\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" started`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'start',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error starting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to start job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error starting migration:', error)\n alert(\n `Failed to start migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const pauseMigrationJob = async (jobId: string) => {\n try {\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" paused`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=pause`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n addLog(`Error pausing migration \"${jobName}\"`)\n throw new Error('Failed to pause migration job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error pausing migration:', error)\n alert('Failed to pause migration job')\n }\n }\n\n const resumeMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('resuming')) return\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" resumed`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n const startResponse = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'resume',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!startResponse.ok) {\n const errorData = await startResponse.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error resuming migration:', error)\n alert(\n `Failed to resume migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const retryMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('retrying')) return\n\n const jobName = getJobName(jobId)\n addLog(`Retrying migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'retry',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error retrying migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to retry job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error retrying migration:', error)\n alert(\n `Failed to retry migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const updateMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('updating')) return\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to update the \"${jobName}\" migration? This will update existing items with new field mappings without re-processing all content.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Updating migration \"${jobName}\" with new field mappings`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'update',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error updating migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to update job')\n }\n\n await response.json()\n addLog(`Successfully started update for \"${jobName}\"`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error updating migration:', error)\n alert(\n `Failed to update migration job: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const restartMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('restarting')) return\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to restart the \"${jobName}\" migration? This will re-process ALL items, including those that were already successfully migrated.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Restarting migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'restart',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error restarting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to restart job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error restarting migration:', error)\n alert(\n `Failed to restart migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const rollbackMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to rollback the \"${jobName}\" migration? This will DELETE all items that were migrated by this job.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Rolling back migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'rollback',\n jobId,\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error rolling back migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to rollback job')\n }\n\n const result = await response.json()\n addLog(`Rollback complete for \"${jobName}\": ${result.data?.deleted ?? 0} items deleted`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error rolling back migration:', error)\n alert(\n `Failed to rollback migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const handleJobAction = async (action: JobActionType, jobId: string) => {\n switch (action) {\n case 'start':\n return startMigrationJob(jobId)\n case 'pause':\n return pauseMigrationJob(jobId)\n case 'resume':\n return resumeMigrationJob(jobId)\n case 'retry':\n return retryMigrationJob(jobId)\n case 'update':\n return updateMigrationJob(jobId)\n case 'restart':\n return restartMigrationJob(jobId)\n case 'rollback':\n return rollbackMigrationJob(jobId)\n }\n }\n\n // --- Config handlers ---\n\n const scanWordPressContent = async () => {\n if (!validateSiteConfig()) {\n return\n }\n\n setSiteConfig((prev) => ({ ...prev, connectionStatus: 'scanning' }))\n\n try {\n const response = await fetch('/api/wordpress/discover-content', {\n body: JSON.stringify({\n wpPassword: siteConfig.wpPassword,\n wpSiteUrl: siteConfig.wpSiteUrl,\n wpUsername: siteConfig.wpUsername,\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (response.ok && result.success) {\n const contentTypes = result.contentTypes || []\n const totalItems = result.totalItems || 0\n\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'scanned' as const,\n discoveredContent: contentTypes,\n totalItems,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (error) {\n console.error('Failed to save discovered content to localStorage:', error)\n }\n\n if (contentTypes.length === 0) {\n alert(\n 'WordPress scan completed, but no content was found. This could be due to:\\n\\n' +\n '• Empty WordPress site (no posts, pages, media, etc.)\\n' +\n '• Insufficient user permissions\\n' +\n '• WordPress REST API restrictions\\n\\n' +\n 'Please check your WordPress site and user permissions.',\n )\n }\n } else {\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'failed' as const,\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (error) {\n console.error('Failed to save failed scan state to localStorage:', error)\n }\n\n alert(`WordPress scan failed: ${result.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Content scan failed:', error)\n\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'failed' as const,\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (storageError) {\n console.error('Failed to save network error state to localStorage:', storageError)\n }\n\n alert('WordPress scan failed: Network error')\n }\n }\n\n const handleSiteConfigChange = (field: keyof WordPressSiteConfig, value: string) => {\n const connectionFields = ['wpSiteUrl', 'wpUsername', 'wpPassword']\n const shouldReset = connectionFields.includes(field)\n\n setSiteConfig((prev) => ({\n ...prev,\n [field]: value,\n ...(shouldReset && {\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n totalItems: 0,\n }),\n }))\n }\n\n const handleSaveConfig = async () => {\n if (!validateSiteConfig()) {\n return\n }\n\n try {\n persistConfig(siteConfig)\n setConfigState({\n isEditing: false,\n isSaved: true,\n })\n\n await scanWordPressContent()\n } catch (error) {\n console.error('Failed to save configuration:', error)\n alert('Failed to save configuration. Please try again.')\n }\n }\n\n const handleEditConfig = () => {\n setConfigState({\n isEditing: true,\n isSaved: true,\n })\n }\n\n const handleCancelEdit = () => {\n try {\n const savedConfig = localStorage.getItem('wp-site-config')\n if (savedConfig) {\n const parsed = JSON.parse(savedConfig)\n setSiteConfig((prev) => ({\n ...prev,\n ...parsed,\n connectionStatus: parsed.connectionStatus || prev.connectionStatus || 'not-scanned',\n discoveredContent: parsed.discoveredContent || prev.discoveredContent || [],\n totalItems: parsed.totalItems || prev.totalItems || 0,\n }))\n }\n setConfigState({\n isEditing: false,\n isSaved: true,\n })\n } catch (error) {\n console.error('Failed to reload saved configuration:', error)\n }\n }\n\n // Combine client-side action logs with server-side migration logs\n const getAllLogs = (): string[] => {\n const combinedLogs: Array<{\n level?: string\n message: string\n source: 'client' | 'server'\n timestamp: Date\n }> = []\n\n logs.forEach((log) => {\n const timestampMatch = log.match(/^\\[(\\d{1,2}:\\d{2}:\\d{2}(?:\\s?[AP]M)?)\\]/)\n let timestamp = new Date()\n\n if (timestampMatch) {\n const timeStr = timestampMatch[1]\n const today = new Date()\n const timeWithDate = `${today.toDateString()} ${timeStr}`\n timestamp = new Date(timeWithDate)\n }\n\n combinedLogs.push({\n message: log,\n source: 'client',\n timestamp,\n })\n })\n\n if (summary.recentLogs) {\n summary.recentLogs.forEach((log) => {\n const timestamp = new Date(log.timestamp)\n const levelIcon = log.level === 'error' ? '❌' : log.level === 'warning' ? '⚠️' : 'ℹ️'\n const formattedMessage = `[${timestamp.toLocaleTimeString()}] ${levelIcon} [${log.jobName}] ${log.message}`\n\n combinedLogs.push({\n level: log.level,\n message: formattedMessage,\n source: 'server',\n timestamp,\n })\n })\n }\n\n return combinedLogs\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, 100)\n .map((log) => log.message)\n }\n\n return {\n configState,\n loading,\n showSiteConfig,\n siteConfig,\n summary,\n\n // Config actions\n handleCancelEdit,\n handleEditConfig,\n handleSaveConfig,\n handleSiteConfigChange,\n setShowSiteConfig,\n\n // Job actions\n handleJobAction,\n\n // Utilities\n getAllLogs,\n }\n}\n"],"names":["localStorage","discoveredContent","setLogs","prev","logEntry","console","loadSavedConfig","refreshInterval","summary","siteConfig","configState","lastVisibilityChange","alert","siteName","wpPassword","wpSiteUrl","wpUsername","clearLogs","addLog","jobId","totalItems","connectionStatus","timestamp","combinedLogs","level","loading","showSiteConfig","handleCancelEdit","handleEditConfig","handleSaveConfig","handleSiteConfigChange","setShowSiteConfig","handleJobAction","getAllLogs"],"mappings":";;;AAMA;AACA;AACE;AACAA;AACF;AAEO;AACL;AACA;AACA;AAEA;;AAEEC;;;;;;AAMF;AACA;;;AAGA;AACA;AAEA;;AAEE;AACAC;AAAsBC;AAAMC;AAAS;AACvC;AAEA;AACEF;AACF;AAEA;;AAEIG;;;;AAIF;;AAEA;AACF;;;AAIE;;AAEA;AAEA;;;AAGI;;;;;;AAGmD;AACnD;AACF;;AAEA;AACF;;AAGAC;AACF;;;AAIE;;AAGEC;;;AAGF;;AAGE;;AAEA;AACF;;AACEC;AAAmB;;;AAIrB;AAOE;;;AAIA;AACF;;AAEAC;AACAC;AACAA;AACAD;AACD;;;;AAMC;;;AAGE;;AAGEE;AACF;AACF;;;;AAMA;AACF;;;AAIE;AACE;;AAEI;AACA;;AAEA;AACF;;AAEA;AACF;AACF;;AAGA;AACF;AAEA;;AAEE;;;AAGA;;AAEF;AAEA;;AAMIC;;AAIF;;AAEF;;AAGEC;AACAC;AACAC;AACAC;;AAGF;;AAEE;AACF;;AAIA;;;;;AAMIC;AAEA;AACAC;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;AAEI;AACAD;;;AAIA;;AAGEA;AACA;AACF;;AAGF;;;AAGA;AACF;AAEA;;;;AAKI;AACAA;;;AAIA;;;;AAKE;AACF;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AACAD;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AAEA;AAIA;AACE;AACF;AAEAD;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;AAEA;AACAD;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AAEA;AAIA;AACE;AACF;AAEAA;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AACE;AACF;AAEAD;AAEA;;;AAGIC;AACF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGAD;;AAGF;;;;;AAOA;AACF;;;;AAKM;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACJ;AACF;;AAIA;AACE;AACE;AACF;;AAE2B;;;;;;AAKrBJ;AACAC;AACAC;AACF;;;AAC8C;;AAEhD;;AAIA;AACE;;AAGA;AACE;;;AAGAI;AACF;;;;AAMA;;AAEA;;;AAUA;;AAEA;AACE;;AAEAnB;;AAEF;;;;AAMA;;AAEA;AAEAW;AACF;AACF;;AAGE;AACE;;AAEAX;;AAEF;;;;AAMA;;AAEA;;AAGF;AACF;;AAGE;AAA0B;AAAa;AAAc;AAAa;;;AAIhE;AACA;AACA;;AAEEA;;;;AAIN;AAEA;AACE;AACE;AACF;;;;;;AAOE;;AAGF;;;AAGA;AACF;AAEA;;;;AAIE;AACF;AAEA;;;AAGI;;;AAGI;AACA;AACAoB;AACApB;AACAmB;;AAEJ;;;;AAIA;AACF;;AAEA;AACF;;AAGA;AACE;;;AASE;AAEA;;AAEE;AACA;AACAE;AACF;AAEAC;;;AAGED;AACF;AACF;;AAGEd;AACE;;AAEA;AAEAe;AACEC;;;AAGAF;AACF;AACF;AACF;;AAMF;;AAGEZ;AACAe;AACAC;AACAjB;AACAD;;AAGAmB;AACAC;AACAC;AACAC;AACAC;;AAGAC;;AAGAC;AACF;AACF;;"}
1
+ {"version":3,"file":"useMigrationDashboard.js","sources":["../../../src/components/dashboard/useMigrationDashboard.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\n\nimport type { JobActionType, MigrationSummary, WordPressSiteConfig } from './types'\n\nconst persistScanResult = (config: WordPressSiteConfig) => {\n localStorage.setItem('wp-scan-result', JSON.stringify(config))\n}\n\nconst loadScanResult = (): WordPressSiteConfig | null => {\n try {\n const saved = localStorage.getItem('wp-scan-result')\n return saved ? JSON.parse(saved) : null\n } catch {\n return null\n }\n}\n\nexport const useMigrationDashboard = (initialSummary: MigrationSummary) => {\n const [summary, setSummary] = useState<MigrationSummary>(initialSummary)\n const [loading, setLoading] = useState(false)\n const [logs, setLogs] = useState<string[]>([])\n\n const [siteConfig, setSiteConfig] = useState<WordPressSiteConfig>({\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n totalItems: 0,\n })\n\n const addLog = (message: string) => {\n const timestamp = new Date().toLocaleTimeString()\n const logEntry = `[${timestamp}] ${message}`\n setLogs((prev) => [...prev, logEntry])\n }\n\n const refreshDashboard = async () => {\n try {\n const response = await fetch('/api/wordpress/migration-summary?refresh=true')\n const newSummary = await response.json()\n setSummary(newSummary)\n } catch (error) {\n console.error('Failed to refresh dashboard:', error)\n }\n }\n\n // Load initial data on mount\n useEffect(() => {\n void refreshDashboard()\n\n const saved = loadScanResult()\n if (saved) {\n setSiteConfig(saved)\n }\n }, [])\n\n // Smart periodic refresh - only when jobs are active\n useEffect(() => {\n let refreshInterval: NodeJS.Timeout | null = null\n\n if (summary.activeJobs > 0) {\n refreshInterval = setInterval(() => {\n void refreshDashboard()\n }, 15000)\n }\n\n return () => {\n if (refreshInterval) {\n clearInterval(refreshInterval)\n }\n }\n }, [summary.activeJobs])\n\n // Only refresh on significant page visibility changes\n useEffect(() => {\n let lastVisibilityChange = Date.now()\n\n const handleVisibilityChange = () => {\n if (!document.hidden && Date.now() - lastVisibilityChange > 30000) {\n void refreshDashboard()\n }\n\n if (document.hidden) {\n lastVisibilityChange = Date.now()\n }\n }\n\n document.addEventListener('visibilitychange', handleVisibilityChange)\n\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange)\n }\n }, [])\n\n // Listen for localStorage events to immediately refresh when jobs are changed\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === 'migration-event' && e.newValue) {\n try {\n const event = JSON.parse(e.newValue)\n if (event.type?.startsWith('migration-job-')) {\n void refreshDashboard()\n }\n } catch {\n // Ignore parsing errors\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => window.removeEventListener('storage', handleStorageChange)\n }, [])\n\n const getJobName = (jobId: string) => {\n const job = summary.recentJobs?.find((j) => j.id === jobId)\n return job?.jobName || `Job ${jobId}`\n }\n\n // --- Job action handlers ---\n\n const startMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n await refreshDashboard()\n setLogs([])\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" started`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'start', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error starting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to start job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error starting migration:', error)\n alert(\n `Failed to start migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const pauseMigrationJob = async (jobId: string) => {\n try {\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" paused`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=pause`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n addLog(`Error pausing migration \"${jobName}\"`)\n throw new Error('Failed to pause migration job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error pausing migration:', error)\n alert('Failed to pause migration job')\n }\n }\n\n const resumeMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" resumed`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n const startResponse = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'resume', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!startResponse.ok) {\n const errorData = await startResponse.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error resuming migration:', error)\n alert(\n `Failed to resume migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const retryMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n addLog(`Retrying migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'retry', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error retrying migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to retry job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error retrying migration:', error)\n alert(\n `Failed to retry migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const updateMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to update the \"${jobName}\" migration? This will update existing items with new field mappings without re-processing all content.`,\n )\n\n if (!confirmed) return\n\n addLog(`Updating migration \"${jobName}\" with new field mappings`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'update', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error updating migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to update job')\n }\n\n await response.json()\n addLog(`Successfully started update for \"${jobName}\"`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error updating migration:', error)\n alert(\n `Failed to update migration job: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const restartMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to restart the \"${jobName}\" migration? This will re-process ALL items, including those that were already successfully migrated.`,\n )\n\n if (!confirmed) return\n\n addLog(`Restarting migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'restart', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error restarting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to restart job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error restarting migration:', error)\n alert(\n `Failed to restart migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const rollbackMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to rollback the \"${jobName}\" migration? This will DELETE all items that were migrated by this job.`,\n )\n\n if (!confirmed) return\n\n addLog(`Rolling back migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'rollback', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error rolling back migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to rollback job')\n }\n\n const result = await response.json()\n addLog(`Rollback complete for \"${jobName}\": ${result.data?.deleted ?? 0} items deleted`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error rolling back migration:', error)\n alert(\n `Failed to rollback migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const handleJobAction = async (action: JobActionType, jobId: string) => {\n switch (action) {\n case 'start':\n return startMigrationJob(jobId)\n case 'pause':\n return pauseMigrationJob(jobId)\n case 'resume':\n return resumeMigrationJob(jobId)\n case 'retry':\n return retryMigrationJob(jobId)\n case 'update':\n return updateMigrationJob(jobId)\n case 'restart':\n return restartMigrationJob(jobId)\n case 'rollback':\n return rollbackMigrationJob(jobId)\n }\n }\n\n // --- Content discovery ---\n\n const scanWordPressContent = async () => {\n setSiteConfig((prev) => ({ ...prev, connectionStatus: 'scanning' }))\n\n try {\n const response = await fetch('/api/wordpress/discover-content', {\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (response.ok && result.success) {\n const contentTypes = result.contentTypes || []\n const totalItems = result.totalItems || 0\n\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'scanned',\n discoveredContent: contentTypes,\n totalItems,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n if (contentTypes.length === 0) {\n alert(\n 'WordPress scan completed, but no content was found. This could be due to:\\n\\n' +\n '- Empty WordPress site\\n' +\n '- Insufficient user permissions\\n' +\n '- WordPress REST API restrictions\\n\\n' +\n 'Please check your WordPress site and user permissions.',\n )\n }\n } else {\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'failed',\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n alert(`WordPress scan failed: ${result.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Content scan failed:', error)\n\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'failed',\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n alert('WordPress scan failed: Network error')\n }\n }\n\n // Combine client-side action logs with server-side migration logs\n const getAllLogs = (): string[] => {\n const combinedLogs: Array<{\n level?: string\n message: string\n source: 'client' | 'server'\n timestamp: Date\n }> = []\n\n logs.forEach((log) => {\n const timestampMatch = log.match(/^\\[(\\d{1,2}:\\d{2}:\\d{2}(?:\\s?[AP]M)?)\\]/)\n let timestamp = new Date()\n\n if (timestampMatch) {\n const timeStr = timestampMatch[1]\n const today = new Date()\n const timeWithDate = `${today.toDateString()} ${timeStr}`\n timestamp = new Date(timeWithDate)\n }\n\n combinedLogs.push({\n message: log,\n source: 'client',\n timestamp,\n })\n })\n\n if (summary.recentLogs) {\n summary.recentLogs.forEach((log) => {\n const timestamp = new Date(log.timestamp)\n const levelIcon = log.level === 'error' ? '❌' : log.level === 'warning' ? '⚠️' : 'ℹ️'\n const formattedMessage = `[${timestamp.toLocaleTimeString()}] ${levelIcon} [${log.jobName}] ${log.message}`\n\n combinedLogs.push({\n level: log.level,\n message: formattedMessage,\n source: 'server',\n timestamp,\n })\n })\n }\n\n return combinedLogs\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, 100)\n .map((log) => log.message)\n }\n\n return {\n loading,\n siteConfig,\n summary,\n\n // Job actions\n handleJobAction,\n\n // Content discovery\n scanWordPressContent,\n\n // Utilities\n getAllLogs,\n }\n}\n"],"names":["localStorage","discoveredContent","setLogs","prev","logEntry","refreshInterval","summary","lastVisibilityChange","addLog","jobId","totalItems","alert","timestamp","combinedLogs","level","loading","siteConfig","handleJobAction","scanWordPressContent","getAllLogs"],"mappings":";;;AAMA;AACEA;AACF;AAEA;;;AAGI;AACF;;AAEA;AACF;AAEO;AACL;AACA;AACA;AAEA;;AAEEC;;AAEF;AAEA;;AAEE;AACAC;AAAsBC;AAAMC;AAAS;AACvC;AAEA;;;;;AAKE;;AAEA;AACF;;;;AAME;AACA;;AAEA;AACF;;;AAIE;;AAGEC;;;AAGF;;AAGE;;AAEA;AACF;;AACEC;AAAmB;;;;AAMrB;;;AAGE;;AAGEC;AACF;AACF;;;;AAMA;AACF;;;AAIE;AACE;;AAEI;AACA;;AAEA;AACF;;AAEA;AACF;AACF;;AAGA;AACF;AAEA;;AAEE;AACF;;AAIA;;;;AAIIL;AAEA;AACAM;AAEA;;;AAC0CC;AAAM;;;AACA;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;AAEI;AACAD;;;AAIA;;AAGEA;AACA;AACF;;AAGF;;;AAGA;AACF;AAEA;;;AAII;AACAA;;;AAIA;;;;AAKE;AACF;AAEA;;;AAC2CC;AAAM;;;AACD;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AACAD;AAEA;;;AAC0CC;AAAM;;;AACA;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAD;AAEA;;;AAC2CC;AAAM;;;AACD;;AAEhD;;;;AAKE;AACF;AAEA;AACAD;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAA;AAEA;;;AAC4CC;AAAM;;;AACF;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAD;AAEA;;;AAC6CC;AAAM;;;AACH;;AAEhD;;;;AAKE;AACF;;AAGAD;;AAGF;;;;;AAOA;AACF;;;;AAKM;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACJ;AACF;;AAIA;;AAC6B;;;;;;;AAIuB;;AAEhD;;AAIA;AACE;;AAGA;;;AAGEE;AACF;;;;;AAaA;;AAEA;;AAEET;;AAEF;;;AAKAU;AACF;AACF;;AAGE;;AAEEV;;AAEF;;;;AAMF;AACF;;AAGA;AACE;;;AASE;AAEA;;AAEE;AACA;AACAW;AACF;AAEAC;;;AAGED;AACF;AACF;;AAGEN;AACE;;AAEA;AAEAO;AACEC;;;AAGAF;AACF;AACF;AACF;;AAMF;;AAGEG;AACAC;AACAV;;AAGAW;;AAGAC;;AAGAC;AACF;AACF;;"}
@@ -1,8 +1,8 @@
1
1
  import { analyzePayloadFields } from '../fields/analyzer.js';
2
2
  import { MIGRATION_COLLECTION } from '../types.js';
3
3
  import { WordPressClient } from '../wordpress/client.js';
4
- import { requireMigrationAccess, parseRequestBody, validateWordPressCredentials, requireAuth } from '../helpers/auth.js';
5
- import { createSuccessResponse, createErrorResponse } from '../helpers/responses.js';
4
+ import { requireMigrationAccess, requireAuth, parseRequestBody } from '../helpers/auth.js';
5
+ import { createErrorResponse, createSuccessResponse } from '../helpers/responses.js';
6
6
  import { getContentCache, isCacheValid, setContentCache } from '../helpers/cache.js';
7
7
  import { checkRateLimit } from '../helpers/rateLimiter.js';
8
8
 
@@ -15,9 +15,15 @@ const wordpressConnectionHandler = async (req, pluginOptions)=>{
15
15
  return createErrorResponse('Too many connection attempts. Try again in 1 minute.', 429);
16
16
  }
17
17
  try {
18
- const body = await parseRequestBody(req);
19
- const credentials = validateWordPressCredentials(body);
20
- const client = new WordPressClient(credentials);
18
+ const { wpSiteUrl, wpUsername, wpPassword } = pluginOptions;
19
+ if (!wpSiteUrl || !wpUsername || !wpPassword) {
20
+ return createErrorResponse('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.', 400);
21
+ }
22
+ const client = new WordPressClient({
23
+ wpSiteUrl,
24
+ wpUsername,
25
+ wpPassword
26
+ });
21
27
  const result = await client.testConnection();
22
28
  return createSuccessResponse(result, result.success ? 200 : 400);
23
29
  } catch (error) {
@@ -29,9 +35,15 @@ const discoverWordPressContent = async (req, pluginOptions)=>{
29
35
  const authError = await requireMigrationAccess(req, pluginOptions.access);
30
36
  if (authError) return authError;
31
37
  try {
32
- const body = await parseRequestBody(req);
33
- const credentials = validateWordPressCredentials(body);
34
- const client = new WordPressClient(credentials);
38
+ const { wpSiteUrl, wpUsername, wpPassword } = pluginOptions;
39
+ if (!wpSiteUrl || !wpUsername || !wpPassword) {
40
+ return createErrorResponse('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.', 400);
41
+ }
42
+ const client = new WordPressClient({
43
+ wpSiteUrl,
44
+ wpUsername,
45
+ wpPassword
46
+ });
35
47
  const result = await client.discoverContent();
36
48
  if (result.success) {
37
49
  return createSuccessResponse({
@@ -52,16 +64,15 @@ const fetchWordPressContentFields = async (req, pluginOptions)=>{
52
64
  try {
53
65
  const body = await parseRequestBody(req);
54
66
  const { contentType } = body;
55
- // Credentials from request body take priority, then fall back to plugin config (env vars)
56
- const wpSiteUrl = body.wpSiteUrl || pluginOptions.wpSiteUrl;
57
- const wpUsername = body.wpUsername || pluginOptions.wpUsername;
58
- const wpPassword = body.wpPassword || pluginOptions.wpPassword;
59
- if (!wpSiteUrl || !wpUsername || !wpPassword) {
60
- throw new Error('WordPress credentials are required. Configure them in the plugin config or the dashboard Site Configuration panel.');
61
- }
62
67
  if (!contentType) {
63
68
  throw new Error('contentType is required');
64
69
  }
70
+ const wpSiteUrl = pluginOptions.wpSiteUrl;
71
+ const wpUsername = pluginOptions.wpUsername;
72
+ const wpPassword = pluginOptions.wpPassword;
73
+ if (!wpSiteUrl || !wpUsername || !wpPassword) {
74
+ throw new Error('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.');
75
+ }
65
76
  const credentials = {
66
77
  wpPassword,
67
78
  wpSiteUrl,