show notifications in header as a drawer dropdown
This commit is contained in:
parent
6dae6ba53c
commit
b2fe71e482
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import JKLayout from './layouts/JKLayout';
|
||||
import Layout from './layouts/JKLayout';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
|
|
@ -10,7 +10,7 @@ const App = () => {
|
|||
|
||||
return (
|
||||
<Router basename={process.env.PUBLIC_URL}>
|
||||
<JKLayout />
|
||||
<Layout />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
.navbar-light{
|
||||
justify-content:left;
|
||||
.navbar-text {
|
||||
color: $jk-navigation-text-color;
|
||||
a {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
function JKTimeAgo(props) {
|
||||
return (
|
||||
<TimeAgo
|
||||
date={props.date}
|
||||
formatter={(value, unit) => {
|
||||
if (unit === 'second' && value < 15) return 'just now';
|
||||
if (unit === 'second') return 'few seconds ago';
|
||||
if (unit === 'minute') return `${value} ${value === 1 ? 'minute' : 'minutes'} ago`;
|
||||
if (unit === 'hour') return `${value} ${value === 1 ? 'hour' : 'hours'} ago`;
|
||||
if (unit === 'day') return `${value} ${value === 1 ? 'day' : 'days'} ago`;
|
||||
if (unit === 'week') return `${value} ${value === 1 ? 'week' : 'weeks'} ago`;
|
||||
if (unit === 'month') return `${value} ${value === 1 ? 'month' : 'months'} ago`;
|
||||
if (unit === 'year') return `${value} ${value === 1 ? 'year' : 'years'} ago`;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKTimeAgo;
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useContext, useEffect } from 'react';
|
||||
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import NavbarTop from '../navbar/NavbarTop';
|
||||
import NavbarVertical from '../navbar/NavbarVertical';
|
||||
import JKFooter from '../footer/JKFooter';
|
||||
import NavbarTop from '../navbar/JKNavbarTop';
|
||||
import NavbarVertical from '../navbar/JKNavbarVertical';
|
||||
import Footer from '../footer/JKFooter';
|
||||
|
||||
import AppContext from '../../context/Context';
|
||||
import { getPageName } from '../../helpers/utils';
|
||||
|
|
@ -12,17 +11,19 @@ import { getPageName } from '../../helpers/utils';
|
|||
import useScript from '../../hooks/useScript';
|
||||
import { useDispatch } from "react-redux";
|
||||
import { addMessage } from "../../store/features/textMessagesSlice"
|
||||
import { add as addNotification } from '../../store/features/notificationSlice';
|
||||
|
||||
import JKHome from './JkHome';
|
||||
import Home from './JkHome';
|
||||
import loadable from '@loadable/component';
|
||||
const JKDashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
|
||||
import { truthy } from 'is_js';
|
||||
const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
|
||||
|
||||
function JKDashboard() {
|
||||
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
|
||||
const isKanban = getPageName('kanban');
|
||||
|
||||
useEffect(() => {
|
||||
JKDashboardRoutes.preload();
|
||||
DashboardRoutes.preload();
|
||||
}, []);
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
|
@ -48,28 +49,71 @@ function JKDashboard() {
|
|||
});
|
||||
|
||||
registerTextMessageCallback();
|
||||
|
||||
registerFriendRequest();
|
||||
registerFriendRequestAccepted();
|
||||
}
|
||||
|
||||
const registerTextMessageCallback = () => {
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.TEXT_MESSAGE, function(header, payload) {
|
||||
const json = payload
|
||||
const receivedMsg = {
|
||||
id: json.text_message_id,
|
||||
message: json.msg,
|
||||
senderId: json.sender_id,
|
||||
senderName: json.sender_name,
|
||||
console.log('registerTextMessageCallback payload', payload);
|
||||
console.log('registerTextMessageCallback header', header);
|
||||
const msg = {
|
||||
id: payload.text_message_id,
|
||||
message: payload.msg,
|
||||
senderId: payload.sender_id,
|
||||
senderName: payload.sender_name,
|
||||
receiverId: window.currentUser.id,
|
||||
receiverName: window.currentUser.first_name,
|
||||
createdAt: json.created_at,
|
||||
createdAt: payload.created_at,
|
||||
sent: true
|
||||
}
|
||||
|
||||
dispatch(addMessage(receivedMsg))
|
||||
dispatch(addMessage(msg))
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
});
|
||||
}
|
||||
|
||||
const registerFriendRequest = () => {
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
|
||||
console.log('registerFriendRequest payload', payload);
|
||||
console.log('registerFriendRequest header', header);
|
||||
handleNotification(payload, header.type);
|
||||
})
|
||||
}
|
||||
|
||||
const registerFriendRequestAccepted = () => {
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.FRIEND_REQUEST_ACCEPTED, function(header, payload) {
|
||||
console.log('registerFriendRequestAccepted payload', payload);
|
||||
console.log('registerFriendRequestAccepted header', header);
|
||||
handleNotification(payload, header.type);
|
||||
})
|
||||
}
|
||||
|
||||
const handleNotification = (payload, type) => {
|
||||
const notification = {
|
||||
description: type,
|
||||
notification_id: payload.notification_id,
|
||||
session_id: payload.session_id,
|
||||
friend_request_id: payload.friend_request_id,
|
||||
formatted_msg: payload.msg,
|
||||
text_message_id: payload.text_message_id,
|
||||
message: payload.msg,
|
||||
source_user_id: payload.sender_id,
|
||||
source_user: {
|
||||
name: payload.sender_name
|
||||
},
|
||||
created_at: payload.created_at
|
||||
}
|
||||
|
||||
try {
|
||||
dispatch(addNotification(notification));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useScript(`${process.env.REACT_APP_LEGACY_BASE_URL}/client_scripts`, initJKScripts);
|
||||
|
||||
return (
|
||||
|
|
@ -79,13 +123,12 @@ function JKDashboard() {
|
|||
<div className="content">
|
||||
<NavbarTop />
|
||||
<Switch>
|
||||
<Route path="/" exact component={JKHome} />
|
||||
<JKDashboardRoutes />
|
||||
<Route path="/" exact component={Home} />
|
||||
<DashboardRoutes />
|
||||
</Switch>
|
||||
{!isKanban && <JKFooter />}
|
||||
{!isKanban && <Footer />}
|
||||
</div>
|
||||
{/* <SidePanelModal path={location.pathname} /> */}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Collapse, Navbar, NavItem, Nav } from 'reactstrap';
|
||||
import classNames from 'classnames';
|
||||
import AppContext from '../../context/Context';
|
||||
import Logo from './Logo';
|
||||
//import SearchBox from './SearchBox';
|
||||
import TopNavRightSideNavItem from './JKTopNavRightSideNavItem';
|
||||
//import NavbarTopDropDownMenus from './NavbarTopDropDownMenus';
|
||||
import { navbarBreakPoint, topNavbarBreakpoint } from '../../config';
|
||||
//import autoCompleteInitialItem from '../../data/autocomplete/autocomplete';
|
||||
|
||||
const JKNavbarTop = () => {
|
||||
const {
|
||||
showBurgerMenu,
|
||||
setShowBurgerMenu,
|
||||
isTopNav,
|
||||
isVertical,
|
||||
isCombo,
|
||||
navbarCollapsed,
|
||||
setNavbarCollapsed
|
||||
} = useContext(AppContext);
|
||||
const handleBurgerMenu = () => {
|
||||
isTopNav && !isCombo && setNavbarCollapsed(!navbarCollapsed);
|
||||
(isCombo || isVertical) && setShowBurgerMenu(!showBurgerMenu);
|
||||
};
|
||||
return (
|
||||
<Navbar
|
||||
light
|
||||
className="navbar-glass fs--1 font-weight-semi-bold row navbar-top sticky-kit mb-3 d-flex"
|
||||
expand={isTopNav && topNavbarBreakpoint}
|
||||
>
|
||||
<div
|
||||
className={classNames('toggle-icon-wrapper mr-md-3 mr-2', {
|
||||
'd-lg-none': isTopNav && !isCombo,
|
||||
[`d-${navbarBreakPoint}-none`]: isVertical || isCombo
|
||||
})}
|
||||
>
|
||||
<button
|
||||
className="navbar-toggler-humburger-icon btn btn-link d-flex flex-center"
|
||||
onClick={handleBurgerMenu}
|
||||
id="burgerMenu"
|
||||
>
|
||||
<span className="navbar-toggle-icon">
|
||||
<span className="toggle-line" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<Logo at="navbar-top" id="topLogo" width={140} />
|
||||
{/* {isTopNav ? (
|
||||
<Collapse navbar isOpen={navbarCollapsed} className="scrollbar">
|
||||
<Nav navbar>
|
||||
<NavbarTopDropDownMenus setNavbarCollapsed={setNavbarCollapsed} />
|
||||
</Nav>
|
||||
</Collapse>
|
||||
) : (
|
||||
<Nav navbar className={`align-items-center d-none d-${topNavbarBreakpoint}-block`}>
|
||||
<NavItem>
|
||||
<SearchBox autoCompleteItem={autoCompleteInitialItem} />
|
||||
</NavItem>
|
||||
</Nav>
|
||||
)} */}
|
||||
|
||||
<TopNavRightSideNavItem />
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKNavbarTop;
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import classNames from 'classnames';
|
||||
import is from 'is_js';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import { Button, Collapse, Nav, Navbar } from 'reactstrap';
|
||||
import bgNavbarImg from '../../assets/img/generic/bg-navbar.png';
|
||||
import { navbarBreakPoint, topNavbarBreakpoint } from '../../config';
|
||||
import AppContext from '../../context/Context';
|
||||
import routes from '../../routes';
|
||||
import Flex from '../common/Flex';
|
||||
import Logo from './Logo';
|
||||
import NavbarTopDropDownMenus from './NavbarTopDropDownMenus';
|
||||
import NavbarVerticalMenu from './NavbarVerticalMenu';
|
||||
import ToggleButton from './ToggleButton';
|
||||
|
||||
const JKNavbarVertical = ({ navbarStyle }) => {
|
||||
const navBarRef = useRef(null);
|
||||
|
||||
const {
|
||||
showBurgerMenu,
|
||||
isNavbarVerticalCollapsed,
|
||||
setIsNavbarVerticalCollapsed,
|
||||
isCombo,
|
||||
setShowBurgerMenu,
|
||||
setNavbarCollapsed
|
||||
} = useContext(AppContext);
|
||||
|
||||
const HTMLClassList = document.getElementsByTagName('html')[0].classList;
|
||||
//Control Component did mount and unmounted of hover effect
|
||||
if (isNavbarVerticalCollapsed) {
|
||||
HTMLClassList.add('navbar-vertical-collapsed');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (is.windows()) {
|
||||
HTMLClassList.add('windows');
|
||||
}
|
||||
if (is.chrome()) {
|
||||
HTMLClassList.add('chrome');
|
||||
}
|
||||
if (is.firefox()) {
|
||||
HTMLClassList.add('firefox');
|
||||
}
|
||||
return () => {
|
||||
HTMLClassList.remove('navbar-vertical-collapsed-hover');
|
||||
};
|
||||
}, [isNavbarVerticalCollapsed, HTMLClassList]);
|
||||
|
||||
//Control mouseEnter event
|
||||
let time = null;
|
||||
const handleMouseEnter = () => {
|
||||
if (isNavbarVerticalCollapsed) {
|
||||
time = setTimeout(() => {
|
||||
HTMLClassList.add('navbar-vertical-collapsed-hover');
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Navbar
|
||||
expand={navbarBreakPoint}
|
||||
className={classNames('navbar-vertical navbar-glass', {
|
||||
[`navbar-${navbarStyle}`]: navbarStyle !== 'transparent'
|
||||
})}
|
||||
light
|
||||
>
|
||||
<Flex align="center">
|
||||
{/* <ToggleButton
|
||||
isNavbarVerticalCollapsed={isNavbarVerticalCollapsed}
|
||||
setIsNavbarVerticalCollapsed={setIsNavbarVerticalCollapsed}
|
||||
/> */}
|
||||
<Logo at="navbar-vertical" width={250} />
|
||||
</Flex>
|
||||
|
||||
<Collapse
|
||||
navbar
|
||||
isOpen={showBurgerMenu}
|
||||
className="scrollbar"
|
||||
innerRef={navBarRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={() => {
|
||||
clearTimeout(time);
|
||||
HTMLClassList.remove('navbar-vertical-collapsed-hover');
|
||||
}}
|
||||
style={
|
||||
navbarStyle === 'vibrant' && {
|
||||
backgroundImage: `linear-gradient(-45deg, rgba(0, 160, 255, 0.86), #0048a2),url(${bgNavbarImg})`
|
||||
}
|
||||
}
|
||||
>
|
||||
<Nav navbar vertical className="mt-3">
|
||||
<NavbarVerticalMenu routes={routes} />
|
||||
</Nav>
|
||||
<div className="settings px-3 px-xl-0">
|
||||
{isCombo && (
|
||||
<div className={`d-${topNavbarBreakpoint}-none`}>
|
||||
<div className="navbar-vertical-divider">
|
||||
<hr className="navbar-vertical-hr my-2" />
|
||||
</div>
|
||||
<Nav navbar>
|
||||
<NavbarTopDropDownMenus setNavbarCollapsed={setNavbarCollapsed} setShowBurgerMenu={setShowBurgerMenu} />
|
||||
</Nav>
|
||||
</div>
|
||||
)}
|
||||
{/* <div className="navbar-vertical-divider">
|
||||
<hr className="navbar-vertical-hr my-2" />
|
||||
</div>
|
||||
<Button
|
||||
tag={'a'}
|
||||
href="https://themes.getbootstrap.com/product/falcon-admin-dashboard-webapp-template-react/"
|
||||
target="_blank"
|
||||
color="primary"
|
||||
size="sm"
|
||||
block
|
||||
className="my-3 btn-purchase"
|
||||
>
|
||||
Purchase
|
||||
</Button> */}
|
||||
</div>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
JKNavbarVertical.protoTypes = {
|
||||
navbarStyle: PropTypes.string
|
||||
};
|
||||
|
||||
JKNavbarVertical.defaultProps = {
|
||||
navbarStyle: 'transparent'
|
||||
};
|
||||
|
||||
export default JKNavbarVertical;
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import ListGroup from 'reactstrap/es/ListGroup';
|
||||
import ListGroupItem from 'reactstrap/es/ListGroupItem';
|
||||
//import { rawEarlierNotifications, rawNewNotifications } from '../../data/notification/notification';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
//import useFakeFetch from '../../hooks/useFakeFetch';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import Notification from '../notification/JKNotification';
|
||||
|
||||
import { fetchNotifications } from '../../store/features/notificationSlice';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const JKNotificationDropdown = () => {
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
const notifications = useSelector(state => state.notification.notifications.slice(0, 5));
|
||||
|
||||
const LIMIT = 20;
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
// State
|
||||
//const { data: newNotifications, setData: setNewNotifications } = useFakeFetch(rawNewNotifications);
|
||||
//const { data: earlierNotifications, setData: setEarlierNotifications } = useFakeFetch(rawEarlierNotifications);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isAllRead, setIsAllRead] = useState(false);
|
||||
|
||||
// Handler
|
||||
const handleToggle = e => {
|
||||
e.preventDefault();
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
// const markAsRead = e => {
|
||||
// e.preventDefault();
|
||||
// const updatedNewNotifications = newNotifications.map(notification => {
|
||||
// if (notification.hasOwnProperty('unread')) {
|
||||
// return {
|
||||
// ...notification,
|
||||
// unread: false
|
||||
// };
|
||||
// }
|
||||
// return notification;
|
||||
// });
|
||||
// const updatedEarlierNotifications = earlierNotifications.map(notification => {
|
||||
// if (notification.hasOwnProperty('unread')) {
|
||||
// return {
|
||||
// ...notification,
|
||||
// unread: false
|
||||
// };
|
||||
// }
|
||||
// setIsAllRead(true);
|
||||
// return notification;
|
||||
// });
|
||||
|
||||
// setNewNotifications(updatedNewNotifications);
|
||||
// setEarlierNotifications(updatedEarlierNotifications);
|
||||
// };
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadNotifications();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
className="mx-3"
|
||||
isOpen={isOpen}
|
||||
toggle={handleToggle}
|
||||
onMouseOver={() => {
|
||||
let windowWidth = window.innerWidth;
|
||||
windowWidth > 992 && setIsOpen(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
let windowWidth = window.innerWidth;
|
||||
windowWidth > 992 && setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<DropdownToggle
|
||||
nav
|
||||
className={classNames('px-0', {
|
||||
'notification-indicator notification-indicator-primary': !isAllRead
|
||||
})}
|
||||
>
|
||||
<FontAwesomeIcon icon="bell" transform="shrink-6" className="fs-4" />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card">
|
||||
<Card className="card-notification shadow-none" style={{ maxWidth: '20rem' }}>
|
||||
<FalconCardHeader className="card-header" title="Notifications" titleTag="h6" light={false}>
|
||||
{/* <Link className="card-link font-weight-normal" to="#!">
|
||||
Mark all as read
|
||||
</Link> */}
|
||||
</FalconCardHeader>
|
||||
<ListGroup flush className="font-weight-normal fs--1">
|
||||
{isIterableArray(notifications) &&
|
||||
notifications.map(notification => (
|
||||
<ListGroupItem key={notification.notification_id} onClick={handleToggle}>
|
||||
<Notification notification={notification} flush />
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</ListGroup>
|
||||
<div className="card-footer text-center border-top" onClick={handleToggle}>
|
||||
<Link className="card-link d-block" to="/pages/notifications">
|
||||
View all
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKNotificationDropdown;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { DropdownItem, DropdownMenu, DropdownToggle, Dropdown } from 'reactstrap';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const ProfileDropdown = () => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const toggle = () => setDropdownOpen(prevState => !prevState);
|
||||
const {currentUser, setCurrentUser} = useAuth();
|
||||
const history = useHistory();
|
||||
//const [cookies, setCookie, removeCookie] = useCookies(['remember_token']);
|
||||
|
||||
const handleLogout = () => {
|
||||
setCurrentUser(null);
|
||||
//localStorage.setItem('user', null);
|
||||
// removeCookie("remember_token", {
|
||||
// domain: ".jamkazam.local"
|
||||
// });
|
||||
// history.push('/authentication/basic/logout');
|
||||
console.log("signout...");
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
// <Avatar src={team3} />
|
||||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
isOpen={dropdownOpen}
|
||||
toggle={toggle}
|
||||
onMouseOver={() => {
|
||||
let windowWidth = window.innerWidth;
|
||||
windowWidth > 992 && setDropdownOpen(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
let windowWidth = window.innerWidth;
|
||||
windowWidth > 992 && setDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
<DropdownToggle nav className="pr-0">
|
||||
{currentUser && currentUser.name}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card">
|
||||
<div className="bg-white rounded-soft py-2">
|
||||
<DropdownItem tag={Link} to="/pages/settings">
|
||||
My Profile
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={handleLogout}>
|
||||
Logout
|
||||
</DropdownItem>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileDropdown;
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Nav, NavItem, NavLink, UncontrolledTooltip, NavbarText } from 'reactstrap';
|
||||
import ProfileDropdown from './JKProfileDropdown';
|
||||
import NotificationDropdown from './JKNotificationDropdown';
|
||||
//import SettingsAnimatedIcon from './SettingsAnimatedIcon';
|
||||
import CartNotification from './CartNotification';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Link } from 'react-router-dom';
|
||||
import AppContext from '../../context/Context';
|
||||
import classNames from 'classnames';
|
||||
import { navbarBreakPoint } from '../../config';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
const TopNavRightSideNavItem = () => {
|
||||
|
||||
//const notifications = useSelector(state => state.notification.notifications.slice(0, 5));
|
||||
|
||||
|
||||
const { isTopNav, isCombo } = useContext(AppContext);
|
||||
return (
|
||||
<Nav navbar className="navbar-nav-icons ml-auto flex-row align-items-center">
|
||||
{/* <NavItem>
|
||||
<SettingsAnimatedIcon />
|
||||
</NavItem> */}
|
||||
<NavbarText className="d-none d-md-inline">Keep JamKazam Improving:</NavbarText>
|
||||
<NavItem className="d-none d-md-inline ml-1 mr-6">
|
||||
<NavLink>Subscribe</NavLink>
|
||||
</NavItem>
|
||||
{(isCombo || isTopNav) && (
|
||||
<NavItem className={classNames(`p-2 px-lg-0 cursor-pointer`, { [`d-${navbarBreakPoint}-none`]: isCombo })}>
|
||||
<NavLink tag={Link} to="/changelog" id="changelog">
|
||||
<FontAwesomeIcon icon="code-branch" transform="right-6 grow-4" />
|
||||
</NavLink>
|
||||
<UncontrolledTooltip autohide={false} placement="left" target="changelog">
|
||||
Changelog
|
||||
</UncontrolledTooltip>
|
||||
</NavItem>
|
||||
)}
|
||||
{/* <CartNotification /> */}
|
||||
|
||||
<NotificationDropdown />
|
||||
|
||||
<ProfileDropdown />
|
||||
</Nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopNavRightSideNavItem;
|
||||
|
|
@ -3,11 +3,11 @@ import { Collapse, Navbar, NavItem, Nav } from 'reactstrap';
|
|||
import classNames from 'classnames';
|
||||
import AppContext from '../../context/Context';
|
||||
import Logo from './Logo';
|
||||
//import SearchBox from './SearchBox';
|
||||
import SearchBox from './SearchBox';
|
||||
import TopNavRightSideNavItem from './TopNavRightSideNavItem';
|
||||
//import NavbarTopDropDownMenus from './NavbarTopDropDownMenus';
|
||||
import NavbarTopDropDownMenus from './NavbarTopDropDownMenus';
|
||||
import { navbarBreakPoint, topNavbarBreakpoint } from '../../config';
|
||||
//import autoCompleteInitialItem from '../../data/autocomplete/autocomplete';
|
||||
import autoCompleteInitialItem from '../../data/autocomplete/autocomplete';
|
||||
|
||||
const NavbarTop = () => {
|
||||
const {
|
||||
|
|
@ -26,7 +26,7 @@ const NavbarTop = () => {
|
|||
return (
|
||||
<Navbar
|
||||
light
|
||||
className="navbar-glass fs--1 font-weight-semi-bold row navbar-top sticky-kit mb-3 d-flex"
|
||||
className="navbar-glass fs--1 font-weight-semi-bold row navbar-top sticky-kit"
|
||||
expand={isTopNav && topNavbarBreakpoint}
|
||||
>
|
||||
<div
|
||||
|
|
@ -45,8 +45,8 @@ const NavbarTop = () => {
|
|||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<Logo at="navbar-top" id="topLogo" width={180} />
|
||||
{/* {isTopNav ? (
|
||||
<Logo at="navbar-top" width={40} id="topLogo" />
|
||||
{isTopNav ? (
|
||||
<Collapse navbar isOpen={navbarCollapsed} className="scrollbar">
|
||||
<Nav navbar>
|
||||
<NavbarTopDropDownMenus setNavbarCollapsed={setNavbarCollapsed} />
|
||||
|
|
@ -58,7 +58,7 @@ const NavbarTop = () => {
|
|||
<SearchBox autoCompleteItem={autoCompleteInitialItem} />
|
||||
</NavItem>
|
||||
</Nav>
|
||||
)} */}
|
||||
)}
|
||||
|
||||
<TopNavRightSideNavItem />
|
||||
</Navbar>
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ const NavbarVertical = ({ navbarStyle }) => {
|
|||
light
|
||||
>
|
||||
<Flex align="center">
|
||||
{/* <ToggleButton
|
||||
<ToggleButton
|
||||
isNavbarVerticalCollapsed={isNavbarVerticalCollapsed}
|
||||
setIsNavbarVerticalCollapsed={setIsNavbarVerticalCollapsed}
|
||||
/> */}
|
||||
<Logo at="navbar-vertical" width={250} />
|
||||
/>
|
||||
<Logo at="navbar-vertical" width={40} />
|
||||
</Flex>
|
||||
|
||||
<Collapse
|
||||
|
|
@ -87,7 +87,7 @@ const NavbarVertical = ({ navbarStyle }) => {
|
|||
}
|
||||
}
|
||||
>
|
||||
<Nav navbar vertical className="mt-3">
|
||||
<Nav navbar vertical>
|
||||
<NavbarVerticalMenu routes={routes} />
|
||||
</Nav>
|
||||
<div className="settings px-3 px-xl-0">
|
||||
|
|
@ -101,7 +101,7 @@ const NavbarVertical = ({ navbarStyle }) => {
|
|||
</Nav>
|
||||
</div>
|
||||
)}
|
||||
{/* <div className="navbar-vertical-divider">
|
||||
<div className="navbar-vertical-divider">
|
||||
<hr className="navbar-vertical-hr my-2" />
|
||||
</div>
|
||||
<Button
|
||||
|
|
@ -114,7 +114,7 @@ const NavbarVertical = ({ navbarStyle }) => {
|
|||
className="my-3 btn-purchase"
|
||||
>
|
||||
Purchase
|
||||
</Button> */}
|
||||
</Button>
|
||||
</div>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ const NotificationDropdown = () => {
|
|||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
className="mx-3"
|
||||
isOpen={isOpen}
|
||||
toggle={handleToggle}
|
||||
onMouseOver={() => {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,14 @@
|
|||
//import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DropdownItem, DropdownMenu, DropdownToggle, Dropdown } from 'reactstrap';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import team3 from '../../assets/img/team/3.jpg';
|
||||
import Avatar from '../common/Avatar';
|
||||
|
||||
const ProfileDropdown = () => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const toggle = () => setDropdownOpen(prevState => !prevState);
|
||||
const {currentUser, setCurrentUser} = useAuth();
|
||||
const history = useHistory();
|
||||
//const [cookies, setCookie, removeCookie] = useCookies(['remember_token']);
|
||||
|
||||
const handleLogout = () => {
|
||||
//setCurrentUser(null);
|
||||
//localStorage.setItem('user', null);
|
||||
// removeCookie("remember_token", {
|
||||
// domain: ".jamkazam.local"
|
||||
// });
|
||||
// history.push('/authentication/basic/logout');
|
||||
console.log("signout...");
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
// <Avatar src={team3} />
|
||||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
|
|
@ -40,11 +24,11 @@ const ProfileDropdown = () => {
|
|||
}}
|
||||
>
|
||||
<DropdownToggle nav className="pr-0">
|
||||
{currentUser && currentUser.name}
|
||||
<Avatar src={team3} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card">
|
||||
<div className="bg-white rounded-soft py-2">
|
||||
{/* <DropdownItem className="font-weight-bold text-warning" href="#!">
|
||||
<DropdownItem className="font-weight-bold text-warning" href="#!">
|
||||
<FontAwesomeIcon icon="crown" className="mr-1" />
|
||||
<span>Go Pro</span>
|
||||
</DropdownItem>
|
||||
|
|
@ -57,11 +41,8 @@ const ProfileDropdown = () => {
|
|||
<DropdownItem divider />
|
||||
<DropdownItem tag={Link} to="/pages/settings">
|
||||
Settings
|
||||
</DropdownItem> */}
|
||||
<DropdownItem tag={Link} to="/pages/settings">
|
||||
My Profile
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={handleLogout}>
|
||||
</DropdownItem>
|
||||
<DropdownItem tag={Link} to="/authentication/basic/logout">
|
||||
Logout
|
||||
</DropdownItem>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,30 +1,36 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Navbar, Nav, NavItem, NavLink, NavbarText } from 'reactstrap';
|
||||
// import ProfileDropdown from './ProfileDropdown';
|
||||
// import NotificationDropdown from './NotificationDropdown';
|
||||
// import SettingsAnimatedIcon from './SettingsAnimatedIcon';
|
||||
// import CartNotification from './CartNotification';
|
||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
// import { Link } from 'react-router-dom';
|
||||
import { Nav, NavItem, NavLink, UncontrolledTooltip } from 'reactstrap';
|
||||
import ProfileDropdown from './ProfileDropdown';
|
||||
import NotificationDropdown from './NotificationDropdown';
|
||||
import SettingsAnimatedIcon from './SettingsAnimatedIcon';
|
||||
import CartNotification from './CartNotification';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Link } from 'react-router-dom';
|
||||
import AppContext from '../../context/Context';
|
||||
// import classNames from 'classnames';
|
||||
// import { navbarBreakPoint } from '../../config';
|
||||
import JKNavbarTopProfile from './JKNavbarTopProfile';
|
||||
import classNames from 'classnames';
|
||||
import { navbarBreakPoint } from '../../config';
|
||||
|
||||
const TopNavRightSideNavItem = () => {
|
||||
const { isTopNav, isCombo } = useContext(AppContext);
|
||||
return (
|
||||
<Navbar expand="md" className="ml-auto">
|
||||
<Nav className="align-items-center" navbar>
|
||||
<NavbarText className="d-none d-md-inline">Keep JamKazam Improving:</NavbarText>
|
||||
<NavItem className="d-none d-md-inline mr-5">
|
||||
<NavLink>Subscribe</NavLink>
|
||||
<Nav navbar className="navbar-nav-icons ml-auto flex-row align-items-center">
|
||||
<NavItem>
|
||||
<SettingsAnimatedIcon />
|
||||
</NavItem>
|
||||
{(isCombo || isTopNav) && (
|
||||
<NavItem className={classNames(`p-2 px-lg-0 cursor-pointer`, { [`d-${navbarBreakPoint}-none`]: isCombo })}>
|
||||
<NavLink tag={Link} to="/changelog" id="changelog">
|
||||
<FontAwesomeIcon icon="code-branch" transform="right-6 grow-4" />
|
||||
</NavLink>
|
||||
<UncontrolledTooltip autohide={false} placement="left" target="changelog">
|
||||
Changelog
|
||||
</UncontrolledTooltip>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<JKNavbarTopProfile />
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
)}
|
||||
<CartNotification />
|
||||
<NotificationDropdown />
|
||||
<ProfileDropdown />
|
||||
</Nav>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar'
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { acceptFriendRequest } from '../../store/features/peopleSlice';
|
||||
import { Button } from 'reactstrap';
|
||||
|
||||
function JKFriendRequestNotification(props) {
|
||||
const { formatted_msg, created_at, friend_request_id } = props.notification;
|
||||
const handleOnAccept = props.handleOnAccept;
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const handleClick = async event => {
|
||||
event.stopPropagation();
|
||||
const options = {
|
||||
userId: currentUser.id,
|
||||
friend_request_id: friend_request_id,
|
||||
status: 'accept'
|
||||
};
|
||||
|
||||
dispatch(acceptFriendRequest(options))
|
||||
.unwrap()
|
||||
.then((response) => {
|
||||
handleOnAccept()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
<ProfileAvatar />
|
||||
</div>
|
||||
<div className="notification-body">
|
||||
<p className="mb-1">{formatted_msg}</p>
|
||||
<span className="notification-time">
|
||||
<TimeAgo date={created_at} />
|
||||
</span>
|
||||
<div>
|
||||
<Button
|
||||
color="secondary"
|
||||
size="sm"
|
||||
outline={true}
|
||||
className="fs--1 px-2 py-1 mr-1"
|
||||
onClick={handleClick}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKFriendRequestNotification;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react'
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar'
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
|
||||
const JKGenericNotification = (notification) => {
|
||||
|
||||
const {formatted_msg, created_at} = notification;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
<ProfileAvatar />
|
||||
</div>
|
||||
<div className="notification-body">
|
||||
<p className="mb-1">{formatted_msg}</p>
|
||||
<span className="notification-time">
|
||||
<TimeAgo date={created_at} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default JKGenericNotification
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { removeNotification } from '../../store/features/notificationSlice';
|
||||
|
||||
|
||||
import JKGenericNotification from './JKGenericNotification';
|
||||
import JKFriendRequestNotification from './JKFriendRequestNotification';
|
||||
import TextMessageNotification from './JKTextMessageNotification';
|
||||
|
||||
function JKNotification(props) {
|
||||
|
||||
const { description, notification_id } = props.notification
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleOnAccept = () => {
|
||||
deleteNotification();
|
||||
}
|
||||
|
||||
const deleteNotification = async () => {
|
||||
const options = {
|
||||
userId: currentUser.id,
|
||||
notificationId: notification_id
|
||||
}
|
||||
console.log('before removeNotification', options);
|
||||
await dispatch(removeNotification(options))
|
||||
.unwrap()
|
||||
.then(resp => {
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const NOTIFICATION_TYPES = {
|
||||
TEXT_MESSAGE: 'TEXT_MESSAGE',
|
||||
FRIEND_REQUEST: 'FRIEND_REQUEST'
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
switch (description) {
|
||||
case NOTIFICATION_TYPES.TEXT_MESSAGE:
|
||||
return <TextMessageNotification notification={props.notification } handleOnAccept={handleOnAccept} />;
|
||||
break;
|
||||
case NOTIFICATION_TYPES.FRIEND_REQUEST:
|
||||
return <JKFriendRequestNotification notification={props.notification} handleOnAccept={handleOnAccept} />;
|
||||
break;
|
||||
|
||||
default:
|
||||
return <JKGenericNotification {...props.notification} />;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (<a className="notification bg-200">{render()}</a>);
|
||||
|
||||
// <Link className={classNames('notification', { 'bg-200': unread, 'notification-flush': flush }, className)} to={to}>
|
||||
// {avatar && (
|
||||
// <div className="notification-avatar">
|
||||
// <Avatar {...avatar} className="mr-3" />
|
||||
// </div>
|
||||
// )}
|
||||
// <div className="notification-body">
|
||||
// <p className={emoji ? 'mb-1' : 'mb-0'} dangerouslySetInnerHTML={createMarkup(children)} />
|
||||
// <span className="notification-time">
|
||||
// {emoji && (
|
||||
// <span className="mr-1" role="img" aria-label="Emoji">
|
||||
// {emoji}
|
||||
// </span>
|
||||
// )}
|
||||
// {time}
|
||||
// </span>
|
||||
// </div>
|
||||
// </Link>
|
||||
};
|
||||
|
||||
// JKNotification.propTypes = {
|
||||
// // to: PropTypes.string.isRequired,
|
||||
// // avatar: PropTypes.shape(Avatar.propTypes),
|
||||
// // time: PropTypes.string.isRequired,
|
||||
// // className: PropTypes.string,
|
||||
// // unread: PropTypes.bool,
|
||||
// // flush: PropTypes.bool,
|
||||
// // emoji: PropTypes.string,
|
||||
// // children: PropTypes.node
|
||||
// };
|
||||
|
||||
//JKNotification.defaultProps = { unread: false, flush: false };
|
||||
|
||||
export default JKNotification;
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { selectPersonById, fetchPerson } from '../../store/features/peopleSlice';
|
||||
import JKMessageButton from '../profile/JKMessageButton';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import ProfileAvatar from '../profile/JKProfileAvatar';
|
||||
import TimeAgo from '../common/JKTimeAgo';
|
||||
|
||||
function JKTextMessageNotification(props) {
|
||||
const { source_user, source_user_id, message, created_at } = props.notification;
|
||||
const handleOnAccept = props.handleOnAccept;
|
||||
|
||||
const { currentUser } = useAuth();
|
||||
//const [user, setUser] = useState(null);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const user = useSelector(state => state.people.people.find(person => person.id === source_user_id));
|
||||
|
||||
// const user = useSelector(selectPersonById)
|
||||
|
||||
const loadSourceUser = () => {
|
||||
if (!user) {
|
||||
dispatch(fetchPerson({ userId: source_user_id }))
|
||||
.unwrap()
|
||||
.then(resp => {
|
||||
console.log('after fetch person', resp);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadSourceUser();
|
||||
return () => {
|
||||
//cleanup
|
||||
};
|
||||
}, []);
|
||||
|
||||
const truncate = msg => {
|
||||
if (msg.length <= 200) {
|
||||
return msg;
|
||||
} else {
|
||||
return `${msg.substring(0, 200)}...`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="notification-avatar mr-3">
|
||||
<ProfileAvatar />
|
||||
</div>
|
||||
<div className="notification-body">
|
||||
<p className="mb-1">
|
||||
{source_user.name} said: {truncate(message)}
|
||||
</p>
|
||||
<span className="notification-time">
|
||||
<TimeAgo date={created_at} />
|
||||
</span>
|
||||
<div>
|
||||
{user && (
|
||||
<JKMessageButton
|
||||
currentUser={currentUser}
|
||||
user={user}
|
||||
cssClasses="fs--1 px-2 py-1 mr-1"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
outline={true}
|
||||
>
|
||||
<span>Reply</span>
|
||||
</JKMessageButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default JKTextMessageNotification;
|
||||
|
|
@ -29,39 +29,6 @@ const JKPeople = ({ className }) => {
|
|||
const totalPages = useSelector(state => state.people.totalPages)
|
||||
const loadingStatus = useSelector(state => state.people.status)
|
||||
|
||||
// const fetchPeople = React.useCallback(page => {
|
||||
// //getMusicians(page)
|
||||
// console.log("PAGE", page);
|
||||
// getPeople({ page: page })
|
||||
// .then(response => {
|
||||
// if (!response.ok) {
|
||||
// //TODO: handle failure
|
||||
// //console.log(response);
|
||||
// throw new Error('Network response was not ok');
|
||||
// }
|
||||
// return response.json();
|
||||
// })
|
||||
// .then(data => {
|
||||
// console.log('PEOPLE', data.musicians);
|
||||
// //const users = new Set([...people, ...data.musicians]);
|
||||
// //console.log("new users", users);
|
||||
// //setPeople(Array.from(users));
|
||||
|
||||
// setPeople(prev => Array.from(new Set([...prev, ...data.musicians])))
|
||||
|
||||
// setTotalPages(data.page_count);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// //TODO: handle error
|
||||
// console.log(error);
|
||||
// })
|
||||
// .finally(() => {
|
||||
// setLoading(false);
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
|
||||
|
||||
const loadPeople = React.useCallback(page => {
|
||||
try {
|
||||
dispatch(fetchPeople({page}))
|
||||
|
|
@ -71,14 +38,6 @@ const JKPeople = ({ className }) => {
|
|||
|
||||
}, [page]);
|
||||
|
||||
// const loadPeople = (page) => {
|
||||
// try {
|
||||
// dispatch(fetchPeople({page}))
|
||||
// } catch (error) {
|
||||
// console.log('Error fetching people', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
loadPeople(page);
|
||||
}, [page]);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const JKConnectButton = (props) => {
|
|||
const buttonTitle = () => {
|
||||
let title;
|
||||
if (pendingFriendRequest) {
|
||||
title = 'You have sent a friend request to this user';
|
||||
title = 'Thre is a pending friend request';
|
||||
} else if (!isFriend) {
|
||||
title = 'Send friend request';
|
||||
} else if (isFriend) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Button, Tooltip } from "reactstrap";
|
|||
import JKMessageModal from './JKMessageModal';
|
||||
|
||||
const JKMessageButton = props => {
|
||||
const { currentUser, user, cssClasses, children, size } = props;
|
||||
const { currentUser, user, cssClasses, children, size, color, outline } = props;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isFriend, setIsFriend] = useState(false);
|
||||
const [pendingFriendRequest, setPendingFriendRequest] = useState(false);
|
||||
|
|
@ -27,8 +27,9 @@ const JKMessageButton = props => {
|
|||
<Button
|
||||
id={"text-message-user-" + user.id}
|
||||
onClick={() => setShowModal(!showModal)}
|
||||
color="primary"
|
||||
color={color}
|
||||
size={size}
|
||||
outline={outline}
|
||||
className={cssClasses}
|
||||
data-testid="message"
|
||||
disabled={!isFriend || pendingFriendRequest}
|
||||
|
|
@ -49,4 +50,10 @@ const JKMessageButton = props => {
|
|||
);
|
||||
};
|
||||
|
||||
JKMessageButton.defaultProps = {
|
||||
color: 'primary',
|
||||
size: 'sm',
|
||||
outline: false
|
||||
};
|
||||
|
||||
export default JKMessageButton;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Modal, ModalHeader, ModalBody, Row, Col, Button, ModalFooter, Alert } from 'reactstrap';
|
||||
import { Scrollbar } from 'react-scrollbars-custom';
|
||||
import TimeAgo from 'react-timeago';
|
||||
//import TimeAgo from 'react-timeago';
|
||||
import TimeAgo from '../common/JKTimeAgo'
|
||||
import JKProfileAvatar from './JKProfileAvatar';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
|
@ -49,15 +50,16 @@ const JKMessageModal = props => {
|
|||
}
|
||||
};
|
||||
|
||||
const sendMessage = async () => {
|
||||
try {
|
||||
let msgData = {
|
||||
message: newMessage,
|
||||
target_user_id: user.id,
|
||||
source_user_id: currentUser.id
|
||||
};
|
||||
setNewMessage('');
|
||||
await dispatch(postNewMessage(msgData)).unwrap();
|
||||
const sendMessage = () => {
|
||||
let msgData = {
|
||||
message: newMessage,
|
||||
target_user_id: user.id,
|
||||
source_user_id: currentUser.id
|
||||
};
|
||||
setNewMessage('');
|
||||
|
||||
try {
|
||||
dispatch(postNewMessage(msgData));
|
||||
} catch (err) {
|
||||
console.log('addNewMessage error', err);
|
||||
} finally {
|
||||
|
|
@ -153,19 +155,7 @@ const JKMessageModal = props => {
|
|||
<div>
|
||||
<strong>{message.senderName}</strong>
|
||||
<time className="notification-time ml-2 t-1">
|
||||
<TimeAgo
|
||||
date={message.createdAt}
|
||||
formatter={(value, unit) => {
|
||||
if (unit === 'second' && value < 15) return 'just now';
|
||||
if (unit === 'second') return 'few seconds ago';
|
||||
if (unit === 'minute') return `${value} ${value === 1 ? 'minute' : 'minutes'} ago`;
|
||||
if (unit === 'hour') return `${value} ${value === 1 ? 'hour' : 'hours'} ago`;
|
||||
if (unit === 'day') return `${value} ${value === 1 ? 'day' : 'days'} ago`;
|
||||
if (unit === 'week') return `${value} ${value === 1 ? 'week' : 'weeks'} ago`;
|
||||
if (unit === 'month') return `${value} ${value === 1 ? 'month' : 'months'} ago`;
|
||||
if (unit === 'year') return `${value} ${value === 1 ? 'year' : 'years'} ago`;
|
||||
}}
|
||||
/>
|
||||
<TimeAgo date={message.createdAt} />
|
||||
</time>
|
||||
</div>
|
||||
<div>{message.message}</div>
|
||||
|
|
|
|||
|
|
@ -106,4 +106,26 @@ export const getNotifications = (userId, options = {}) => {
|
|||
.catch(error => reject(error))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export const acceptFriendRequest = (userId, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { status, friend_request_id } = options
|
||||
apiFetch(`/users/${userId}/friend_requests/${friend_request_id}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ status })
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteNotification = (userId, notificationId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${userId}/notifications/${notificationId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
|
||||
import JKDashboardLoadingIndicator from '../components/dashboard/JKDashboardLoadingIndicator';
|
||||
import JKLoginRequest from '../components/auth/JKLoginRequest';
|
||||
import JKDashboard from '../components/dashboard/JKDashboard';
|
||||
import DashboardLoadingIndicator from '../components/dashboard/JKDashboardLoadingIndicator';
|
||||
import LoginRequest from '../components/auth/JKLoginRequest';
|
||||
import Dashboard from '../components/dashboard/JKDashboard';
|
||||
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { getCurrentUser } from '../helpers/rest';
|
||||
|
|
@ -15,7 +15,7 @@ const AUTH_STAGES = {
|
|||
unauthenticated: 3
|
||||
};
|
||||
|
||||
const JKDashboardLayout = ({ location }) => {
|
||||
const DashboardLayout = ({ location }) => {
|
||||
|
||||
const { setCurrentUser } = useAuth();
|
||||
const [stage, setStage] = useState(AUTH_STAGES['loading']);
|
||||
|
|
@ -48,11 +48,11 @@ const JKDashboardLayout = ({ location }) => {
|
|||
|
||||
switch (stage) {
|
||||
case AUTH_STAGES['authenticated']:
|
||||
return <JKDashboard />;
|
||||
return <Dashboard />;
|
||||
case AUTH_STAGES['unauthenticated']:
|
||||
return <JKLoginRequest />;
|
||||
return <LoginRequest />;
|
||||
default:
|
||||
return <JKDashboardLoadingIndicator />;
|
||||
return <DashboardLoadingIndicator />;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -71,6 +71,6 @@ const ProtectedRoute = ({ component: Component, ...rest }) => {
|
|||
);
|
||||
};
|
||||
|
||||
JKDashboardLayout.propTypes = { location: PropTypes.object.isRequired };
|
||||
DashboardLayout.propTypes = { location: PropTypes.object.isRequired };
|
||||
|
||||
export default JKDashboardLayout;
|
||||
export default DashboardLayout;
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
|||
import { toast, ToastContainer } from 'react-toastify';
|
||||
import { CloseButton, Fade } from '../components/common/Toast';
|
||||
|
||||
import JKDashboardLayout from './JKDashboardLayout';
|
||||
import DashboardLayout from './JKDashboardLayout';
|
||||
import ErrorLayout from './ErrorLayout';
|
||||
|
||||
|
||||
const JKLayout = () => {
|
||||
const Layout = () => {
|
||||
return (
|
||||
<Router fallback={<span />}>
|
||||
<Switch>
|
||||
<Route path="/errors" component={ErrorLayout} />
|
||||
<Route component={JKDashboardLayout} />
|
||||
<Route component={DashboardLayout} />
|
||||
</Switch>
|
||||
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_LEFT} />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKLayout;
|
||||
export default Layout;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import {getNotifications, deleteNotification} from '../../helpers/rest'
|
||||
|
||||
const initialState = {
|
||||
notifications: [],
|
||||
status: 'idel',
|
||||
error: null
|
||||
}
|
||||
|
||||
export const fetchNotifications = createAsyncThunk(
|
||||
'notifications/fetch',
|
||||
async (options) => {
|
||||
const { userId, ...rest } = options
|
||||
const response = await getNotifications(userId, rest)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const removeNotification = createAsyncThunk(
|
||||
'notifications/delete',
|
||||
async (options) => {
|
||||
const {userId, notificationId} = options
|
||||
const response = await deleteNotification(userId, notificationId)
|
||||
if (response.status == 204) return {}
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const notificationsSlice = createSlice({
|
||||
name: 'notifications',
|
||||
initialState,
|
||||
reducers: {
|
||||
add: (state, action) => {
|
||||
state.notifications.unshift(action.payload)
|
||||
},
|
||||
modify: state => {},
|
||||
remove: (state, action) => {
|
||||
state.notifications = state.notifications.filter(n => n.id !== action.payload)
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchNotifications.pending, (state, action) => {
|
||||
state.status = 'loading'
|
||||
})
|
||||
.addCase(fetchNotifications.fulfilled, (state, action) => {
|
||||
const records = new Set([...state.notifications, ...action.payload]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(a => a.notification_id === x.notification_id).length > 0 ? null : unique.push(x))
|
||||
state.notifications = unique
|
||||
state.status = 'succeeded'
|
||||
})
|
||||
.addCase(fetchNotifications.rejected, (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.error.message
|
||||
})
|
||||
.addCase(removeNotification.fulfilled, (state, action) => {
|
||||
const notificationId = action.meta.arg.notificationId;
|
||||
console.log('removeNotification.fulfilled', notificationId);
|
||||
state.notifications = state.notifications.filter(n => n.notification_id !== notificationId)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const { add } = notificationsSlice.actions;
|
||||
export default notificationsSlice.reducer;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
|
||||
import { getPeople } from '../../helpers/rest';
|
||||
import { getPeople, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
|
||||
|
||||
const initialState = {
|
||||
people: [],
|
||||
|
|
@ -11,13 +11,30 @@ const initialState = {
|
|||
export const fetchPeople = createAsyncThunk(
|
||||
'people/fetchPeople',
|
||||
async (options, thunkAPI) => {
|
||||
//const { page, data } = options
|
||||
console.log('redux fetch', options);
|
||||
const response = await getPeople(options)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export const fetchPerson = createAsyncThunk(
|
||||
'people/fetchPerson',
|
||||
async (options, thunkAPI) => {
|
||||
const {userId} = options
|
||||
const response = await getPersonById(userId)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const acceptFriendRequest = createAsyncThunk(
|
||||
'people/acceptFriendRequest',
|
||||
async(options) => {
|
||||
const { userId, ...rest } = options
|
||||
const response = await accept(userId, rest)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
||||
export const peopleSlice = createSlice({
|
||||
name: 'people',
|
||||
initialState,
|
||||
|
|
@ -39,7 +56,16 @@ export const peopleSlice = createSlice({
|
|||
state.status = 'failed'
|
||||
state.error = action.error.message
|
||||
})
|
||||
.addCase(acceptFriendRequest.fulfilled, (state, action) => {
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const selectPersonById = (state, userId) => state.people.find((person) => person.id === userId)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default peopleSlice.reducer;
|
||||
|
|
@ -73,7 +73,7 @@ export const textMessageSlice = createSlice({
|
|||
state.error = action.error.message
|
||||
})
|
||||
.addCase(postNewMessage.fulfilled, (state, action) => {
|
||||
console.log("postNewMessage fullfilled", action.payload);
|
||||
//console.log("postNewMessage fullfilled", action.payload);
|
||||
state.messages.push(resturectureTextMessage({ message: action.payload, sent: true }))
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { configureStore } from "@reduxjs/toolkit"
|
||||
import textMessageReducer from "./features/textMessagesSlice"
|
||||
import peopleSlice from "./features/peopleSlice"
|
||||
import notificationSlice from './features/notificationSlice'
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
textMessage: textMessageReducer,
|
||||
people: peopleSlice
|
||||
people: peopleSlice,
|
||||
notification: notificationSlice
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue