now notifications in header drawer scrollable
This commit is contained in:
parent
499cd7e16b
commit
ef79d3a8c0
|
|
@ -9,24 +9,23 @@ import { isIterableArray } from '../../helpers/utils';
|
|||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import Notification from '../notification/JKNotification';
|
||||
import { Scrollbar } from 'react-scrollbars-custom';
|
||||
|
||||
import { fetchNotifications } from '../../store/features/notificationSlice';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
|
||||
import { readNotifications } from '../../helpers/rest';
|
||||
import useNotifications from '../../hooks/useNotifications';
|
||||
|
||||
const JKNotificationDropdown = () => {
|
||||
const { currentUser, isAuthenticated } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
const notifications = useSelector(state => state.notification.notifications);
|
||||
const next = useSelector(state => state.notification.next);
|
||||
const status = useSelector(state => state.notification.status);
|
||||
const unread_total = useSelector(state => state.notification.unread_total);
|
||||
const {
|
||||
notifications,
|
||||
offset,
|
||||
setOffset,
|
||||
next,
|
||||
unread_total,
|
||||
loadNotifications,
|
||||
} = useNotifications(currentUser);
|
||||
|
||||
const LIMIT = 20;
|
||||
const MAX_COUNT_ON_BADGE = 99;
|
||||
const [offset, setOffset] = useState(0);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isAllRead, setIsAllRead] = useState(false);
|
||||
|
|
@ -39,19 +38,6 @@ const JKNotificationDropdown = () => {
|
|||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const loadNotifications = async () => {
|
||||
try {
|
||||
const options = {
|
||||
userId: currentUser.id,
|
||||
offset: offset,
|
||||
limit: LIMIT
|
||||
};
|
||||
await dispatch(fetchNotifications(options)).unwrap();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
readNotifications(currentUser.id)
|
||||
|
|
@ -65,11 +51,12 @@ const JKNotificationDropdown = () => {
|
|||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
loadNotifications();
|
||||
}, []);
|
||||
if(isAuthenticated)
|
||||
loadNotifications();
|
||||
}, [currentUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (offset > 0 && next !== null) {
|
||||
if (isAuthenticated && offset > 0 && next !== null) {
|
||||
loadNotifications();
|
||||
}
|
||||
}, [offset]);
|
||||
|
|
@ -107,9 +94,16 @@ const JKNotificationDropdown = () => {
|
|||
})}
|
||||
>
|
||||
{isIterableArray(notifications) && notifications.length > 0 && !isAllRead && unread_total > 0 && (
|
||||
<div className="num-circle" onClick={handleToggle}>{unread_total < MAX_COUNT_ON_BADGE ? unread_total : `${MAX_COUNT_ON_BADGE}+`}</div>
|
||||
<div className="num-circle" onClick={handleToggle}>
|
||||
{unread_total < MAX_COUNT_ON_BADGE ? unread_total : `${MAX_COUNT_ON_BADGE}+`}
|
||||
</div>
|
||||
)}
|
||||
<FontAwesomeIcon icon={['fas', 'bell']} transform="shrink-5" className="fs-4 bell-icon" onClick={handleToggle} />
|
||||
<FontAwesomeIcon
|
||||
icon={['fas', 'bell']}
|
||||
transform="shrink-5"
|
||||
className="fs-4 bell-icon"
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card" data-testid="notificationDropdown">
|
||||
<Card className="card-notification shadow-none" style={{ maxWidth: '20rem' }}>
|
||||
|
|
@ -129,7 +123,7 @@ const JKNotificationDropdown = () => {
|
|||
mobileNative={true}
|
||||
trackClickBehavior="step"
|
||||
>
|
||||
{isIterableArray(notifications) &&
|
||||
{isIterableArray(notifications) &&
|
||||
notifications.map(notification => (
|
||||
<ListGroupItem
|
||||
key={`notification-drop-item-${notification.notification_id}`}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar'
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
|
|
@ -34,11 +34,6 @@ function JKFriendRequestNotification(props) {
|
|||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!user)
|
||||
dispatch(fetchPerson({ userId: source_user_id }))
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar'
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
import useUserProfile from '../../hooks/useUserProfile';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchPerson } from '../../store/features/peopleSlice';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
const JKGenericNotification = (notification) => {
|
||||
|
||||
const {formatted_msg, created_at, source_user_id} = notification;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const user = useSelector(state => state.people.people.find(person => person.id === source_user_id));
|
||||
const { photoUrl } = useUserProfile(user); // user is the person who sent the message
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchPerson({ userId: source_user_id }))
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {useAuth} from '../../context/UserAuth';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { removeNotification } from '../../store/features/notificationSlice';
|
||||
|
||||
|
||||
import JKGenericNotification from './JKGenericNotification';
|
||||
import JKFriendRequestNotification from './JKFriendRequestNotification';
|
||||
import TextMessageNotification from './JKTextMessageNotification';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchPerson, add as addPerson } from '../../store/features/peopleSlice';
|
||||
import JKMessageButton from '../profile/JKMessageButton';
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar';
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
|
|
@ -10,19 +9,10 @@ import useUserProfile from '../../hooks/useUserProfile';
|
|||
|
||||
function JKTextMessageNotification(props) {
|
||||
const { source_user, source_user_id, message, created_at } = props.notification;
|
||||
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const user = useSelector(state => state.people.people.find(person => person.id === source_user_id));
|
||||
|
||||
const { photoUrl } = useUserProfile(user); // user is the person who sent the message
|
||||
|
||||
useEffect(() => {
|
||||
if(!user)
|
||||
dispatch(fetchPerson({ userId: source_user_id }))
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
|
|
|
|||
|
|
@ -5,35 +5,45 @@ import FalconCardHeader from '../common/FalconCardHeader';
|
|||
import Loader from '../common/Loader';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
|
||||
import { fetchNotifications } from '../../store/features/notificationSlice';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import useNotifications from '../../hooks/useNotifications';
|
||||
|
||||
const JKNotifications = () => {
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
const notifications = useSelector(state => state.notification.notifications);
|
||||
const loadingState = useSelector(state => state.notification.state);
|
||||
|
||||
const LIMIT = 20;
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
const loadNotifications = async () => {
|
||||
try {
|
||||
const options = {
|
||||
userId: currentUser.id,
|
||||
offset: page * LIMIT,
|
||||
limit: LIMIT
|
||||
};
|
||||
await dispatch(fetchNotifications(options)).unwrap();
|
||||
//setPage(prev => prev + 1);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
const { currentUser, isAuthenticated } = useAuth();
|
||||
const {
|
||||
notifications,
|
||||
offset,
|
||||
setOffset,
|
||||
next,
|
||||
unread_total,
|
||||
loadNotifications,
|
||||
notificationStatus: loadingState
|
||||
} = useNotifications(currentUser);
|
||||
|
||||
useEffect(() => {
|
||||
loadNotifications();
|
||||
if(isAuthenticated)
|
||||
loadNotifications();
|
||||
}, [currentUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && offset > 0 && next !== null) {
|
||||
loadNotifications();
|
||||
}
|
||||
}, [offset]);
|
||||
|
||||
useEffect(() => {
|
||||
const onscroll = () => {
|
||||
console.log("scrolling", window.scrollY, window.innerHeight, document.body.scrollHeight);
|
||||
const scrolledTo = window.scrollY + window.innerHeight;
|
||||
const isReachBottom = document.body.scrollHeight === scrolledTo;
|
||||
if (isReachBottom) {
|
||||
setOffset(offset + 1);
|
||||
}
|
||||
};
|
||||
window.addEventListener("scroll", onscroll);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onscroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const JKMessageButton = props => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<JKMessageModal show={showModal} setShow={setShowModal} user={user} currentUser={currentUser} />
|
||||
<JKMessageModal show={showModal} setShow={setShowModal} user={user} />
|
||||
<Button
|
||||
id={"text-message-user-" + user.id}
|
||||
onClick={() => setShowModal(!showModal)}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
import { fetchMessagesByReceiverId, postNewMessage } from '../../store/features/textMessagesSlice';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import useUserProfile from '../../hooks/useUserProfile';
|
||||
|
||||
const JKMessageModal = props => {
|
||||
const { show, setShow, user } = props;
|
||||
|
|
@ -26,6 +27,9 @@ const JKMessageModal = props => {
|
|||
const messageTextBox = useRef();
|
||||
const scrolledToBottom = useRef(false);
|
||||
|
||||
const { photoUrl: userPhotoUrl } = useUserProfile(user);
|
||||
const { photoUrl: currentUserPhotoUrl } = useUserProfile(currentUser);
|
||||
|
||||
const messages = useSelector(state =>
|
||||
state.textMessage.messages
|
||||
.filter(
|
||||
|
|
@ -150,7 +154,7 @@ const JKMessageModal = props => {
|
|||
<div className="d-flex mb-3 mr-1 text-message-row" key={message.id}>
|
||||
<div className="avatar avatar-2xl d-inline-block">
|
||||
<JKProfileAvatar
|
||||
url={message.receiverId === currentUser.id ? currentUser.photo_url : user.photo_url}
|
||||
src={message.receiverId === currentUser.id ? userPhotoUrl : currentUserPhotoUrl }
|
||||
/>
|
||||
</div>
|
||||
<div className="d-inline-block">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import useOnScreen from '../../hooks/useOnScreen';
|
|||
import useKeepScrollPosition from '../../hooks/useKeepScrollPosition';
|
||||
|
||||
import { useLobbyChat } from './JKLobbyChatContext';
|
||||
import useUserProfile from '../../hooks/useUserProfile';
|
||||
|
||||
function JKLobbyChat() {
|
||||
const CHANNEL_LOBBY = 'lobby';
|
||||
|
|
@ -28,6 +29,8 @@ function JKLobbyChat() {
|
|||
const [messagesArrived, setMessagesArrived] = useState(false);
|
||||
//const [offset, setOffset] = useState(0);
|
||||
|
||||
const userProfile = useUserProfile(currentUser);
|
||||
|
||||
const { t } = useTranslation('sessions');
|
||||
|
||||
const chatMessages = useSelector(state => state.lobbyChat.records.messages);
|
||||
|
|
@ -165,7 +168,7 @@ function JKLobbyChat() {
|
|||
<div className="d-flex mb-3 mr-1 text-message-row" key={greaterThan ? `desktop_${message.id}` : `mobile_${message.id}`}>
|
||||
<div className='d-flex align-items-center' ref={ref => (i === 0 ? setLastMessageRef(ref) : null)}>
|
||||
<div className="avatar avatar-2xl">
|
||||
<JKProfileAvatar url={message.user.photo_url} />
|
||||
<JKProfileAvatar src={message.user.photo_url} />
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<div className="d-flex flex-column">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { fetchNotifications } from '../store/features/notificationSlice';
|
||||
import { fetchPeopleByIds } from '../store/features/peopleSlice';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
const useNotifications = user => {
|
||||
const LIMIT = 20;
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
|
||||
const reduxNotifications = useSelector(state => state.notification.notifications);
|
||||
const next = useSelector(state => state.notification.next);
|
||||
const notificationStatus = useSelector(state => state.notification.status);
|
||||
const unread_total = useSelector(state => state.notification.unread_total);
|
||||
const peopleStatus = useSelector(state => state.people.status);
|
||||
const people = useSelector(state => state.people.people);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const loadNotifications = async () => {
|
||||
const options = {
|
||||
userId: user.id,
|
||||
offset: offset,
|
||||
limit: LIMIT
|
||||
};
|
||||
try {
|
||||
await dispatch(fetchNotifications(options)).unwrap();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNotificationSourceUsers = async () => {
|
||||
const sourceUserIds = reduxNotifications.map(notification => notification.source_user_id).filter((value, index, self) => self.indexOf(value) === index);
|
||||
const options = { userId: user.id, ids: sourceUserIds };
|
||||
try {
|
||||
await dispatch(fetchPeopleByIds(options)).unwrap();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (reduxNotifications && reduxNotifications.length && notificationStatus === 'succeeded') {
|
||||
fetchNotificationSourceUsers();
|
||||
}
|
||||
}, [reduxNotifications, notificationStatus]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const newNotifications = reduxNotifications.filter(notification => !notifications.find(n => n.id === notification.id));
|
||||
setNotifications(prev => [...prev, ...newNotifications]);
|
||||
}, [people]);
|
||||
|
||||
return {
|
||||
notifications,
|
||||
offset,
|
||||
setOffset,
|
||||
loadNotifications,
|
||||
next,
|
||||
unread_total,
|
||||
notificationStatus,
|
||||
peopleStatus
|
||||
};
|
||||
};
|
||||
|
||||
export default useNotifications;
|
||||
|
|
@ -1,44 +1,46 @@
|
|||
import { getPersonById } from '../helpers/rest';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { fetchPerson } from '../store/features/peopleSlice';
|
||||
|
||||
const useUserProfile = (user) => {
|
||||
const [userProfile, setUserProfile] = useState(null)
|
||||
const useUserProfile = user => {
|
||||
const [userProfile, setUserProfile] = useState(null);
|
||||
const people = useSelector(state => state.people.people);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setUserProfile(null);
|
||||
return;
|
||||
}
|
||||
getPersonById(user.id)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setUserProfile(data)
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
const person = people.find(person => person.id === user.id);
|
||||
if (person) {
|
||||
setUserProfile(person);
|
||||
} else {
|
||||
dispatch(fetchPerson({ userId: user.id }))
|
||||
.unwrap()
|
||||
.then(resp => {
|
||||
setUserProfile(resp);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
setUserProfile(null);
|
||||
}
|
||||
return () => {
|
||||
setUserProfile(null);
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
|
||||
const photoUrl = useMemo(() => {
|
||||
if(userProfile && userProfile.v2_photo_uploaded){
|
||||
return userProfile.v2_photo_url
|
||||
}else if(userProfile && !userProfile.v2_photo_uploaded){
|
||||
return user.photo_url
|
||||
if (userProfile && userProfile.v2_photo_uploaded) {
|
||||
return userProfile.v2_photo_url;
|
||||
} else if (userProfile && !userProfile.v2_photo_uploaded) {
|
||||
return user.photo_url;
|
||||
}
|
||||
return null
|
||||
}, [userProfile])
|
||||
return null;
|
||||
}, [userProfile]);
|
||||
|
||||
return{
|
||||
return {
|
||||
userProfile,
|
||||
photoUrl
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default useUserProfile;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import {getNotifications, deleteNotification} from '../../helpers/rest'
|
||||
|
||||
const initialState = {
|
||||
notifications: [],
|
||||
next: null,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
|
||||
import { getPeople, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
|
||||
import { getPeople, getPeopleByIds, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
|
||||
|
||||
const initialState = {
|
||||
people: [],
|
||||
|
|
@ -21,12 +21,19 @@ export const fetchPeople = createAsyncThunk(
|
|||
export const preFetchPeople = createAsyncThunk(
|
||||
'people/preFetchPeople',
|
||||
async (options, thunkAPI) => {
|
||||
|
||||
const response = await getPeople(options)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchPeopleByIds = createAsyncThunk(
|
||||
'people/fetchPeopleByIds',
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getPeopleByIds(options)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchPerson = createAsyncThunk(
|
||||
'people/fetchPerson',
|
||||
async (options, thunkAPI) => {
|
||||
|
|
@ -135,6 +142,20 @@ export const peopleSlice = createSlice({
|
|||
state.people.push(action.payload)
|
||||
}
|
||||
})
|
||||
.addCase(fetchPeopleByIds.pending, (state, action) => {
|
||||
state.status = 'loading'
|
||||
})
|
||||
.addCase(fetchPeopleByIds.fulfilled, (state, action) => {
|
||||
const records = new Set([...state.people, ...action.payload.musicians]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
|
||||
state.people = unique
|
||||
state.status = 'succeeded'
|
||||
})
|
||||
.addCase(fetchPeopleByIds.rejected, (state, action) => {
|
||||
state.error = action.error.message
|
||||
state.status = 'failed'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ node :is_blank_filter do |foo|
|
|||
end
|
||||
|
||||
child(:results => :musicians) {
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request, :last_active_timestamp
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score, :is_friend, :is_following, :pending_friend_request, :last_active_timestamp, :v2_photo_url, :v2_photo_uploaded
|
||||
|
||||
# node :is_friend do |musician|
|
||||
# @search.is_friend?(musician)
|
||||
|
|
|
|||
Loading…
Reference in New Issue