ai-helpdesk-api / MobileApp /src /components /NotificationProvider.js
ritesh19180's picture
Upload folder using huggingface_hub
d3ec6da verified
Raw
History Blame Contribute Delete
5.02 kB
import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
Platform,
} from 'react-native';
import { CheckCircle2, XCircle, AlertTriangle, Info, X } from 'lucide-react-native';
const NotificationContext = createContext(null);
const TYPES = {
success: { bg: '#f0fdf4', border: '#bbf7d0', text: '#15803d', icon: CheckCircle2, iconColor: '#16a34a' },
error: { bg: '#fef2f2', border: '#fecaca', text: '#b91c1c', icon: XCircle, iconColor: '#dc2626' },
warning: { bg: '#fffbeb', border: '#fed7aa', text: '#92400e', icon: AlertTriangle, iconColor: '#f59e0b' },
info: { bg: '#eff6ff', border: '#bfdbfe', text: '#1e40af', icon: Info, iconColor: '#3b82f6' },
};
const Toast = ({ id, type = 'info', title, message, onDismiss }) => {
const config = TYPES[type] || TYPES.info;
const IconComponent = config.icon;
const slideAnim = useRef(new Animated.Value(-120)).current;
const opacityAnim = useRef(new Animated.Value(0)).current;
React.useEffect(() => {
Animated.parallel([
Animated.spring(slideAnim, { toValue: 0, damping: 18, stiffness: 200, useNativeDriver: true }),
Animated.timing(opacityAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
]).start();
}, []);
const dismiss = useCallback(() => {
Animated.parallel([
Animated.timing(slideAnim, { toValue: -120, duration: 250, useNativeDriver: true }),
Animated.timing(opacityAnim, { toValue: 0, duration: 200, useNativeDriver: true }),
]).start(() => onDismiss(id));
}, [id, onDismiss, slideAnim, opacityAnim]);
return (
<Animated.View
style={[
styles.toast,
{
backgroundColor: config.bg,
borderColor: config.border,
transform: [{ translateY: slideAnim }],
opacity: opacityAnim,
},
]}
>
<View style={[styles.toastIcon, { backgroundColor: config.iconColor + '18' }]}>
<IconComponent size={18} color={config.iconColor} />
</View>
<View style={styles.toastContent}>
{title ? <Text style={[styles.toastTitle, { color: config.text }]}>{title}</Text> : null}
{message ? <Text style={[styles.toastMessage, { color: config.text + 'cc' }]}>{message}</Text> : null}
</View>
<TouchableOpacity onPress={dismiss} style={styles.toastClose}>
<X size={16} color={config.text} />
</TouchableOpacity>
</Animated.View>
);
};
// topInset is passed in from App.js (which has access to SafeAreaProvider context)
export const NotificationProvider = ({ children, topInset = 50 }) => {
const [toasts, setToasts] = useState([]);
const timerRefs = useRef({});
const dismiss = useCallback((id) => {
clearTimeout(timerRefs.current[id]);
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
const notify = useCallback(({ type = 'info', title, message, duration = 4000 }) => {
const id = Date.now().toString();
setToasts((prev) => [...prev.slice(-2), { id, type, title, message }]);
timerRefs.current[id] = setTimeout(() => dismiss(id), duration);
return id;
}, [dismiss]);
const success = useCallback((title, message, opts) => notify({ type: 'success', title, message, ...opts }), [notify]);
const error = useCallback((title, message, opts) => notify({ type: 'error', title, message, ...opts }), [notify]);
const warning = useCallback((title, message, opts) => notify({ type: 'warning', title, message, ...opts }), [notify]);
const info = useCallback((title, message, opts) => notify({ type: 'info', title, message, ...opts }), [notify]);
return (
<NotificationContext.Provider value={{ notify, success, error, warning, info }}>
{children}
<View style={[styles.container, { top: topInset + 8 }]} pointerEvents="box-none">
{toasts.map((toast) => (
<Toast key={toast.id} {...toast} onDismiss={dismiss} />
))}
</View>
</NotificationContext.Provider>
);
};
export const useNotification = () => {
const ctx = useContext(NotificationContext);
if (!ctx) throw new Error('useNotification must be used inside NotificationProvider');
return ctx;
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 16,
right: 16,
zIndex: 9999,
gap: 8,
},
toast: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 16,
borderWidth: 1.5,
padding: 14,
gap: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 8,
},
toastIcon: {
width: 36,
height: 36,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0,
},
toastContent: { flex: 1 },
toastTitle: { fontSize: 14, fontWeight: '700', marginBottom: 2 },
toastMessage: { fontSize: 13, fontWeight: '500', lineHeight: 18 },
toastClose: { padding: 4, flexShrink: 0 },
});