Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-21 08:53:55 -05:00
parent 9dce7874a4
commit ca9717fe7c
5 changed files with 123 additions and 59 deletions

View File

@ -6,13 +6,19 @@ import { FirebaseProvider } from './firebase/FirebaseProvider';
import { ErrorBoundary } from './components/common'; import { ErrorBoundary } from './components/common';
import { AuthInitializer } from './components/Auth'; import { AuthInitializer } from './components/Auth';
import { ModalProvider } from './components/common/ModalProvider'; import { ModalProvider } from './components/common/ModalProvider';
import ToastProvider from './components/common/ToastProvider';
import { useAppSelector } from './redux';
import { selectSettings } from './redux';
function App() { function App() {
const playerSettings = useAppSelector(selectSettings);
const showToasts = playerSettings?.showToasts ?? true;
return ( return (
<ErrorBoundary> <ErrorBoundary>
<FirebaseProvider> <FirebaseProvider>
<Router> <Router>
<AuthInitializer> <AuthInitializer>
<ToastProvider toastsEnabled={showToasts}>
<ModalProvider> <ModalProvider>
<Layout> <Layout>
<Routes> <Routes>
@ -31,6 +37,7 @@ function App() {
</Routes> </Routes>
</Layout> </Layout>
</ModalProvider> </ModalProvider>
</ToastProvider>
</AuthInitializer> </AuthInitializer>
</Router> </Router>
</FirebaseProvider> </FirebaseProvider>

View File

@ -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<ToastProps, 'onClose'> {
id: string;
}
const ToastProvider: React.FC<{ children: React.ReactNode; toastsEnabled?: boolean }> = ({ children, toastsEnabled = true }) => {
const [toasts, setToasts] = useState<ToastItem[]>([]);
const showToast = useCallback((toast: Omit<ToastProps, 'onClose'>) => {
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 (
<ToastContext.Provider value={contextValue}>
{children}
{toastsEnabled && toasts.map(toast => (
<Toast
key={toast.id}
{...toast}
onClose={() => removeToast(toast.id)}
/>
))}
</ToastContext.Provider>
);
};
export default ToastProvider;

View File

@ -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 { IonContent, IonHeader, IonTitle, IonToolbar, IonList, IonItem, IonLabel, IonToggle, IonButton, IonIcon, IonModal, IonSearchbar } from '@ionic/react';
import { ban } from 'ionicons/icons'; import { ban } from 'ionicons/icons';
import { useAppSelector } from '../../redux'; 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 { useDisabledSongs } from '../../hooks';
import { InfiniteScrollList, ActionButton, SongItem } from '../../components/common'; import { InfiniteScrollList, ActionButton, SongItem } from '../../components/common';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types'; import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
@ -14,6 +16,7 @@ import type { Song, DisabledSong } from '../../types';
const Settings: React.FC = () => { const Settings: React.FC = () => {
const isAdmin = useAppSelector(selectIsAdmin); const isAdmin = useAppSelector(selectIsAdmin);
const playerSettings = useAppSelector(selectSettings); const playerSettings = useAppSelector(selectSettings);
const dispatch = useDispatch();
const { const {
disabledSongs, disabledSongs,
loading, loading,
@ -22,6 +25,8 @@ const Settings: React.FC = () => {
const [showDisabledSongsModal, setShowDisabledSongsModal] = useState(false); const [showDisabledSongsModal, setShowDisabledSongsModal] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const controllerNameRedux = useAppSelector(selectControllerName);
const existingPlayer = useAppSelector(state => state.controller.data?.player) || {};
// Convert disabled songs object to array for display // Convert disabled songs object to array for display
const disabledSongsArray: DisabledSong[] = Object.entries(disabledSongs).map(([key, disabledSong]) => ({ const disabledSongsArray: DisabledSong[] = Object.entries(disabledSongs).map(([key, disabledSong]) => ({
@ -38,8 +43,23 @@ const Settings: React.FC = () => {
: disabledSongsArray; : disabledSongsArray;
const handleToggleSetting = async (setting: string, value: boolean) => { const handleToggleSetting = async (setting: string, value: boolean) => {
// This would need to be implemented with the settings service
debugLog(`Toggle ${setting} to ${value}`); 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) => { const handleToggleDebug = (enabled: boolean) => {
@ -88,6 +108,14 @@ const Settings: React.FC = () => {
onIonChange={(e) => handleToggleSetting('userpick', e.detail.checked)} onIonChange={(e) => handleToggleSetting('userpick', e.detail.checked)}
/> />
</IonItem> </IonItem>
<IonItem>
<IonLabel>Show Toasts</IonLabel>
<IonToggle
slot="end"
checked={playerSettings?.showToasts ?? true}
onIonChange={(e) => handleToggleSetting('showToasts', e.detail.checked)}
/>
</IonItem>
<IonItem> <IonItem>
<IonLabel>Debug Logging</IonLabel> <IonLabel>Debug Logging</IonLabel>
<IonToggle <IonToggle

View File

@ -1,45 +1,16 @@
import { useState, useCallback } from 'react'; import { createContext, useContext } from 'react';
import type { ToastProps } from '../types'; import type { ToastProps } from '../types';
interface ToastItem extends Omit<ToastProps, 'onClose'> { interface ToastContextType {
id: string; showToast: (toast: Omit<ToastProps, 'onClose'>) => 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<ToastContextType | undefined>(undefined);
export const useToast = () => { export const useToast = () => {
const [toasts, setToasts] = useState<ToastItem[]>([]); return useContext(ToastContext);
const showToast = useCallback((toast: Omit<ToastProps, 'onClose'>) => {
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,
};
}; };

View File

@ -36,6 +36,7 @@ export interface QueueItem extends Keyable {
export interface Settings { export interface Settings {
autoadvance: boolean; autoadvance: boolean;
userpick: boolean; userpick: boolean;
showToasts?: boolean;
} }
export interface Singer extends Keyable { export interface Singer extends Keyable {