payload-wordpress-migrator
Version:
A PayloadCMS plugin for WordPress migration - migrate and manage WordPress content directly in your Payload admin dashboard
436 lines (435 loc) • 19.7 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useField } from '@payloadcms/ui';
import React, { useEffect, useRef, useState } from 'react';
const SimpleFieldMapping = ({ path })=>{
const { setValue, value } = useField({
path
});
const { value: contentTypeValue } = useField({
path: 'contentType'
});
const { value: targetCollectionValue } = useField({
path: 'targetCollection'
});
// Use ref to prevent infinite loops - change to any type to handle different value types
const isUpdatingRef = useRef(false);
const lastValueRef = useRef('');
// Dynamic field options from API
const [sourceFields, setSourceFields] = useState([]);
const [destinationFields, setDestinationFields] = useState([]);
const [fieldMappings, setFieldMappings] = useState([
{
destinationField: '',
sourceField: ''
}
]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// Fetch WordPress content fields
const fetchWordPressFields = async (contentType)=>{
try {
setLoading(true);
setError('');
const savedConfig = localStorage.getItem('wp-site-config');
if (!savedConfig) {
throw new Error('WordPress site configuration not found');
}
const config = JSON.parse(savedConfig);
const requestBody = {
contentType,
wpPassword: config.wpPassword,
wpSiteUrl: config.wpSiteUrl,
wpUsername: config.wpUsername
};
const response = await fetch('/api/wordpress/content-fields', {
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
const result = await response.json();
if (response.ok && result.success) {
const wpFieldOptions = (result.fields || []).map((field)=>({
type: field.type,
label: `${field.label} (${field.type})${field.custom ? ' *' : ''}`,
path: field.path || field.name,
value: field.path || field.name
}));
setSourceFields(wpFieldOptions);
} else {
console.error('API returned error:', result);
throw new Error(result.error || 'Failed to fetch WordPress fields');
}
} catch (error) {
console.error('Error fetching WordPress fields:', error);
setError(error instanceof Error ? error.message : 'Failed to fetch WordPress fields');
setSourceFields([]);
}
};
// Fetch PayloadCMS collection fields
const fetchPayloadFields = async (collectionSlug)=>{
try {
const response = await fetch(`/api/collections/${collectionSlug}/fields`);
const result = await response.json();
if (response.ok && result.success) {
const payloadFieldOptions = (result.fields || []).map((field)=>({
type: field.type,
label: `${field.label} (${field.type})`,
path: field.path || field.name,
value: field.path || field.name
}));
setDestinationFields(payloadFieldOptions);
} else {
console.error('Payload API returned error:', result);
throw new Error(result.error || 'Failed to fetch Payload collection fields');
}
} catch (error) {
console.error('Error fetching Payload fields:', error);
setDestinationFields([]);
}
};
// Fetch fields when both content type and target collection are selected
useEffect(()=>{
const fetchFields = async ()=>{
if (contentTypeValue && targetCollectionValue) {
setLoading(true);
try {
await Promise.all([
fetchWordPressFields(contentTypeValue),
fetchPayloadFields(targetCollectionValue)
]);
} catch (error) {
console.error('Error fetching fields:', error);
} finally{
setLoading(false);
}
} else {
setSourceFields([]);
setDestinationFields([]);
setFieldMappings([
{
destinationField: '',
sourceField: ''
}
]);
setError('');
}
};
void fetchFields();
}, [
contentTypeValue,
targetCollectionValue
]);
// Parse existing field mappings from JSON value - only when value actually changes
useEffect(()=>{
if (isUpdatingRef.current) {
return;
} // Prevent processing our own updates
// Convert value to string for comparison
const currentValue = typeof value === 'string' ? value : JSON.stringify(value || '');
if (value && currentValue !== lastValueRef.current) {
try {
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
if (parsed && Array.isArray(parsed.fieldMappings)) {
setFieldMappings(parsed.fieldMappings);
}
lastValueRef.current = currentValue;
} catch (error) {
console.warn('Failed to parse field mappings:', error);
}
}
}, [
value
]);
// Function to update parent value without causing loops
const updateParentValue = (mappings)=>{
const newValue = JSON.stringify({
contentType: contentTypeValue,
fieldMappings: mappings,
targetCollection: targetCollectionValue
});
// Prevent infinite loops
if (newValue !== lastValueRef.current && !isUpdatingRef.current) {
isUpdatingRef.current = true;
setValue(newValue);
lastValueRef.current = newValue;
// Reset the flag after a short delay
setTimeout(()=>{
isUpdatingRef.current = false;
}, 100);
}
};
// Add new mapping row
const handleAddMapping = ()=>{
const newMappings = [
...fieldMappings,
{
destinationField: '',
sourceField: ''
}
];
setFieldMappings(newMappings);
updateParentValue(newMappings);
};
// Remove mapping row
const handleRemoveMapping = (idx)=>{
if (fieldMappings.length <= 1) {
return;
} // Keep at least one mapping
const newMappings = fieldMappings.filter((_, i)=>i !== idx);
setFieldMappings(newMappings);
updateParentValue(newMappings);
};
// Update mapping
const handleChange = (idx, field, newValue)=>{
const newMappings = fieldMappings.map((mapping, i)=>i === idx ? {
...mapping,
[field]: newValue
} : mapping);
setFieldMappings(newMappings);
updateParentValue(newMappings);
};
// Filter dropdown options to exclude already-mapped fields (except for current row)
const getFilteredOptions = (options, field, idx)=>{
const used = fieldMappings.map((m, i)=>i !== idx ? m[field] : null).filter(Boolean);
return options.filter((opt)=>!used.includes(opt.value));
};
if (!contentTypeValue || !targetCollectionValue) {
return /*#__PURE__*/ _jsxs("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: '1rem'
},
children: [
/*#__PURE__*/ _jsx("label", {
style: {
color: 'var(--theme-elevation-700)',
fontSize: '14px',
fontWeight: '600'
},
children: "Field Mapping Configuration"
}),
/*#__PURE__*/ _jsx("div", {
style: {
backgroundColor: 'var(--theme-elevation-50)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
color: 'var(--theme-elevation-600)',
padding: '1rem',
textAlign: 'center'
},
children: 'Please select both "Content Type to Migrate" and "Target Payload Collection" to configure field mappings.'
})
]
});
}
return /*#__PURE__*/ _jsxs("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: '1rem'
},
children: [
/*#__PURE__*/ _jsxs("div", {
style: {
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between'
},
children: [
/*#__PURE__*/ _jsx("label", {
style: {
color: 'var(--theme-elevation-700)',
fontSize: '14px',
fontWeight: '600'
},
children: "Field Mapping Configuration"
}),
/*#__PURE__*/ _jsx("button", {
disabled: loading,
onClick: handleAddMapping,
style: {
backgroundColor: loading ? 'var(--theme-elevation-200)' : 'var(--theme-success-600)',
border: 'none',
borderRadius: '4px',
color: loading ? 'var(--theme-elevation-500)' : 'white',
cursor: loading ? 'not-allowed' : 'pointer',
fontSize: '12px',
fontWeight: '500',
padding: '0.5rem 1rem'
},
type: "button",
children: "Add Mapping"
})
]
}),
loading && /*#__PURE__*/ _jsx("div", {
style: {
backgroundColor: 'var(--theme-elevation-50)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
color: 'var(--theme-elevation-600)',
padding: '1rem',
textAlign: 'center'
},
children: "Loading field schemas from WordPress and PayloadCMS..."
}),
error && /*#__PURE__*/ _jsxs("div", {
style: {
backgroundColor: 'var(--theme-error-50)',
border: '1px solid var(--theme-error-200)',
borderRadius: '4px',
color: 'var(--theme-error-700)',
padding: '1rem'
},
children: [
/*#__PURE__*/ _jsx("strong", {
children: "Error:"
}),
" ",
error
]
}),
!loading && !error && sourceFields.length === 0 && destinationFields.length === 0 && /*#__PURE__*/ _jsx("div", {
style: {
backgroundColor: 'var(--theme-elevation-50)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
color: 'var(--theme-elevation-600)',
padding: '1rem',
textAlign: 'center'
},
children: "No fields found. Please check your WordPress connection and selected content type."
}),
!loading && !error && (sourceFields.length > 0 || destinationFields.length > 0) && /*#__PURE__*/ _jsxs(_Fragment, {
children: [
fieldMappings.length === 0 && /*#__PURE__*/ _jsx("div", {
style: {
backgroundColor: 'var(--theme-elevation-50)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
color: 'var(--theme-elevation-600)',
padding: '1rem',
textAlign: 'center'
},
children: 'No field mappings configured. Click "Add Mapping" to start.'
}),
fieldMappings.map((mapping, idx)=>/*#__PURE__*/ _jsxs("div", {
style: {
alignItems: 'center',
backgroundColor: 'var(--theme-elevation-0)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
display: 'grid',
gap: '0.75rem',
gridTemplateColumns: '1fr auto 1fr auto',
padding: '1rem'
},
children: [
/*#__PURE__*/ _jsxs("select", {
onChange: (e)=>handleChange(idx, 'sourceField', e.target.value),
style: {
backgroundColor: 'var(--theme-elevation-0)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
fontSize: '14px',
padding: '0.5rem'
},
value: mapping.sourceField,
children: [
/*#__PURE__*/ _jsx("option", {
value: "",
children: "Select WordPress field..."
}),
getFilteredOptions(sourceFields, 'sourceField', idx).map((opt, optIdx)=>/*#__PURE__*/ _jsx("option", {
value: opt.value,
children: opt.label
}, `source-${idx}-${opt.value}-${optIdx}`))
]
}),
/*#__PURE__*/ _jsx("span", {
style: {
color: 'var(--theme-elevation-400)',
fontSize: '18px',
fontWeight: 'bold'
},
children: "→"
}),
/*#__PURE__*/ _jsxs("select", {
onChange: (e)=>handleChange(idx, 'destinationField', e.target.value),
style: {
backgroundColor: 'var(--theme-elevation-0)',
border: '1px solid var(--theme-elevation-200)',
borderRadius: '4px',
fontSize: '14px',
padding: '0.5rem'
},
value: mapping.destinationField,
children: [
/*#__PURE__*/ _jsx("option", {
value: "",
children: "Select Payload field..."
}),
getFilteredOptions(destinationFields, 'destinationField', idx).map((opt, optIdx)=>/*#__PURE__*/ _jsx("option", {
value: opt.value,
children: opt.label
}, `dest-${idx}-${opt.value}-${optIdx}`))
]
}),
/*#__PURE__*/ _jsx("button", {
disabled: fieldMappings.length <= 1,
onClick: ()=>handleRemoveMapping(idx),
style: {
backgroundColor: fieldMappings.length <= 1 ? 'var(--theme-elevation-200)' : 'var(--theme-error-600)',
border: 'none',
borderRadius: '4px',
color: fieldMappings.length <= 1 ? 'var(--theme-elevation-500)' : 'white',
cursor: fieldMappings.length <= 1 ? 'not-allowed' : 'pointer',
fontSize: '12px',
fontWeight: '500',
minWidth: '60px',
padding: '0.5rem'
},
type: "button",
children: "Remove"
})
]
}, idx))
]
}),
/*#__PURE__*/ _jsxs("div", {
style: {
color: 'var(--theme-elevation-500)',
fontSize: '12px',
fontStyle: 'italic',
marginTop: '0.5rem'
},
children: [
"Map WordPress fields to their corresponding Payload fields. Each field can only be mapped once. Fields marked with * are custom fields (ACF/Meta).",
sourceFields.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsx("br", {}),
/*#__PURE__*/ _jsx("strong", {
children: "Available WordPress fields:"
}),
" ",
sourceFields.length,
" |",
' ',
/*#__PURE__*/ _jsx("strong", {
children: "Available Payload fields:"
}),
" ",
destinationFields.length
]
})
]
})
]
});
};
export default SimpleFieldMapping;
//# sourceMappingURL=SimpleFieldMapping.js.map