diff --git a/jam-ui/package-lock.json b/jam-ui/package-lock.json index 3244ddf71..75a8fc1ab 100644 --- a/jam-ui/package-lock.json +++ b/jam-ui/package-lock.json @@ -1736,6 +1736,24 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@reduxjs/toolkit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.1.tgz", + "integrity": "sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==", + "requires": { + "immer": "^9.0.1", + "redux": "^4.1.0", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0" + }, + "dependencies": { + "immer": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==" + } + } + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -15328,6 +15346,11 @@ "@babel/runtime": "^7.9.2" } }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15631,6 +15654,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", diff --git a/jam-ui/package.json b/jam-ui/package.json index 020bf7ddb..92d75d58c 100644 --- a/jam-ui/package.json +++ b/jam-ui/package.json @@ -17,6 +17,7 @@ "@fullcalendar/react": "^5.3.1", "@fullcalendar/timegrid": "^5.3.1", "@loadable/component": "^5.13.2", + "@reduxjs/toolkit": "^1.6.1", "attr-accept": "^2.2.2", "bootstrap": "^4.5.3", "chart.js": "^2.9.3", diff --git a/jam-ui/src/App.js b/jam-ui/src/App.js index c89603aa9..a928319b9 100644 --- a/jam-ui/src/App.js +++ b/jam-ui/src/App.js @@ -1,63 +1,16 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import Layout from './layouts/Layout'; +import JKLayout from './layouts/JKLayout'; import 'react-toastify/dist/ReactToastify.min.css'; import 'react-datetime/css/react-datetime.css'; import 'react-image-lightbox/style.css'; -import useScript from './hooks/useScript'; - -import { useDispatch } from "react-redux"; -//import { addMessage } from "./store/features/textMessagesSlice" - const App = () => { - - function initJKScripts() { - const app = window.JK.JamKazam(); - const jamServer = new window.JK.JamServer(app, function(event_type) { - console.log('---EVENT_TYPE---', event_type); - //return app.activeElementEvent(event_type) - }); - jamServer.initialize(); - window.JK.initJamClient(app); - - const clientInit = new window.JK.ClientInit(); - clientInit.init(); - - window.JK.JamServer.connect() // singleton here defined in JamServer.js - .done(function() { - console.log('Jamserver connected'); - //_initAfterConnect(true); - }) - .fail(function() { - console.log('Jamserver connection error'); - //_initAfterConnect(false); - }); - - registerTextMessageCallback(); - - } - - function registerTextMessageCallback(){ - window.JK.JamServer.registerMessageCallback(window.JK.MessageType.TEXT_MESSAGE, function(header, payload) { - console.log('Handling TEXT_MESSAGE ' + JSON.stringify(payload)); - // chatMessageReceived(payload); - // context.ChatActions.msgReceived(payload); - // handledNotification(payload); - - //dispatch(addMessage()) - - }); - } - - const dispatch = useDispatch() - - useScript(`${process.env.REACT_APP_LEGACY_BASE_URL}/client_scripts`, initJKScripts); return ( - + ); }; diff --git a/jam-ui/src/components/auth/JKLoginRequest.js b/jam-ui/src/components/auth/JKLoginRequest.js new file mode 100644 index 000000000..285b35fa4 --- /dev/null +++ b/jam-ui/src/components/auth/JKLoginRequest.js @@ -0,0 +1,29 @@ +import React from 'react' +import { Card, CardBody, Row, Col } from 'reactstrap'; +import Logo from '../navbar/Logo'; +import Section from '../common/Section'; + +function JKLoginRequest() { + return ( +
+ + + + + + +

Signin to begin

+

Please login to your jamkazam account before accessing this interface.

+ + Signin + +
+
+
+ +
+
+ ) +} + +export default JKLoginRequest diff --git a/jam-ui/src/components/dashboard/JKDashboardLoadingIndicator.js b/jam-ui/src/components/dashboard/JKDashboardLoadingIndicator.js new file mode 100644 index 000000000..ffb2eea35 --- /dev/null +++ b/jam-ui/src/components/dashboard/JKDashboardLoadingIndicator.js @@ -0,0 +1,11 @@ +import React from 'react' + +function JKDashboardLoadingIndicator() { + return ( +
+ Loading... +
+ ) +} + +export default JKDashboardLoadingIndicator diff --git a/jam-ui/src/components/dashboard/JkDashboard.js b/jam-ui/src/components/dashboard/JkDashboard.js index fa0adb4b2..15bb2f33d 100644 --- a/jam-ui/src/components/dashboard/JkDashboard.js +++ b/jam-ui/src/components/dashboard/JkDashboard.js @@ -1,10 +1,93 @@ -import React from "react"; +import React, { useContext, useEffect } from 'react'; -const JKDashboard = () => { +import { Route, Switch } from 'react-router-dom'; - return( -

Dashboard

- ) +import NavbarTop from '../navbar/NavbarTop'; +import NavbarVertical from '../navbar/NavbarVertical'; +import JKFooter from '../footer/JKFooter'; + +import AppContext from '../../context/Context'; +import { getPageName } from '../../helpers/utils'; + +import useScript from '../../hooks/useScript'; +import { useDispatch } from "react-redux"; +import { addMessage } from "../../store/features/textMessagesSlice" + +import JKHome from './JkHome'; +import loadable from '@loadable/component'; +const JKDashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes')); + +function JKDashboard() { + const { isFluid, isVertical, navbarStyle } = useContext(AppContext); + const isKanban = getPageName('kanban'); + + useEffect(() => { + JKDashboardRoutes.preload(); + }, []); + + const dispatch = useDispatch() + + const initJKScripts = () => { + const app = window.JK.JamKazam(); + const jamServer = new window.JK.JamServer(app, function(event_type) { + console.log('EVENT_TYPE', event_type); + }); + jamServer.initialize(); + window.JK.initJamClient(app); + const clientInit = new window.JK.ClientInit(); + clientInit.init(); + + window.JK.JamServer.connect() // singleton here defined in JamServer.js + .done(function() { + console.log('Jamserver connected'); + //_initAfterConnect(true); + }) + .fail(function() { + console.log('Jamserver connection error'); + //_initAfterConnect(false); + }); + + registerTextMessageCallback(); + + } + + 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, + receiverId: window.currentUser.id, + receiverName: window.currentUser.first_name, + createdAt: json.created_at, + sent: true + } + + dispatch(addMessage(receivedMsg)) + + }); + } + + useScript(`${process.env.REACT_APP_LEGACY_BASE_URL}/client_scripts`, initJKScripts); + + return ( +
+ {isVertical && } + +
+ + + + + + {!isKanban && } +
+ {/* */} + +
+ ); } -export default JKDashboard; \ No newline at end of file +export default JKDashboard; diff --git a/jam-ui/src/components/dashboard/JkHome.js b/jam-ui/src/components/dashboard/JkHome.js new file mode 100644 index 000000000..c117e97dc --- /dev/null +++ b/jam-ui/src/components/dashboard/JkHome.js @@ -0,0 +1,10 @@ +import React from "react"; + +const JKHome = () => { + + return( +

Dashboard - Home

+ ) +} + +export default JKHome; \ No newline at end of file diff --git a/jam-ui/src/components/footer/JKFooter.js b/jam-ui/src/components/footer/JKFooter.js new file mode 100644 index 000000000..3f14c9ecb --- /dev/null +++ b/jam-ui/src/components/footer/JKFooter.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Col, Row } from 'reactstrap'; +import { version } from '../../config'; + +const JKFooter = () => ( + +); + +export default JKFooter; diff --git a/jam-ui/src/components/profile/JKMessageModal.js b/jam-ui/src/components/profile/JKMessageModal.js index 418c6303d..aba024824 100644 --- a/jam-ui/src/components/profile/JKMessageModal.js +++ b/jam-ui/src/components/profile/JKMessageModal.js @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef } from 'react'; -import { Modal, ModalHeader, ModalBody, Row, Col, Button, ModalFooter } from 'reactstrap'; +import { Modal, ModalHeader, ModalBody, Row, Col, Button, ModalFooter, Badge } from 'reactstrap'; import { Scrollbar } from 'react-scrollbars-custom'; import TimeAgo from 'react-timeago'; import JKProfileAvatar from './JKProfileAvatar'; @@ -11,31 +11,40 @@ const JKMessageModal = props => { const { show, setShow, user } = props; const LIMIT = 20; + const [fetching, setFetching] = useState(false); const [offset, setOffset] = useState(0); const [newMessage, setNewMessage] = useState(''); + const [messagesArrived, setMessagesArrived] = useState(false); const toggle = () => setShow(!show); const { currentUser } = useAuth(); const dispatch = useDispatch(); const scrollbar = useRef(); + const messageTextBox = useRef(); + const scrolledToBottom = useRef(false); const messages = useSelector(state => state.textMessage.messages .filter( message => - (message.senderId === user.id && message.receiverId === currentUser.id) || - (message.senderId === currentUser.id && message.receiverId === user.id) + (message.senderId === user.id && message.receiverId === window.currentUser.id) || + (message.senderId === window.currentUser.id && message.receiverId === user.id) ) - .reverse() + .sort((a, b) => { + return new Date(a.createdAt) - new Date(b.createdAt); + }) ); const fetchMessages = async () => { try { - await dispatch(fetchMessagesByReceiverId(user.id)).unwrap(); - scrollbar.current.scrollToBottom(); + const options = { userId: user.id, offset: offset, limit: LIMIT }; + setFetching(true); + await dispatch(fetchMessagesByReceiverId(options)).unwrap(); } catch (err) { console.log('ERROR', err); + } finally { + setFetching(false); } }; @@ -48,37 +57,102 @@ const JKMessageModal = props => { }; setNewMessage(''); await dispatch(postNewMessage(msgData)).unwrap(); - fetchMessages(); } catch (err) { console.log('addNewMessage error', err); } finally { } }; - const handleOnKeyPress = (event) => { - //console.log('event', event.key); - if(event.key === 'Enter' || event.key === "NumpadEnter"){ - sendMessage() + const handleOnKeyPress = event => { + if (event.key === 'Enter' || event.key === 'NumpadEnter') { + event.preventDefault(); + sendMessage(); + event.target.value = ''; } - } + }; + + const scrollAtTop = () => { + return scrollbar.current.scrollTop === 0; + }; + + const scrollAtMiddle = () => { + return scrollbar.current.scrollHeight - scrollbar.current.clientHeight > scrollbar.current.scrollTop; + }; + + const scrollAtBottom = () => { + return scrollbar.current.scrollHeight - scrollbar.current.clientHeight === scrollbar.current.scrollTop; + }; + + const goToBottom = () => { + if (scrollbar && scrollbar.current) { + scrollbar.current.scrollToBottom(); + } + }; useEffect(() => { - if (show) { - //console.log('JKMessageModal User', user.id); + if (show && messages.length === 0) { fetchMessages(); } - }, [show, dispatch]); + }, [show]); + + + useEffect(() => { + if (scrollbar && scrollbar.current) { + if (!fetching && !scrollAtTop()) { + if (messages[messages.length - 1]['senderId'] !== currentUser.id) { + if (!scrolledToBottom.current) { + setMessagesArrived(true); + } else { + goToBottom(); + } + } else { + goToBottom(); + } + } + } + }, [messages]); + + useEffect(() => { + if (!messagesArrived) { + setMessagesArrived(false); + goToBottom(); + } + }, [messagesArrived]); + + const handleScrollStop = scrollValues => { + scrolledToBottom.current = false; + if (scrollValues.scrollTop === 0) { + setOffset(prev => prev + 1); + } else if (scrollValues.scrollTop === scrollValues.scrollHeight - scrollValues.clientHeight) { + scrolledToBottom.current = true; + setMessagesArrived(false); + } + }; + + useEffect(() => { + if (offset !== 0) { + fetchMessages(); + } + }, [offset]); return ( <> Conversation with {user.name} - + {messages.map((message, index) => (
- +
@@ -93,6 +167,9 @@ const JKMessageModal = props => { 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`; }} /> @@ -103,9 +180,24 @@ const JKMessageModal = props => {
))} + {messagesArrived && ( + + + + + + )} -