diff --git a/frontend/src/components/forms/LiveGroupFilter.jsx b/frontend/src/components/forms/LiveGroupFilter.jsx
index 68f4db8c..4a473afe 100644
--- a/frontend/src/components/forms/LiveGroupFilter.jsx
+++ b/frontend/src/components/forms/LiveGroupFilter.jsx
@@ -16,20 +16,11 @@ import {
Box,
MultiSelect,
Tooltip,
- Popover,
- ScrollArea,
- Center,
} from '@mantine/core';
import { Info } from 'lucide-react';
import useChannelsStore from '../../store/channels';
import useStreamProfilesStore from '../../store/streamProfiles';
import { CircleCheck, CircleX } from 'lucide-react';
-import { useChannelLogoSelection } from '../../hooks/useSmartLogos';
-import { FixedSizeList as List } from 'react-window';
-import LazyLogo from '../LazyLogo';
-import LogoForm from './Logo';
-import logo from '../../images/logo.png';
-import API from '../../api';
// Custom item component for MultiSelect with tooltip
const OptionWithTooltip = forwardRef(
@@ -42,33 +33,12 @@ const OptionWithTooltip = forwardRef(
)
);
-const LiveGroupFilter = ({
- playlist,
- groupStates,
- setGroupStates,
- autoEnableNewGroupsLive,
- setAutoEnableNewGroupsLive,
-}) => {
+const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => {
const channelGroups = useChannelsStore((s) => s.channelGroups);
const profiles = useChannelsStore((s) => s.profiles);
const streamProfiles = useStreamProfilesStore((s) => s.profiles);
const fetchStreamProfiles = useStreamProfilesStore((s) => s.fetchProfiles);
const [groupFilter, setGroupFilter] = useState('');
- const [epgSources, setEpgSources] = useState([]);
-
- // Logo selection functionality
- const {
- logos: channelLogos,
- ensureLogosLoaded,
- isLoading: logosLoading,
- } = useChannelLogoSelection();
- const [logoModalOpen, setLogoModalOpen] = useState(false);
- const [currentEditingGroupId, setCurrentEditingGroupId] = useState(null);
-
- // Ensure logos are loaded when component mounts
- useEffect(() => {
- ensureLogosLoaded();
- }, [ensureLogosLoaded]);
// Fetch stream profiles when component mounts
useEffect(() => {
@@ -77,49 +47,34 @@ const LiveGroupFilter = ({
}
}, [streamProfiles.length, fetchStreamProfiles]);
- // Fetch EPG sources when component mounts
- useEffect(() => {
- const fetchEPGSources = async () => {
- try {
- const sources = await API.getEPGs();
- setEpgSources(sources || []);
- } catch (error) {
- console.error('Failed to fetch EPG sources:', error);
- }
- };
- fetchEPGSources();
- }, []);
-
useEffect(() => {
if (Object.keys(channelGroups).length === 0) {
return;
}
setGroupStates(
- playlist.channel_groups
- .filter((group) => channelGroups[group.channel_group]) // Filter out groups that don't exist
- .map((group) => {
- // Parse custom_properties if present
- let customProps = {};
- if (group.custom_properties) {
- try {
- customProps =
- typeof group.custom_properties === 'string'
- ? JSON.parse(group.custom_properties)
- : group.custom_properties;
- } catch {
- customProps = {};
- }
+ playlist.channel_groups.map((group) => {
+ // Parse custom_properties if present
+ let customProps = {};
+ if (group.custom_properties) {
+ try {
+ customProps =
+ typeof group.custom_properties === 'string'
+ ? JSON.parse(group.custom_properties)
+ : group.custom_properties;
+ } catch (e) {
+ customProps = {};
}
- return {
- ...group,
- name: channelGroups[group.channel_group].name,
- auto_channel_sync: group.auto_channel_sync || false,
- auto_sync_channel_start: group.auto_sync_channel_start || 1.0,
- custom_properties: customProps,
- original_enabled: group.enabled,
- };
- })
+ }
+ return {
+ ...group,
+ name: channelGroups[group.channel_group].name,
+ auto_channel_sync: group.auto_channel_sync || false,
+ auto_sync_channel_start: group.auto_sync_channel_start || 1.0,
+ custom_properties: customProps,
+ original_enabled: group.enabled,
+ };
+ })
);
}, [playlist, channelGroups]);
@@ -154,27 +109,21 @@ const LiveGroupFilter = ({
);
};
- // Handle logo selection from LogoForm
- const handleLogoSuccess = ({ logo }) => {
- if (logo && logo.id && currentEditingGroupId !== null) {
- setGroupStates(
- groupStates.map((state) => {
- if (state.channel_group === currentEditingGroupId) {
- return {
- ...state,
- custom_properties: {
- ...state.custom_properties,
- custom_logo_id: logo.id,
- },
- };
- }
- return state;
- })
- );
- ensureLogosLoaded(); // Refresh logos
- }
- setLogoModalOpen(false);
- setCurrentEditingGroupId(null);
+ // Toggle force_dummy_epg in custom_properties for a group
+ const toggleForceDummyEPG = (id) => {
+ setGroupStates(
+ groupStates.map((state) => {
+ if (state.channel_group == id) {
+ const customProps = { ...(state.custom_properties || {}) };
+ customProps.force_dummy_epg = !customProps.force_dummy_epg;
+ return {
+ ...state,
+ custom_properties: customProps,
+ };
+ }
+ return state;
+ })
+ );
};
const selectAll = () => {
@@ -210,16 +159,6 @@ const LiveGroupFilter = ({
-
- setAutoEnableNewGroupsLive(event.currentTarget.checked)
- }
- size="sm"
- description="When disabled, new groups from the M3U source will be created but disabled by default. You can enable them manually later."
- />
-
{/* Group Enable/Disable Button */}
- toggleGroupEnabled(group.channel_group)}
+ radius="md"
+ size="xs"
+ leftSection={
+ group.enabled ? (
+
+ ) : (
+
+ )
}
- disabled={!group.enabled || !group.is_stale}
- multiline
- w={220}
+ fullWidth
>
-
-
+
+ {group.name}
+
+
{/* Auto Sync Controls */}
@@ -332,10 +254,10 @@ const LiveGroupFilter = ({
placeholder="Select options..."
data={[
{
- value: 'force_epg',
- label: 'Force EPG Source',
+ value: 'force_dummy_epg',
+ label: 'Force Dummy EPG',
description:
- 'Force a specific EPG source for all auto-synced channels, or disable EPG assignment entirely',
+ 'Assign a dummy EPG to all channels in this group if no EPG is matched',
},
{
value: 'group_override',
@@ -373,23 +295,12 @@ const LiveGroupFilter = ({
description:
'Assign a specific stream profile to all channels in this group during auto sync',
},
- {
- value: 'custom_logo',
- label: 'Custom Logo',
- description:
- 'Assign a custom logo to all auto-synced channels in this group',
- },
]}
itemComponent={OptionWithTooltip}
value={(() => {
const selectedValues = [];
- if (
- group.custom_properties?.custom_epg_id !==
- undefined ||
- group.custom_properties?.force_dummy_epg ||
- group.custom_properties?.force_epg_selected
- ) {
- selectedValues.push('force_epg');
+ if (group.custom_properties?.force_dummy_epg) {
+ selectedValues.push('force_dummy_epg');
}
if (
group.custom_properties?.group_override !==
@@ -429,12 +340,6 @@ const LiveGroupFilter = ({
) {
selectedValues.push('stream_profile_assignment');
}
- if (
- group.custom_properties?.custom_logo_id !==
- undefined
- ) {
- selectedValues.push('custom_logo');
- }
return selectedValues;
})()}
onChange={(values) => {
@@ -448,22 +353,13 @@ const LiveGroupFilter = ({
...(state.custom_properties || {}),
};
- // Handle force_epg
- if (selectedOptions.includes('force_epg')) {
- // Set default to force_dummy_epg if no EPG settings exist yet
- if (
- newCustomProps.custom_epg_id ===
- undefined &&
- !newCustomProps.force_dummy_epg
- ) {
- // Default to "No EPG (Disabled)"
- newCustomProps.force_dummy_epg = true;
- }
+ // Handle force_dummy_epg
+ if (
+ selectedOptions.includes('force_dummy_epg')
+ ) {
+ newCustomProps.force_dummy_epg = true;
} else {
- // Remove all EPG settings when deselected
- delete newCustomProps.custom_epg_id;
delete newCustomProps.force_dummy_epg;
- delete newCustomProps.force_epg_selected;
}
// Handle group_override
@@ -563,17 +459,6 @@ const LiveGroupFilter = ({
delete newCustomProps.stream_profile_id;
}
- // Handle custom_logo
- if (selectedOptions.includes('custom_logo')) {
- if (
- newCustomProps.custom_logo_id === undefined
- ) {
- newCustomProps.custom_logo_id = null;
- }
- } else {
- delete newCustomProps.custom_logo_id;
- }
-
return {
...state,
custom_properties: newCustomProps,
@@ -900,364 +785,6 @@ const LiveGroupFilter = ({
/>
)}
-
- {/* Show logo selector only if custom_logo is selected */}
- {group.custom_properties?.custom_logo_id !==
- undefined && (
-
-
- {
- setGroupStates(
- groupStates.map((state) => {
- if (
- state.channel_group ===
- group.channel_group
- ) {
- return {
- ...state,
- logoPopoverOpened: opened,
- };
- }
- return state;
- })
- );
- if (opened) {
- ensureLogosLoaded();
- }
- }}
- withArrow
- >
-
- {
- setGroupStates(
- groupStates.map((state) => {
- if (
- state.channel_group ===
- group.channel_group
- ) {
- return {
- ...state,
- logoPopoverOpened: true,
- };
- }
- return {
- ...state,
- logoPopoverOpened: false,
- };
- })
- );
- }}
- size="xs"
- />
-
-
- e.stopPropagation()}
- >
-
- {
- const val = e.currentTarget.value;
- setGroupStates(
- groupStates.map((state) =>
- state.channel_group ===
- group.channel_group
- ? {
- ...state,
- logoFilter: val,
- }
- : state
- )
- );
- }}
- />
- {logosLoading && (
-
- Loading...
-
- )}
-
-
-
- {(() => {
- const logoOptions = [
- { id: '0', name: 'Default' },
- ...Object.values(channelLogos),
- ];
- const filteredLogos = logoOptions.filter(
- (logo) =>
- logo.name
- .toLowerCase()
- .includes(
- (
- group.logoFilter || ''
- ).toLowerCase()
- )
- );
-
- if (filteredLogos.length === 0) {
- return (
-
-
- {group.logoFilter
- ? 'No logos match your filter'
- : 'No logos available'}
-
-
- );
- }
-
- return (
-
- {({ index, style }) => {
- const logoItem = filteredLogos[index];
- return (
- {
- setGroupStates(
- groupStates.map((state) => {
- if (
- state.channel_group ===
- group.channel_group
- ) {
- return {
- ...state,
- custom_properties: {
- ...state.custom_properties,
- custom_logo_id:
- logoItem.id,
- },
- logoPopoverOpened: false,
- };
- }
- return state;
- })
- );
- }}
- onMouseEnter={(e) => {
- e.currentTarget.style.backgroundColor =
- 'rgb(68, 68, 68)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.backgroundColor =
- 'transparent';
- }}
- >
-
-
{
- if (e.target.src !== logo) {
- e.target.src = logo;
- }
- }}
- />
-
- {logoItem.name || 'Default'}
-
-
-
- );
- }}
-
- );
- })()}
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {/* Show EPG selector when force_epg is selected */}
- {(group.custom_properties?.custom_epg_id !== undefined ||
- group.custom_properties?.force_dummy_epg ||
- group.custom_properties?.force_epg_selected) && (
-
-
- )}
>
)}
@@ -1265,16 +792,6 @@ const LiveGroupFilter = ({
))}
-
- {/* Logo Upload Modal */}
- {
- setLogoModalOpen(false);
- setCurrentEditingGroupId(null);
- }}
- onSuccess={handleLogoSuccess}
- />
);
};
diff --git a/frontend/src/components/forms/LoginForm.jsx b/frontend/src/components/forms/LoginForm.jsx
index 353cd50e..916a2c30 100644
--- a/frontend/src/components/forms/LoginForm.jsx
+++ b/frontend/src/components/forms/LoginForm.jsx
@@ -1,24 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useAuthStore from '../../store/auth';
-import API from '../../api';
-import {
- Paper,
- Title,
- TextInput,
- Button,
- Center,
- Stack,
- Text,
- Image,
- Group,
- Divider,
- Modal,
- Anchor,
- Code,
- Checkbox,
-} from '@mantine/core';
-import logo from '../../assets/logo.png';
+import { Paper, Title, TextInput, Button, Center, Stack } from '@mantine/core';
const LoginForm = () => {
const login = useAuthStore((s) => s.login);
@@ -28,69 +11,12 @@ const LoginForm = () => {
const navigate = useNavigate(); // Hook to navigate to other routes
const [formData, setFormData] = useState({ username: '', password: '' });
- const [rememberMe, setRememberMe] = useState(false);
- const [savePassword, setSavePassword] = useState(false);
- const [forgotPasswordOpened, setForgotPasswordOpened] = useState(false);
- const [version, setVersion] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- // Simple base64 encoding/decoding for localStorage
- // Note: This is obfuscation, not encryption. Use browser's password manager for real security.
- const encodePassword = (password) => {
- try {
- return btoa(password);
- } catch (error) {
- console.error('Encoding error:', error);
- return null;
- }
- };
-
- const decodePassword = (encoded) => {
- try {
- return atob(encoded);
- } catch (error) {
- console.error('Decoding error:', error);
- return '';
- }
- };
-
- useEffect(() => {
- // Fetch version info
- API.getVersion().then((data) => {
- setVersion(data?.version);
- });
- }, []);
-
- useEffect(() => {
- // Load saved username if it exists
- const savedUsername = localStorage.getItem(
- 'dispatcharr_remembered_username'
- );
- const savedPassword = localStorage.getItem('dispatcharr_saved_password');
-
- if (savedUsername) {
- setFormData((prev) => ({ ...prev, username: savedUsername }));
- setRememberMe(true);
-
- if (savedPassword) {
- try {
- const decrypted = decodePassword(savedPassword);
- if (decrypted) {
- setFormData((prev) => ({ ...prev, password: decrypted }));
- setSavePassword(true);
- }
- } catch {
- // If decoding fails, just skip
- }
- }
- }
- }, []);
-
- useEffect(() => {
- if (isAuthenticated) {
- navigate('/channels');
- }
- }, [isAuthenticated, navigate]);
+ // useEffect(() => {
+ // if (isAuthenticated) {
+ // navigate('/channels');
+ // }
+ // }, [isAuthenticated, navigate]);
const handleInputChange = (e) => {
setFormData({
@@ -101,38 +27,13 @@ const LoginForm = () => {
const handleSubmit = async (e) => {
e.preventDefault();
- setIsLoading(true);
+ await login(formData);
try {
- await login(formData);
-
- // Save username if remember me is checked
- if (rememberMe) {
- localStorage.setItem(
- 'dispatcharr_remembered_username',
- formData.username
- );
-
- // Save password if save password is checked
- if (savePassword) {
- const encoded = encodePassword(formData.password);
- if (encoded) {
- localStorage.setItem('dispatcharr_saved_password', encoded);
- }
- } else {
- localStorage.removeItem('dispatcharr_saved_password');
- }
- } else {
- localStorage.removeItem('dispatcharr_remembered_username');
- localStorage.removeItem('dispatcharr_saved_password');
- }
-
await initData();
- // Navigation will happen automatically via the useEffect or route protection
+ navigate('/channels');
} catch (e) {
console.log(`Failed to login: ${e}`);
- await logout();
- setIsLoading(false);
}
};
@@ -144,29 +45,11 @@ const LoginForm = () => {
>
-
-
-
- Dispatcharr
-
-
- Welcome back! Please log in to continue.
-
-
-
+
+ Login
+
-
- {version && (
-
- v{version}
-
- )}
-
- setForgotPasswordOpened(false)}
- title="Reset Your Password"
- centered
- >
-
-
- To reset your password, your administrator needs to run a Django
- management command:
-
-
-
- If running with Docker:
-
-
- docker exec <container_name> python manage.py changepassword
- <username>
-
-
-
-
- If running locally:
-
- python manage.py changepassword <username>
-
-
- The command will prompt for a new password. Replace
- <container_name> with your Docker container name
- and <username> with the account username.
-
-
- Please contact your system administrator to perform a password
- reset.
-
-
-
);
};
diff --git a/frontend/src/components/forms/Logo.jsx b/frontend/src/components/forms/Logo.jsx
index c6e63ba6..cb7f385e 100644
--- a/frontend/src/components/forms/Logo.jsx
+++ b/frontend/src/components/forms/Logo.jsx
@@ -1,6 +1,5 @@
-import React, { useState, useEffect, useMemo } from 'react';
-import { useForm } from 'react-hook-form';
-import { yupResolver } from '@hookform/resolvers/yup';
+import React, { useState, useEffect } from 'react';
+import { useFormik } from 'formik';
import * as Yup from 'yup';
import {
Modal,
@@ -19,148 +18,143 @@ import { Upload, FileImage, X } from 'lucide-react';
import { notifications } from '@mantine/notifications';
import API from '../../api';
-const schema = Yup.object({
- name: Yup.string().required('Name is required'),
- url: Yup.string()
- .required('URL is required')
- .test(
- 'valid-url-or-path',
- 'Must be a valid URL or local file path',
- (value) => {
- if (!value) return false;
- // Allow local file paths starting with /data/logos/
- if (value.startsWith('/data/logos/')) return true;
- // Allow valid URLs
- try {
- new URL(value);
- return true;
- } catch {
- return false;
- }
- }
- ),
-});
-
const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => {
const [logoPreview, setLogoPreview] = useState(null);
const [uploading, setUploading] = useState(false);
const [selectedFile, setSelectedFile] = useState(null); // Store selected file
- const defaultValues = useMemo(
- () => ({
- name: logo?.name || '',
- url: logo?.url || '',
+ const formik = useFormik({
+ initialValues: {
+ name: '',
+ url: '',
+ },
+ validationSchema: Yup.object({
+ name: Yup.string().required('Name is required'),
+ url: Yup.string()
+ .required('URL is required')
+ .test(
+ 'valid-url-or-path',
+ 'Must be a valid URL or local file path',
+ (value) => {
+ if (!value) return false;
+ // Allow local file paths starting with /data/logos/
+ if (value.startsWith('/data/logos/')) return true;
+ // Allow valid URLs
+ try {
+ new URL(value);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ ),
}),
- [logo]
- );
+ onSubmit: async (values, { setSubmitting }) => {
+ try {
+ setUploading(true);
+ let uploadResponse = null; // Store upload response for later use
- const {
- register,
- handleSubmit,
- formState: { errors, isSubmitting },
- reset,
- setValue,
- watch,
- } = useForm({
- defaultValues,
- resolver: yupResolver(schema),
+ // If we have a selected file, upload it first
+ if (selectedFile) {
+ try {
+ uploadResponse = await API.uploadLogo(selectedFile, values.name);
+ // Use the uploaded file data instead of form values
+ values.name = uploadResponse.name;
+ values.url = uploadResponse.url;
+ } catch (uploadError) {
+ let errorMessage = 'Failed to upload logo file';
+
+ if (
+ uploadError.code === 'NETWORK_ERROR' ||
+ uploadError.message?.includes('timeout')
+ ) {
+ errorMessage = 'Upload timed out. Please try again.';
+ } else if (uploadError.status === 413) {
+ errorMessage = 'File too large. Please choose a smaller file.';
+ } else if (uploadError.body?.error) {
+ errorMessage = uploadError.body.error;
+ }
+
+ notifications.show({
+ title: 'Upload Error',
+ message: errorMessage,
+ color: 'red',
+ });
+ return; // Don't proceed with creation if upload fails
+ }
+ }
+
+ // Now create or update the logo with the final values
+ // Only proceed if we don't already have a logo from file upload
+ if (logo) {
+ const updatedLogo = await API.updateLogo(logo.id, values);
+ notifications.show({
+ title: 'Success',
+ message: 'Logo updated successfully',
+ color: 'green',
+ });
+ onSuccess?.({ type: 'update', logo: updatedLogo }); // Call onSuccess for updates
+ } else if (!selectedFile) {
+ // Only create a new logo entry if we're not uploading a file
+ // (file upload already created the logo entry)
+ const newLogo = await API.createLogo(values);
+ notifications.show({
+ title: 'Success',
+ message: 'Logo created successfully',
+ color: 'green',
+ });
+ onSuccess?.({ type: 'create', logo: newLogo }); // Call onSuccess for creates
+ } else {
+ // File was uploaded and logo was already created
+ notifications.show({
+ title: 'Success',
+ message: 'Logo uploaded successfully',
+ color: 'green',
+ });
+ onSuccess?.({ type: 'create', logo: uploadResponse });
+ }
+ onClose();
+ } catch (error) {
+ let errorMessage = logo
+ ? 'Failed to update logo'
+ : 'Failed to create logo';
+
+ // Handle specific timeout errors
+ if (
+ error.code === 'NETWORK_ERROR' ||
+ error.message?.includes('timeout')
+ ) {
+ errorMessage = 'Request timed out. Please try again.';
+ } else if (error.response?.data?.error) {
+ errorMessage = error.response.data.error;
+ }
+
+ notifications.show({
+ title: 'Error',
+ message: errorMessage,
+ color: 'red',
+ });
+ } finally {
+ setSubmitting(false);
+ setUploading(false);
+ }
+ },
});
- const onSubmit = async (values) => {
- try {
- setUploading(true);
- let uploadResponse = null; // Store upload response for later use
-
- // If we have a selected file, upload it first
- if (selectedFile) {
- try {
- uploadResponse = await API.uploadLogo(selectedFile, values.name);
- // Use the uploaded file data instead of form values
- values.name = uploadResponse.name;
- values.url = uploadResponse.url;
- } catch (uploadError) {
- let errorMessage = 'Failed to upload logo file';
-
- if (
- uploadError.code === 'NETWORK_ERROR' ||
- uploadError.message?.includes('timeout')
- ) {
- errorMessage = 'Upload timed out. Please try again.';
- } else if (uploadError.status === 413) {
- errorMessage = 'File too large. Please choose a smaller file.';
- } else if (uploadError.body?.error) {
- errorMessage = uploadError.body.error;
- }
-
- notifications.show({
- title: 'Upload Error',
- message: errorMessage,
- color: 'red',
- });
- return; // Don't proceed with creation if upload fails
- }
- }
-
- // Now create or update the logo with the final values
- // Only proceed if we don't already have a logo from file upload
- if (logo) {
- const updatedLogo = await API.updateLogo(logo.id, values);
- notifications.show({
- title: 'Success',
- message: 'Logo updated successfully',
- color: 'green',
- });
- onSuccess?.({ type: 'update', logo: updatedLogo }); // Call onSuccess for updates
- } else if (!selectedFile) {
- // Only create a new logo entry if we're not uploading a file
- // (file upload already created the logo entry)
- const newLogo = await API.createLogo(values);
- notifications.show({
- title: 'Success',
- message: 'Logo created successfully',
- color: 'green',
- });
- onSuccess?.({ type: 'create', logo: newLogo }); // Call onSuccess for creates
- } else {
- // File was uploaded and logo was already created
- notifications.show({
- title: 'Success',
- message: 'Logo uploaded successfully',
- color: 'green',
- });
- onSuccess?.({ type: 'create', logo: uploadResponse });
- }
- onClose();
- } catch (error) {
- let errorMessage = logo
- ? 'Failed to update logo'
- : 'Failed to create logo';
-
- // Handle specific timeout errors
- if (
- error.code === 'NETWORK_ERROR' ||
- error.message?.includes('timeout')
- ) {
- errorMessage = 'Request timed out. Please try again.';
- } else if (error.response?.data?.error) {
- errorMessage = error.response.data.error;
- }
-
- notifications.show({
- title: 'Error',
- message: errorMessage,
- color: 'red',
- });
- } finally {
- setUploading(false);
- }
- };
-
useEffect(() => {
- reset(defaultValues);
- setLogoPreview(logo?.cache_url || null);
+ if (logo) {
+ formik.setValues({
+ name: logo.name || '',
+ url: logo.url || '',
+ });
+ setLogoPreview(logo.cache_url);
+ } else {
+ formik.resetForm();
+ setLogoPreview(null);
+ }
+ // Clear any selected file when logo changes
setSelectedFile(null);
- }, [defaultValues, logo, reset]);
+ }, [logo, isOpen]);
const handleFileSelect = (files) => {
if (files.length === 0) return;
@@ -186,19 +180,18 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => {
setLogoPreview(previewUrl);
// Auto-fill the name field if empty
- const currentName = watch('name');
- if (!currentName) {
+ if (!formik.values.name) {
const nameWithoutExtension = file.name.replace(/\.[^/.]+$/, '');
- setValue('name', nameWithoutExtension);
+ formik.setFieldValue('name', nameWithoutExtension);
}
// Set a placeholder URL (will be replaced after upload)
- setValue('url', 'file://pending-upload');
+ formik.setFieldValue('url', 'file://pending-upload');
};
const handleUrlChange = (event) => {
const url = event.target.value;
- setValue('url', url);
+ formik.setFieldValue('url', url);
// Clear any selected file when manually entering URL
if (selectedFile) {
@@ -217,24 +210,6 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => {
}
};
- const handleUrlBlur = (event) => {
- const urlValue = event.target.value;
- if (urlValue) {
- try {
- const url = new URL(urlValue);
- const pathname = url.pathname;
- const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
- const nameWithoutExtension = filename.replace(/\.[^/.]+$/, '');
- if (nameWithoutExtension) {
- setValue('name', nameWithoutExtension);
- }
- } catch (error) {
- // If the URL is invalid, do nothing.
- // The validation schema will catch this.
- }
- }
- };
-
// Clean up object URLs when component unmounts or preview changes
useEffect(() => {
return () => {
@@ -251,7 +226,7 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => {
title={logo ? 'Edit Logo' : 'Add Logo'}
size="md"
>
-
- }
- multiline
- width={220}
- >
-