diff --git a/src/App.tsx b/src/App.tsx index e33f10c..7831dbf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,31 +6,38 @@ import { FirebaseProvider } from './firebase/FirebaseProvider'; import { ErrorBoundary } from './components/common'; import { AuthInitializer } from './components/Auth'; import { ModalProvider } from './components/common/ModalProvider'; +import ToastProvider from './components/common/ToastProvider'; +import { useAppSelector } from './redux'; +import { selectSettings } from './redux'; function App() { + const playerSettings = useAppSelector(selectSettings); + const showToasts = playerSettings?.showToasts ?? true; return ( - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + diff --git a/src/components/common/ToastProvider.tsx b/src/components/common/ToastProvider.tsx new file mode 100644 index 0000000..f11f964 --- /dev/null +++ b/src/components/common/ToastProvider.tsx @@ -0,0 +1,57 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import Toast from './Toast'; +import { ToastContext } from '../../hooks/useToast'; +import type { ToastProps } from '../../types'; + +interface ToastItem extends Omit { + id: string; +} + +const ToastProvider: React.FC<{ children: React.ReactNode; toastsEnabled?: boolean }> = ({ children, toastsEnabled = true }) => { + const [toasts, setToasts] = useState([]); + + const showToast = useCallback((toast: Omit) => { + const id = Math.random().toString(36).substr(2, 9); + const newToast: ToastItem = { ...toast, id }; + setToasts(prev => [...prev, newToast]); + }, []); + + const removeToast = useCallback((id: string) => { + setToasts(prev => prev.filter(toast => toast.id !== id)); + }, []); + + const showSuccess = useCallback((message: string, duration = 3000) => { + showToast({ message, type: 'success', duration }); + }, [showToast]); + + const showError = useCallback((message: string, duration = 5000) => { + showToast({ message, type: 'error', duration }); + }, [showToast]); + + const showInfo = useCallback((message: string, duration = 3000) => { + showToast({ message, type: 'info', duration }); + }, [showToast]); + + const contextValue = useMemo(() => ({ + showToast, + showSuccess, + showError, + showInfo, + removeToast, + }), [showToast, showSuccess, showError, showInfo, removeToast]); + + return ( + + {children} + {toastsEnabled && toasts.map(toast => ( + removeToast(toast.id)} + /> + ))} + + ); +}; + +export default ToastProvider; \ No newline at end of file diff --git a/src/features/Settings/Settings.tsx b/src/features/Settings/Settings.tsx index 61997d4..f9b0eca 100644 --- a/src/features/Settings/Settings.tsx +++ b/src/features/Settings/Settings.tsx @@ -2,7 +2,9 @@ import React, { useState } from 'react'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonList, IonItem, IonLabel, IonToggle, IonButton, IonIcon, IonModal, IonSearchbar } from '@ionic/react'; import { ban } from 'ionicons/icons'; import { useAppSelector } from '../../redux'; -import { selectIsAdmin, selectSettings } from '../../redux'; +import { selectIsAdmin, selectSettings, updateController, selectControllerName } from '../../redux'; +import { useDispatch } from 'react-redux'; +import { settingsService } from '../../firebase/services'; import { useDisabledSongs } from '../../hooks'; import { InfiniteScrollList, ActionButton, SongItem } from '../../components/common'; import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types'; @@ -14,6 +16,7 @@ import type { Song, DisabledSong } from '../../types'; const Settings: React.FC = () => { const isAdmin = useAppSelector(selectIsAdmin); const playerSettings = useAppSelector(selectSettings); + const dispatch = useDispatch(); const { disabledSongs, loading, @@ -22,6 +25,8 @@ const Settings: React.FC = () => { const [showDisabledSongsModal, setShowDisabledSongsModal] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + const controllerNameRedux = useAppSelector(selectControllerName); + const existingPlayer = useAppSelector(state => state.controller.data?.player) || {}; // Convert disabled songs object to array for display const disabledSongsArray: DisabledSong[] = Object.entries(disabledSongs).map(([key, disabledSong]) => ({ @@ -38,8 +43,23 @@ const Settings: React.FC = () => { : disabledSongsArray; const handleToggleSetting = async (setting: string, value: boolean) => { - // This would need to be implemented with the settings service debugLog(`Toggle ${setting} to ${value}`); + const controllerName = controllerNameRedux; + if (controllerName) { + await settingsService.updateSetting(controllerName, setting, value); + dispatch(updateController({ + controllerName, + updates: { + player: { + ...existingPlayer, + settings: { + ...existingPlayer.settings, + [setting]: value + } + } + } + })); + } }; const handleToggleDebug = (enabled: boolean) => { @@ -88,6 +108,14 @@ const Settings: React.FC = () => { onIonChange={(e) => handleToggleSetting('userpick', e.detail.checked)} /> + + Show Toasts + handleToggleSetting('showToasts', e.detail.checked)} + /> + Debug Logging { - id: string; +interface ToastContextType { + showToast: (toast: Omit) => void; + showSuccess: (message: string, duration?: number) => void; + showError: (message: string, duration?: number) => void; + showInfo: (message: string, duration?: number) => void; + removeToast: (id: string) => void; } +export const ToastContext = createContext(undefined); + export const useToast = () => { - const [toasts, setToasts] = useState([]); - - const showToast = useCallback((toast: Omit) => { - const id = Math.random().toString(36).substr(2, 9); - const newToast: ToastItem = { - ...toast, - id, - }; - - setToasts(prev => [...prev, newToast]); - }, []); - - const removeToast = useCallback((id: string) => { - setToasts(prev => prev.filter(toast => toast.id !== id)); - }, []); - - const showSuccess = useCallback((message: string, duration = 3000) => { - showToast({ message, type: 'success', duration }); - }, [showToast]); - - const showError = useCallback((message: string, duration = 5000) => { - showToast({ message, type: 'error', duration }); - }, [showToast]); - - const showInfo = useCallback((message: string, duration = 3000) => { - showToast({ message, type: 'info', duration }); - }, [showToast]); - - return { - toasts, - showToast, - showSuccess, - showError, - showInfo, - removeToast, - }; + return useContext(ToastContext); }; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index f3a7b2f..d595dd0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -36,6 +36,7 @@ export interface QueueItem extends Keyable { export interface Settings { autoadvance: boolean; userpick: boolean; + showToasts?: boolean; } export interface Singer extends Keyable {