Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
9dce7874a4
commit
ca9717fe7c
@ -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>
|
||||||
|
|||||||
57
src/components/common/ToastProvider.tsx
Normal file
57
src/components/common/ToastProvider.tsx
Normal 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;
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user