session lobby with global chat feature

This commit is contained in:
Nuwan 2024-01-02 02:23:21 +05:30
parent 9b72852a54
commit 5a8c85e765
11 changed files with 467 additions and 97 deletions

View File

@ -1 +1 @@
v14.17.1 v14.21.3

View File

@ -3,7 +3,7 @@ import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const JKModalDialog = ({ title, children, show, onToggle }) => { const JKModalDialog = ({ title, children, show, onToggle, showFooter }) => {
const [modal, setModal] = useState(show); const [modal, setModal] = useState(show);
const toggle = () => { const toggle = () => {
@ -21,9 +21,11 @@ const JKModalDialog = ({ title, children, show, onToggle }) => {
<Modal isOpen={modal} toggle={toggle}> <Modal isOpen={modal} toggle={toggle}>
<ModalHeader toggle={toggle}>{title}</ModalHeader> <ModalHeader toggle={toggle}>{title}</ModalHeader>
<ModalBody>{children}</ModalBody> <ModalBody>{children}</ModalBody>
<ModalFooter> {showFooter && (
<Button onClick={toggle}>{t('close', { ns: 'common' })}</Button> <ModalFooter>
</ModalFooter> <Button onClick={toggle}>{t('close', { ns: 'common' })}</Button>
</ModalFooter>
)}
</Modal> </Modal>
); );
}; };
@ -32,12 +34,14 @@ JKModalDialog.propTypes = {
show: PropTypes.bool.isRequired, show: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
onToggle: PropTypes.func.isRequired onToggle: PropTypes.func.isRequired,
showFooter: PropTypes.bool
}; };
JKModalDialog.defaultProps = { JKModalDialog.defaultProps = {
show: false, show: false,
title: 'Modal Dialog' title: 'Modal Dialog',
showFooter: true
}; };
export default JKModalDialog; export default JKModalDialog;

View File

@ -1,26 +1,54 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { Col, Row, Card, CardBody } from 'reactstrap'; import { Col, Row, Card, CardBody, Button, CardHeader, Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useResponsive } from '@farfetch/react-context-responsive'; import { useResponsive } from '@farfetch/react-context-responsive';
import JKLobbyUserList from '../sessions/JKLobbyUserList'; import JKLobbyUserList from '../sessions/JKLobbyUserList';
import JKLobbyUserSwiper from '../sessions/JKLobbyUserSwiper';
import JKLobbyChat from '../sessions/JKLobbyChat'; import JKLobbyChat from '../sessions/JKLobbyChat';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { fetchOnlineMusicians } from '../../store/features/onlineMusiciansSlice'; import { fetchOnlineMusicians } from '../../store/features/onlineMusiciansSlice';
import { fetchUserLatencies } from '../../store/features/latencySlice'; import { fetchUserLatencies } from '../../store/features/latencySlice';
import { useAuth } from '../../context/UserAuth'; import { useAuth } from '../../context/UserAuth';
import { sessionPrivacyMap } from '../../config';
import jkCustomUrlScheme from '../../helpers/jkCustomUrlScheme';
import useNativeAppCheck from '../../hooks/useNativeAppCheck';
import { useNativeApp } from '../../context/NativeAppContext';
import JKModalDialog from '../common/JKModalDialog';
import JKTooltip from '../common/JKTooltip';
function JKMusicSessionsLobby() { function JKMusicSessionsLobby() {
const { t } = useTranslation(); const { t } = useTranslation();
const { greaterThan } = useResponsive(); const { greaterThan } = useResponsive();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { currentUser } = useAuth(); const { currentUser } = useAuth();
const isNativeAppAvailable = useNativeAppCheck();
const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp();
const onlineMusicians = useSelector(state => state.onlineMusician.musicians); const onlineMusicians = useSelector(state => state.onlineMusician.musicians);
const loadingStatus = useSelector(state => state.onlineMusician.status); const loadingStatus = useSelector(state => state.onlineMusician.status);
const [selectedUsers, setSelectedUsers] = useState([]);
const [submitted, setSubmitted] = useState(false);
const [activeTab, setActiveTab] = useState('1');
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
useEffect(() => { useEffect(() => {
dispatch(fetchOnlineMusicians()); dispatch(fetchOnlineMusicians());
//check if browser notifications are enabled
try {
const notificationsEnabled = localStorage.getItem('showLobbyChatNotifications');
const dontAskAgain = localStorage.getItem('dontAskLobbyChatNotificationPermission');
if (notificationsEnabled || dontAskAgain) {
return;
} else {
setTimeout(() => {
if (true) {
setShowNotificationsModal(true);
}
}, 10000);
}
} catch (error) {}
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -31,30 +59,216 @@ function JKMusicSessionsLobby() {
} }
}, [loadingStatus]); }, [loadingStatus]);
const handleClick = async () => {
const payload = {
privacy: sessionPrivacyMap.public,
description: t('list.descriptions.public_open_session', { ns: 'sessions' }),
inviteeIds: selectedUsers
};
try {
//throw new Error('test');
await isNativeAppAvailable();
//window.open jamkazam app url using custom URL scheme
//an example URL would be: jamkazam://url=https://www.jamkazam.com/client#/createSession/privacy~2|description~hello|inviteeIds~1,2,3,4
const q = `privacy~${payload.privacy}|description~${payload.description}|inviteeIds~${payload.inviteeIds}`;
const urlScheme = jkCustomUrlScheme('createSession', q);
setSubmitted(true);
window.open(urlScheme);
//history.push('/sessions');
} catch (error) {
toggleAppUnavilableModel();
} finally {
setSubmitted(false);
}
return false;
};
const toggleAppUnavilableModel = () => {
setNativeAppUnavailable(prev => !prev);
if (!nativeAppUnavailable) {
setSubmitted(false);
}
};
const toggleShowNotificationsModel = () => {
setShowNotificationsModal(prev => !prev);
};
const grantShowNotificationsPermission = () => {
setShowNotificationsModal(false);
try {
if (!window.Notification) {
console.log('Your web browser does not support notifications.');
alert('Your web browser does not support notifications.');
} else {
// check if permission is already granted
if (Notification.permission === 'granted') {
// show notification here
} else {
// request permission from user
Notification.requestPermission()
.then(function(p) {
if (p === 'granted') {
new Notification('Lobby Chat Notifications', {
body: 'Notifications will appear here.',
});
localStorage.setItem('showLobbyChatNotifications', true);
} else {
console.log('User blocked notifications.');
}
})
.catch(function(err) {
console.error(err);
});
}
}
} catch (error) {}
};
const handleDontAsk = e => {
const checked = e.target.checked;
if (!checked) {
return;
}
try {
localStorage.setItem('dontAskLobbyChatNotificationPermission', true);
} catch (error) {}
};
return ( return (
<div> <>
<> <Card>
<Card> <CardHeader className="card-header bg-light mb-2">
<FalconCardHeader title={t('lobby.page_title', { ns: 'sessions' })} titleClass="font-weight-bold" /> <div className="d-flex justify-content-between">
<CardBody className="pt-0"> <div className="d-flex">
{greaterThan.sm ? ( <h5 className="mb-0 mr-1">{t('lobby.page_title', { ns: 'sessions' })}</h5>
<Row className="justify-content-between"> <JKTooltip
<Col> placement="top"
<div className="table-responsive-xl px-2"> title="
<JKLobbyUserList loadingStatus={loadingStatus} onlineMusicians={onlineMusicians} /> The Lobby shows all users who: 1) are currently on this page; or 2) have this page open in a tab in their browser and have given us permission to send browser notifications; or 3) have the JamKazam app running but arent currently in sessions, and have given us permission to send browser notifications. Use the Lobby to see what other users are currently online but not in sessions. You can message other users to see if they want to join a session with you, and use checkboxes plus the Create Session & Invite Selected Users button to get a session started with other Lobby users.
</div> "
</Col> />
<Col> </div>
<JKLobbyChat /> {greaterThan.sm && (
</Col> <div className="align-self-end">
</Row> <Button
) : ( color="primary"
<Row className="swiper-container d-block d-md-none" data-testid="sessionsSwiper" /> className="ml-2"
onClick={handleClick}
disabled={submitted || selectedUsers.length === 0}
>
Create Session & Invite Selected Users
</Button>
</div>
)} )}
</CardBody> </div>
</Card> </CardHeader>
</> <CardBody className="pt-0">
</div> {greaterThan.sm ? (
<Row className="justify-content-between">
<Col>
<div className="table-responsive-xl px-2">
<JKLobbyUserList
loadingStatus={loadingStatus}
onlineMusicians={onlineMusicians}
setSelectedUsers={setSelectedUsers}
/>
</div>
</Col>
<Col>
<JKLobbyChat />
</Col>
</Row>
) : (
<Row className="swiper-container d-block d-md-none" data-testid="sessionsSwiper">
<Nav tabs>
<NavItem>
<NavLink className="active" onClick={() => setActiveTab('1')}>
Users
</NavLink>
</NavItem>
<NavItem>
<NavLink className="" onClick={() => setActiveTab('2')}>
Chat
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<JKLobbyUserSwiper
loadingStatus={loadingStatus}
onlineMusicians={onlineMusicians}
setSelectedUsers={setSelectedUsers}
/>
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="12">
<JKLobbyChat />
</Col>
</Row>
</TabPane>
</TabContent>
</Row>
)}
</CardBody>
</Card>
<JKModalDialog
show={nativeAppUnavailable}
onToggle={toggleAppUnavilableModel}
title={t('modals.native_app_unavailable.title', { ns: 'common' })}
>
<p>{t('modals.native_app_unavailable.body', { ns: 'common' })}</p>
<div className="d-flex flex-row">
<a
href="https://www.jamkazam.com/downloads"
onClick={() => toggleAppUnavilableModel()}
target="_blank"
className="btn btn-primary mr-2"
>
{t('modals.native_app_unavailable.download_button', { ns: 'common' })}
</a>
<a
href="https://www.jamkazam.com/help_desk"
onClick={() => toggleAppUnavilableModel()}
target="_blank"
className="btn btn-light"
>
{t('modals.native_app_unavailable.help_button', { ns: 'common' })}
</a>
</div>
</JKModalDialog>
<JKModalDialog
show={showNotificationsModal}
onToggle={toggleShowNotificationsModel}
title={t('lobby.chat_notifications.title', { ns: 'sessions' })}
showFooter={false}
>
<p>{t('lobby.chat_notifications.body', { ns: 'sessions' })}</p>
<div className="">
<div>
<label htmlFor="">
<input type="checkbox" className="mr-2" onChange={handleDontAsk} />
{t('lobby.chat_notifications.dont_ask_again', { ns: 'sessions' })}
</label>
</div>
<div className="d-flex justify-content-end">
<Button color="primary" className="ml-2" onClick={grantShowNotificationsPermission}>
{t('lobby.chat_notifications.grant_permission', { ns: 'sessions' })}
</Button>
<Button color="secondary" outline={true} className="ml-2" onClick={() => toggleShowNotificationsModel()}>
{t('lobby.chat_notifications.no_thanks', { ns: 'sessions' })}
</Button>
</div>
</div>
</JKModalDialog>
</>
); );
} }

View File

@ -13,12 +13,13 @@ import JKModalDialog from '../common/JKModalDialog';
import useNativeAppCheck from '../../hooks/useNativeAppCheck'; import useNativeAppCheck from '../../hooks/useNativeAppCheck';
import { useNativeApp } from '../../context/NativeAppContext'; import { useNativeApp } from '../../context/NativeAppContext';
import { useResponsive } from '@farfetch/react-context-responsive'; import { useResponsive } from '@farfetch/react-context-responsive';
import { sessionPrivacyMap } from '../../config';
// const privacyMap = {
// public: 1,
// private_invite: 2,
// private_approve: 3
// };
const privacyMap = {
public: 1,
private_invite: 2,
private_approve: 3
};
const JKNewMusicSession = () => { const JKNewMusicSession = () => {
const { currentUser } = useAuth(); const { currentUser } = useAuth();
@ -54,7 +55,6 @@ const JKNewMusicSession = () => {
} }
}) })
.then(data => { .then(data => {
console.log('friends = ', data);
setFriends(data); setFriends(data);
setIsFriendsFetched(true); setIsFriendsFetched(true);
}); });
@ -85,7 +85,6 @@ const JKNewMusicSession = () => {
description: formData.get('description'), description: formData.get('description'),
inviteeIds: invitees.map(i => i.id).join() inviteeIds: invitees.map(i => i.id).join()
}; };
console.log(payload);
try { try {
//store this payload in localstorage. //store this payload in localstorage.
localStorage.setItem('formData', JSON.stringify(payload)); localStorage.setItem('formData', JSON.stringify(payload));
@ -155,11 +154,11 @@ const JKNewMusicSession = () => {
onChange={e => setPrivacy(e.target.value)} onChange={e => setPrivacy(e.target.value)}
data-testid="session-privacy" data-testid="session-privacy"
> >
<option value={privacyMap['public']}>{t('new.privacy_opt_public', { ns: 'sessions' })}</option> <option value={sessionPrivacyMap['public']}>{t('new.privacy_opt_public', { ns: 'sessions' })}</option>
<option value={privacyMap['private_invite']}> <option value={sessionPrivacyMap['private_invite']}>
{t('new.privacy_opt_private_invite', { ns: 'sessions' })} {t('new.privacy_opt_private_invite', { ns: 'sessions' })}
</option> </option>
<option value={privacyMap['private_approve']}> <option value={sessionPrivacyMap['private_approve']}>
{t('new.privacy_opt_private_approve', { ns: 'sessions' })} {t('new.privacy_opt_private_approve', { ns: 'sessions' })}
</option> </option>
</Input> </Input>

View File

@ -6,6 +6,8 @@ import { useAuth } from '../../context/UserAuth';
import JKProfileAvatar from '../profile/JKProfileAvatar'; import JKProfileAvatar from '../profile/JKProfileAvatar';
import TimeAgo from '../common/JKTimeAgo'; import TimeAgo from '../common/JKTimeAgo';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useTranslation } from 'react-i18next';
import { useResponsive } from '@farfetch/react-context-responsive';
import useOnScreen from '../../hooks/useOnScreen'; import useOnScreen from '../../hooks/useOnScreen';
import useKeepScrollPosition from '../../hooks/useKeepScrollPosition'; import useKeepScrollPosition from '../../hooks/useKeepScrollPosition';
@ -17,6 +19,7 @@ function JKLobbyChat() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const messageTextBox = useRef(); const messageTextBox = useRef();
const scrollbar = useRef(); const scrollbar = useRef();
const { greaterThan } = useResponsive();
const scrolledToBottom = useRef(false); const scrolledToBottom = useRef(false);
const { currentUser } = useAuth(); const { currentUser } = useAuth();
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
@ -126,7 +129,9 @@ function JKLobbyChat() {
useEffect(() => { useEffect(() => {
if (createStatus === 'succeeded') { if (createStatus === 'succeeded') {
fetchMessages({ start: 0, limit: 1, lastOnly: true }); // fetchMessages({ start: 0, limit: 1, lastOnly: true });
setMessages([])
fetchMessages({ start: 0, limit: offset === 0 ? LIMIT : offset * LIMIT})
messageTextBox.current.focus(); messageTextBox.current.focus();
} }
}, [createStatus]); }, [createStatus]);
@ -152,12 +157,12 @@ function JKLobbyChat() {
<div className="border pt-1 pl-3 p-2" style={wrapperStyle}> <div className="border pt-1 pl-3 p-2" style={wrapperStyle}>
<div className="lobby-chat" ref={containerRef} style={containerStyle}> <div className="lobby-chat" ref={containerRef} style={containerStyle}>
{messages.map((message, i) => ( {messages.map((message, i) => (
<div className="d-flex mb-3 mr-1 text-message-row" key={message.id}> <div className="d-flex mb-3 mr-1 text-message-row" key={greaterThan ? `desktop_${message.id}` : `mobile_${message.id}`}>
<div ref={ref => (i === 0 ? setLastMessageRef(ref) : null)}> <div className='d-flex align-items-center' ref={ref => (i === 0 ? setLastMessageRef(ref) : null)}>
<div className="avatar avatar-2xl d-inline-block"> <div className="avatar avatar-2xl">
<JKProfileAvatar url={message.user.photo_url} /> <JKProfileAvatar url={message.user.photo_url} />
</div> </div>
<div className="d-inline-block"> <div className="pt-2">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<div> <div>
<strong>{message.user.name}</strong> <strong>{message.user.name}</strong>
@ -171,9 +176,9 @@ function JKLobbyChat() {
)} )}
</div> </div>
<div> <div>
{ message.id }
{message.message} {message.message}
{/* <hr />
{message.id} */}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,24 +1,29 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Row } from 'reactstrap';
import JKProfileAvatar from '../profile/JKProfileAvatar'; import JKProfileAvatar from '../profile/JKProfileAvatar';
import JKConnectButton from '../profile/JKConnectButton'; import JKConnectButton from '../profile/JKConnectButton';
import JKMessageButton from '../profile/JKMessageButton'; import JKMessageButton from '../profile/JKMessageButton';
import JKMoreDetailsButton from '../profile/JKMoreDetailsButton'; import JKMoreDetailsButton from '../profile/JKMoreDetailsButton';
import JKLatencyBadge from '../profile/JKLatencyBadge';
import JKProfileInstrumentsList from '../profile/JKProfileInstrumentsList';
import { useTranslation } from 'react-i18next';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import JKProfileSidePanel from '../profile/JKProfileSidePanel'; import JKProfileSidePanel from '../profile/JKProfileSidePanel';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { fetchPerson } from '../../store/features/peopleSlice'; import { fetchPerson } from '../../store/features/peopleSlice';
import { useResponsive } from '@farfetch/react-context-responsive';
import { useAuth } from '../../context/UserAuth'; import { useAuth } from '../../context/UserAuth';
function JKLobbyUser({ user }) { function JKLobbyUser({ user, setSelectedUsers }) {
const [showSidePanel, setShowSidePanel] = useState(false) const { t } = useTranslation();
const [showSidePanel, setShowSidePanel] = useState(false);
const { currentUser } = useAuth(); const { currentUser } = useAuth();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { greaterThan } = useResponsive();
const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id)); const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id));
const userData = useSelector(state => state.people.people.find(p => p.id === user.id)); const userData = useSelector(state => state.people.people.find(p => p.id === user.id));
const toggleMoreDetails = async (e) => { const toggleMoreDetails = async e => {
e.preventDefault(); e.preventDefault();
try { try {
await dispatch(fetchPerson({ userId: user.id })).unwrap(); await dispatch(fetchPerson({ userId: user.id })).unwrap();
@ -28,43 +33,105 @@ function JKLobbyUser({ user }) {
setShowSidePanel(prev => !prev); setShowSidePanel(prev => !prev);
}; };
const setSelection = e => {
if (e.target.checked) {
setSelectedUsers(prev => [...prev, user.id]);
} else {
setSelectedUsers(prev => prev.filter(u => u !== user.id));
}
};
return ( return (
<> <>
<tr> {greaterThan.sm ? (
<td className="align-middle"> <tr>
<Row className="d-flex flex-row justify-content-between" style={{ alignItems: 'center' }}> <td className="align-middle">
<input type="checkbox" className="align-middle" /> <div className="d-flex">
<div className="avatar avatar-sm"> <div className="mr-2 pt-2">
<a href="/#" onClick={toggleMoreDetails}> <input type="checkbox" className="align-middle" onClick={setSelection} />
<JKProfileAvatar src={user.photo_url} /> </div>
</a> <div className="">
</div> <div className="d-flex align-items-center">
<div className="ml-2 ms-2" style={{ width: '70%' }}> <div className="avatar avatar-sm mr-2">
<a href="/#" onClick={toggleMoreDetails}> <JKProfileAvatar src={user.photo_url} />
{user.name} </div>
</a> <div className="">
</div> <a href="/#" onClick={toggleMoreDetails}>
</Row> <strong>{user.name}</strong>
</td> </a>
<td className="align-middle text-center"> </div>
<JKConnectButton </div>
currentUser={currentUser} <div>
user={user} <strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
addContent={<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />} <JKLatencyBadge latencyData={latencyData} />
removeContent={<FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" />} </div>
cssClasses="fs--1 px-2 py-1 mr-1" <div>
/> <strong>{t('person_attributes.instruments', { ns: 'people' })}</strong>
{/* <JKProfileInstrumentsList instruments={user.instruments} toggleMoreDetails={toggleMoreDetails} /> */}
</div>
</div>
</div>
</td>
<JKMessageButton currentUser={currentUser} user={user} cssClasses="fs--1 px-2 py-1 mr-1"> <td className="align-middle text-center">
<FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" /> <JKConnectButton
</JKMessageButton> currentUser={currentUser}
user={user}
addContent={<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />}
removeContent={<FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" />}
cssClasses="fs--1 px-2 py-1 mr-1"
/>
<JKMoreDetailsButton toggleMoreDetails={toggleMoreDetails} cssClasses="btn btn-primary fs--1 px-2 py-1"> <JKMessageButton currentUser={currentUser} user={user} cssClasses="fs--1 px-2 py-1 mr-1">
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" /> <FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" />
</JKMoreDetailsButton> </JKMessageButton>
</td>
</tr> <JKMoreDetailsButton toggleMoreDetails={toggleMoreDetails} cssClasses="btn btn-primary fs--1 px-2 py-1">
<JKProfileSidePanel user={userData} latencyData={latencyData} show={showSidePanel} setShow={setShowSidePanel} /> <FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</JKMoreDetailsButton>
</td>
</tr>
) : (
<>
<div>
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
<JKLatencyBadge latencyData={latencyData} />
</div>
<div>
<h5>{t('person_attributes.instruments', { ns: 'people' })}</h5>
{/* <JKProfileInstrumentsList instruments={instruments} toggleMoreDetails={toggleMoreDetails} /> */}
</div>
<div>
<h5>{t('person_attributes.genres', { ns: 'people' })}</h5>
{/* <JKProfileGenres genres={genres} toggleMoreDetails={toggleMoreDetails} /> */}
</div>
<br />
<JKConnectButton
currentUser={currentUser}
user={user}
addContent={<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />}
removeContent={<FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" />}
cssClasses="fs--1 px-2 py-1 mr-1"
/>
<JKMessageButton currentUser={currentUser} user={user} cssClasses="fs--1 px-2 py-1 mr-1">
<FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" />
</JKMessageButton>
<a href="/#" onClick={toggleMoreDetails} data-testid="btnMore">
<span
className="btn btn-primary fs--1 px-2 py-1"
data-bs-toggle="tooltip"
title={t('view_profile', { ns: 'people' })}
>
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</span>
</a>
</>
)}
<JKProfileSidePanel user={userData} latencyData={latencyData} show={showSidePanel} setShow={setShowSidePanel} />
</> </>
); );
} }

View File

@ -5,9 +5,9 @@ import JKLobbyUser from './JKLobbyUser';
import { isIterableArray } from '../../helpers/utils'; import { isIterableArray } from '../../helpers/utils';
import Loader from '../common/Loader'; import Loader from '../common/Loader';
function JKLobbyUserList({ loadingStatus, onlineMusicians}) { function JKLobbyUserList({ loadingStatus, onlineMusicians, selectedUsers, setSelectedUsers }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
{loadingStatus === 'loading' && onlineMusicians.length === 0 ? ( {loadingStatus === 'loading' && onlineMusicians.length === 0 ? (
@ -16,7 +16,7 @@ function JKLobbyUserList({ loadingStatus, onlineMusicians}) {
<Table striped bordered className="fs--1" data-testid="sessionsLobbyUsersTable"> <Table striped bordered className="fs--1" data-testid="sessionsLobbyUsersTable">
<thead className="bg-200 text-900"> <thead className="bg-200 text-900">
<tr> <tr>
<th width="35%" scope="col"> <th width="75%" scope="col">
{t('lobby.header.musician', { ns: 'sessions' })} {t('lobby.header.musician', { ns: 'sessions' })}
</th> </th>
<th scope="col" className="text-center"> <th scope="col" className="text-center">
@ -26,7 +26,7 @@ function JKLobbyUserList({ loadingStatus, onlineMusicians}) {
</thead> </thead>
<tbody className="list"> <tbody className="list">
{isIterableArray(onlineMusicians) ? ( {isIterableArray(onlineMusicians) ? (
onlineMusicians.map(musician => <JKLobbyUser key={musician.id} user={musician} />) onlineMusicians.map(musician => <JKLobbyUser key={musician.id} user={musician} setSelectedUsers={setSelectedUsers} />)
) : ( ) : (
<tr> <tr>
<td colSpan={2}>No users currently online.</td> <td colSpan={2}>No users currently online.</td>

View File

@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import JKProfileAvatar from '../profile/JKProfileAvatar';
// import Swiper core and required modules
import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from 'swiper';
// Import Swiper React components
import { Swiper, SwiperSlide } from 'swiper/react';
import JKLobbyUser from '../sessions/JKLobbyUser';
// Import Swiper styles
import 'swiper/swiper.scss';
import 'swiper/components/navigation/navigation.scss';
import 'swiper/components/pagination/pagination.scss';
import 'swiper/components/scrollbar/scrollbar.scss';
import { Card, CardBody, CardHeader } from 'reactstrap';
SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
const JKLobbyUserSwiper = ({ onlineMusicians, setSelectedUsers, loadingStatus }) => {
return (
<>
<Swiper
spaceBetween={0}
slidesPerView={1}
//onSlideChange={() => console.log('slide change')}
onSlideNextTransitionEnd={swiper => {
if(swiper.isEnd){
//goNextPage()
}
}}
pagination={{
clickable: true,
type: 'custom'
}}
navigation={{
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}}
>
{onlineMusicians.map((musician, index) => (
<SwiperSlide key={`session-lobby-swiper-item-${musician.id}`}>
<Card className="swiper-person-card">
<CardHeader className="text-center bg-200">
<div className="avatar avatar-xl d-inline-block me-2 mr-2">
<JKProfileAvatar url={musician.photo_url} size="xl"/>
</div>
<h5 className="d-inline-block align-top mt-1">{musician.name}</h5>
</CardHeader>
<CardBody>
<JKLobbyUser user={musician} setSelectedUsers={setSelectedUsers} viewMode="swipe" />
</CardBody>
</Card>
</SwiperSlide>
))}
</Swiper>
<div className="py-4 px-6 bg-white border-top w-100 fixed-bottom">
<div className="swiper-pagination" />
<div className="swiper-button-prev" />
<div className="swiper-button-next" />
</div>
</>
);
};
JKLobbyUserSwiper.propTypes = {
onlineMusicians: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired,
setSelectedUsers: PropTypes.func.isRequired,
};
export default JKLobbyUserSwiper;

View File

@ -15,4 +15,9 @@ export const settings = {
isNavbarVerticalCollapsed: false, isNavbarVerticalCollapsed: false,
navbarStyle: 'transparent' navbarStyle: 'transparent'
}; };
export default { version, navbarBreakPoint, topNavbarBreakpoint, settings }; export const sessionPrivacyMap = {
public: 1,
private_invite: 2,
private_approve: 3
};
export default { version, navbarBreakPoint, topNavbarBreakpoint, settings, sessionPrivacyMap };

View File

@ -44,6 +44,13 @@
"header": { "header": {
"musician": "Musician", "musician": "Musician",
"actions": "Actions" "actions": "Actions"
},
"chat_notifications": {
"title": "Lobby Chat Notifications",
"body": "To avoid missing lobby chat messages when youre not actively watching this page, you can give us permission to give you browser notifications when this page is open in a tab on your browser, but youre not currently looking at this tab/page",
"dont_ask_again": "Dont ask me again",
"grant_permission": "Grant Permission",
"no_thanks": "Not Now. Thanks"
} }
} }
} }

View File

@ -34,19 +34,13 @@ const chatMessagesSlice = createSlice({
state.status = 'loading'; state.status = 'loading';
}) })
.addCase(fetchLobbyChatMessages.fulfilled, (state, action) => { .addCase(fetchLobbyChatMessages.fulfilled, (state, action) => {
console.log('_DEBUG_1 fetchLobbyChatMessages', action.payload);
//let chats = [...state.records.messages, ...action.payload.chats];
const lastOnly = action.meta.arg.lastOnly; const lastOnly = action.meta.arg.lastOnly;
console.log('_DEBUG_2 fetchLobbyChatMessages', lastOnly);
state.records = { state.records = {
next: state.records.next === null && lastOnly? null : action.payload.next, next: state.records.next === null && lastOnly? null : action.payload.next,
messages: action.payload.chats.map(m => ({...m, status: 'delivered'})).sort((a, b) => { messages: action.payload.chats.map(m => ({...m, status: 'delivered'})).sort((a, b) => {
return new Date(a.created_at) - new Date(b.created_at); return new Date(a.created_at) - new Date(b.created_at);
}) })
}; };
// state.offset_messages = action.payload.chats.sort((a, b) => {
// return new Date(a.created_at) - new Date(b.created_at);
// });
state.status = 'succeeded'; state.status = 'succeeded';
}) })
.addCase(fetchLobbyChatMessages.rejected, (state, action) => { .addCase(fetchLobbyChatMessages.rejected, (state, action) => {