Files
profilarr/frontend/src/hooks/useFormatModal.js

290 lines
9.6 KiB
JavaScript

import {useState, useCallback} from 'react';
import {CustomFormats} from '@api/data';
import Alert from '@ui/Alert';
import {useFormatTesting} from './useFormatTesting';
export const useFormatModal = (initialFormat, onSuccess) => {
// Form state
const [name, setName] = useState('');
const [originalName, setOriginalName] = useState('');
const [description, setDescription] = useState('');
const [tags, setTags] = useState([]);
const [conditions, setConditions] = useState([]);
const [tests, setTests] = useState([]);
const [isCloning, setIsCloning] = useState(false);
// Enhanced UI state with field-specific errors
const [formErrors, setFormErrors] = useState({
name: '',
conditions: '',
tests: '',
general: ''
});
const [activeTab, setActiveTab] = useState('general');
const [isDeleting, setIsDeleting] = useState(false);
// Initialize testing functionality
const {isRunningTests, runTests} = useFormatTesting();
const validateForm = () => {
const errors = {
name: '',
conditions: '',
tests: '',
general: ''
};
// Name validation
if (!name.trim()) {
errors.name = 'Name is required';
} else if (name.length > 64) {
errors.name = 'Name must be less than 64 characters';
}
// Conditions validation
if (!conditions.length) {
errors.conditions = 'At least one condition is required';
} else {
const invalidConditions = conditions.filter(condition => {
return !condition.field || !condition.operator;
});
if (invalidConditions.length > 0) {
errors.conditions =
'All conditions must have a field and operator';
}
}
// Test validation
if (tests.length > 0) {
const invalidTests = tests.filter(test => !test.input);
if (invalidTests.length > 0) {
errors.tests = 'All tests must have input values';
}
}
setFormErrors(errors);
return !Object.values(errors).some(error => error);
};
const initializeForm = useCallback((format, cloning) => {
setIsCloning(cloning || false);
if (format) {
const initialName = cloning ? `${format.name}` : format.name;
setName(initialName);
setOriginalName(cloning ? '' : format.name);
setDescription(format.description || '');
setTags(format.tags || []);
setConditions(format.conditions || []);
setTests(format.tests || []);
} else {
setName('');
setOriginalName('');
setDescription('');
setTags([]);
setConditions([]);
setTests([]);
}
setFormErrors({name: '', conditions: '', tests: '', general: ''});
setIsDeleting(false);
}, []);
const handleSave = async () => {
try {
// Validate form directly without state updates
const errors = {
name: '',
conditions: '',
tests: '',
general: ''
};
// Name validation
if (!name.trim()) {
errors.name = 'Name is required';
Alert.error('Name is required');
setFormErrors(errors);
return;
}
// Conditions validation
if (!conditions.length) {
errors.conditions = 'At least one condition is required';
Alert.error('At least one condition is required');
setFormErrors(errors);
return;
}
const invalidConditions = conditions.filter(condition => {
// Each condition must have a type
if (!condition.type) return true;
// Validation based on condition type
switch (condition.type) {
case 'release_title':
case 'release_group':
case 'edition':
return !condition.pattern;
case 'language':
return !condition.language;
case 'indexer_flag':
return !condition.flag;
case 'source':
return !condition.source;
case 'resolution':
return !condition.resolution;
case 'quality_modifier':
return !condition.qualityModifier;
case 'size':
return !condition.minSize && !condition.maxSize;
case 'release_type':
return !condition.releaseType;
case 'year':
return !condition.minYear && !condition.maxYear;
default:
return true;
}
});
if (invalidConditions.length > 0) {
errors.conditions =
'All conditions must have required fields filled out';
Alert.error(
'All conditions must have required fields filled out'
);
setFormErrors(errors);
return;
}
// If we get here, form is valid
const data = {
name,
description,
tags,
conditions,
tests
};
if (initialFormat && !isCloning) {
const hasNameChanged = name !== originalName;
await CustomFormats.update(
initialFormat.file_name.replace('.yml', ''),
data,
hasNameChanged ? name : undefined
);
Alert.success('Format updated successfully');
} else {
await CustomFormats.create(data);
Alert.success('Format created successfully');
}
onSuccess();
} catch (error) {
console.error('Error saving format:', error);
// Handle different types of errors
if (error.message.includes('reserved word')) {
setFormErrors(prev => ({...prev, name: error.message}));
Alert.error(error.message);
} else if (error.message.includes('invalid characters')) {
setFormErrors(prev => ({...prev, name: error.message}));
Alert.error(error.message);
} else if (error.message.includes('already exists')) {
setFormErrors(prev => ({...prev, name: error.message}));
Alert.error(error.message);
} else if (error.message.includes('condition')) {
setFormErrors(prev => ({...prev, conditions: error.message}));
Alert.error(error.message);
} else if (error.message.includes('test')) {
setFormErrors(prev => ({...prev, tests: error.message}));
Alert.error(error.message);
} else {
setFormErrors(prev => ({
...prev,
general:
error.message ||
'Failed to save format. Please try again.'
}));
Alert.error(
error.message || 'Failed to save format. Please try again.'
);
}
}
};
const handleDelete = async () => {
if (!initialFormat) return;
if (isDeleting) {
try {
await CustomFormats.delete(
initialFormat.file_name.replace('.yml', '')
);
Alert.success('Format deleted successfully');
onSuccess();
} catch (error) {
console.error('Error deleting format:', error);
Alert.error(
error.message ||
'Failed to delete format. Please try again.'
);
setIsDeleting(false); // Reset delete state on error
}
} else {
setIsDeleting(true);
}
};
const handleRunTests = useCallback(
async (conditions, tests) => {
try {
const updatedTests = await runTests(conditions, tests);
if (updatedTests) {
setTests(updatedTests);
}
} catch (error) {
console.error('Error running tests:', error);
setFormErrors(prev => ({
...prev,
tests:
error.message ||
'Failed to run tests. Please try again.'
}));
Alert.error(
error.message || 'Failed to run tests. Please try again.'
);
}
},
[runTests]
);
return {
// Form state
name,
description,
tags,
conditions,
tests,
// UI state
formErrors,
activeTab,
isDeleting,
isRunningTests,
isCloning,
// Actions
setName,
setDescription,
setTags,
setConditions,
setTests,
setActiveTab,
setIsDeleting,
// Main handlers
initializeForm,
handleSave,
handleDelete,
handleRunTests,
// Validation
validateForm
};
};