import PropTypes from 'prop-types' import { useState, useEffect, useContext, useRef, useCallback, useMemo, memo } from 'react' import { Button, message, Popover, Typography, Space, Flex } from 'antd' import { UserOutlined } from '@ant-design/icons' import BellIcon from '../../Icons/BellIcon' import MailCheckIcon from '../../Icons/MailCheckIcon' import MailIcon from '../../Icons/MailIcon' import { ApiServerContext } from '../context/ApiServerContext' import { AuthContext } from '../context/AuthContext' import { LoadingOutlined } from '@ant-design/icons' import InfoCircleIcon from '../../Icons/InfoCircleIcon' const { Text } = Typography const UserNotifierToggle = memo(({ type, objectData, size = 'middle', disabled = false, ...buttonProps }) => { const { toggleUserNotifier, editUserNotifier, fetchUserNotifiersForObject, fetchAllUserNotifiersForObject, connected, subscribeToObjectUpdates, subscribeToObjectTypeUpdates } = useContext(ApiServerContext) const { userProfile, token, authInitialized } = useContext(AuthContext) const [currentUserNotifier, setCurrentUserNotifier] = useState(null) const [loading, setLoading] = useState(false) const [initialLoad, setInitialLoad] = useState(true) const [allNotifiers, setAllNotifiers] = useState([]) const [popoverOpen, setPopoverOpen] = useState(false) const [popoverLoading, setPopoverLoading] = useState(false) const [emailTogglingId, setEmailTogglingId] = useState(null) const subscribedIdsRef = useRef([]) const unsubscribesRef = useRef([]) const subscribeToObjectTypeUpdatesRef = useRef(null) const updateEventHandlerRef = useRef() const popoverOpenRef = useRef(popoverOpen) popoverOpenRef.current = popoverOpen const objectId = objectData?._id const authReady = Boolean(token && authInitialized) const isNotifying = Boolean(currentUserNotifier) const subscriptionFilter = useMemo( () => ({ object: objectId, objectType: type }), [objectId, type] ) const notifierMatchesFilter = useCallback( (notifier) => { const objectRef = notifier?.object?._id ?? notifier?.object return objectRef === objectId && notifier?.objectType === type }, [objectId, type] ) const apiRef = useRef({ fetchUserNotifiersForObject, fetchAllUserNotifiersForObject }) apiRef.current = { fetchUserNotifiersForObject, fetchAllUserNotifiersForObject } const loadNotifierState = useCallback( async (silent = false) => { if (!authReady || !objectId || !type) return if (!silent) { setInitialLoad(true) } try { const { data } = await apiRef.current.fetchUserNotifiersForObject( objectId, type ) setCurrentUserNotifier(data?.[0] ?? null) } catch (error) { console.error('Error fetching user notifier state:', error) } finally { if (!silent) { setInitialLoad(false) } } }, [authReady, objectId, type] ) const loadAllNotifiers = useCallback( async (silent = false) => { if (!authReady || !objectId || !type) return if (!silent) { setPopoverLoading(true) } try { const { data } = await apiRef.current.fetchAllUserNotifiersForObject( objectId, type ) setAllNotifiers(data || []) } catch (error) { console.error('Error fetching all user notifiers:', error) setAllNotifiers([]) } finally { if (!silent) { setPopoverLoading(false) } } }, [authReady, objectId, type] ) const loadNotifierStateRef = useRef(loadNotifierState) loadNotifierStateRef.current = loadNotifierState const loadAllNotifiersRef = useRef(loadAllNotifiers) loadAllNotifiersRef.current = loadAllNotifiers useEffect(() => { loadNotifierState() }, [loadNotifierState]) useEffect(() => { if (!popoverOpen) return loadAllNotifiers() }, [popoverOpen, loadAllNotifiers]) const updateEventHandler = useCallback( (id, updatedData) => { setAllNotifiers((prev) => { const existing = prev.find((notifier) => notifier._id === id) const merged = existing ? { ...existing, ...updatedData } : { _id: id, ...updatedData } if (!notifierMatchesFilter(merged)) { return prev.filter((notifier) => notifier._id !== id) } if (existing) { return prev.map((notifier) => notifier._id === id ? merged : notifier ) } return [...prev, merged] }) setCurrentUserNotifier((prev) => { if (prev?._id === id) { const merged = { ...prev, ...updatedData } return notifierMatchesFilter(merged) ? merged : null } const userRef = updatedData?.user?._id ?? updatedData?.user if ( userRef === userProfile?._id && notifierMatchesFilter({ _id: id, ...updatedData }) ) { return { _id: id, ...updatedData } } return prev }) }, [notifierMatchesFilter, userProfile?._id] ) updateEventHandlerRef.current = updateEventHandler useEffect(() => { if (connected !== true || !objectId || !type) return const unsubscribe = subscribeToObjectTypeUpdates( 'userNotifier', subscriptionFilter, () => { loadNotifierStateRef.current(true) if (popoverOpenRef.current) { loadAllNotifiersRef.current(true) } } ) subscribeToObjectTypeUpdatesRef.current = unsubscribe return () => { if (unsubscribe) unsubscribe() if (subscribeToObjectTypeUpdatesRef.current === unsubscribe) { subscribeToObjectTypeUpdatesRef.current = null } } }, [connected, subscriptionFilter, subscribeToObjectTypeUpdates, objectId, type]) const subscribedNotifierIds = useMemo(() => { const ids = new Set( allNotifiers.map((notifier) => notifier._id).filter(Boolean) ) if (currentUserNotifier?._id) { ids.add(currentUserNotifier._id) } return [...ids] }, [allNotifiers, currentUserNotifier]) useEffect(() => { if (connected !== true) return const newNotifierIds = subscribedNotifierIds.filter( (id) => !subscribedIdsRef.current.includes(id) ) newNotifierIds.forEach((itemId) => { const unsubscribe = subscribeToObjectUpdates( itemId?.toLowerCase(), 'userNotifier', (updateData) => updateEventHandlerRef.current(itemId, updateData) ) subscribedIdsRef.current.push(itemId) if (unsubscribe) { unsubscribesRef.current.push(unsubscribe) } }) const notifierIdsToUnsubscribe = subscribedIdsRef.current.filter( (id) => !subscribedNotifierIds.includes(id) ) notifierIdsToUnsubscribe.forEach((itemId) => { const index = subscribedIdsRef.current.indexOf(itemId) if (index > -1) { subscribedIdsRef.current.splice(index, 1) const unsubscribe = unsubscribesRef.current[index] if (unsubscribe) { unsubscribe() } unsubscribesRef.current.splice(index, 1) } }) }, [subscribedNotifierIds, connected, subscribeToObjectUpdates]) useEffect(() => { return () => { unsubscribesRef.current.forEach((unsubscribe) => { if (unsubscribe) unsubscribe() }) unsubscribesRef.current = [] subscribedIdsRef.current = [] if (subscribeToObjectTypeUpdatesRef.current) { subscribeToObjectTypeUpdatesRef.current() subscribeToObjectTypeUpdatesRef.current = null } } }, []) const handleClick = useCallback(async () => { if (!authReady || !objectId || !type || loading) return setLoading(true) try { const enabled = await toggleUserNotifier(objectId, type) if (enabled) { const { data } = await fetchUserNotifiersForObject(objectId, type) setCurrentUserNotifier(data?.[0] ?? null) } else { setCurrentUserNotifier(null) } if (popoverOpen) { const { data } = await fetchAllUserNotifiersForObject(objectId, type) setAllNotifiers(data || []) } message.success( enabled ? 'Notifications enabled for this object' : 'Notifications disabled for this object' ) } catch (error) { console.error('Error toggling user notifier:', error) message.error('Failed to update notifications') } finally { setLoading(false) } }, [authReady, objectId, type, loading, popoverOpen, toggleUserNotifier, fetchUserNotifiersForObject, fetchAllUserNotifiersForObject]) const getUserDisplayName = useCallback((user) => { if (!user) return 'Unknown' return ( user.name || user.username || `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.email || 'Unknown' ) }, []) const isCurrentUser = useCallback((user) => user?._id === userProfile?._id, [userProfile?._id]) const handleEmailToggle = useCallback(async (item) => { if (!isCurrentUser(item.user) || emailTogglingId) return setEmailTogglingId(item._id) const newEmail = !item.email try { const result = await editUserNotifier(item._id, { email: newEmail }) if (result) { setAllNotifiers((prev) => prev.map((n) => n._id === item._id ? { ...n, email: result.email ?? newEmail } : n ) ) setCurrentUserNotifier((prev) => prev?._id === item._id ? { ...prev, email: result.email ?? newEmail } : prev ) message.success( (result.email ?? newEmail) ? 'Email notifications enabled' : 'Email notifications disabled' ) } } catch (error) { console.error('Error toggling email:', error) message.error('Failed to update email notifications') } finally { setEmailTogglingId(null) } }, [isCurrentUser, emailTogglingId, editUserNotifier]) const popoverContent = useMemo( () => ( {popoverLoading ? ( Loading, please wait... ) : allNotifiers.length === 0 ? ( No users subscribed. ) : ( <> {[...allNotifiers] .sort( (a, b) => (isCurrentUser(b.user) ? 1 : 0) - (isCurrentUser(a.user) ? 1 : 0) ) .map((item) => ( {getUserDisplayName(item.user)} {isCurrentUser(item.user) && ( (you) )}