Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Seth Call 2024-10-05 15:54:26 -05:00
commit 4732138cbd
11 changed files with 286 additions and 89 deletions

View File

@ -99,6 +99,10 @@ describe('Friends page with data', () => {
cy.intercept('POST', /\S+\/filter\?offset=10/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('POST', /\S+\/filter\?offset=20/, { fixture: 'people_page3' }).as('getPeople_page3');
cy.intercept('GET', /\S+\/profile/, { fixture: 'person' });
cy.intercept('GET', /\S+\/my_notifications\S+/, {
statusCode: 200,
body: []
});
});
describe('listing users', () => {
@ -116,7 +120,6 @@ describe('Friends page with data', () => {
cy.wait('@getPeople_page2')
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
//cy.get('[data-testid=paginate-next-page]').should('not.exist');
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 30);
cy.get('[data-testid=paginate-next-page]').should('not.exist');

View File

@ -19,7 +19,9 @@ describe('Browse sessions', () => {
});
it('alerts when there is no records', () => {
cy.contains('No Records!');
cy.contains('There are no public, open sessions currently available for you to join');
cy.contains('create a session').click();
cy.url().should('include', '/sessions/new');
});
});

View File

@ -27,6 +27,26 @@ beforeEach(() => {
statusCode: 200,
body: [],
}).as('getAppFeatures');
cy.intercept('GET', /\S+\/users\/\S+\/profile/, {
statusCode: 200,
body: {
id: 1,
name: 'Jane Doe',
email: 'jane@example.com',
},
}).as('getUserProfile');
cy.intercept('GET', /\S+\/users\/\S+\/my_notifications/, {
statusCode: 200,
body: [],
}).as('getMyNotifications');
cy.intercept('GET', /\S+\/users\/\S+\/friends/, {
statusCode: 200,
body: [],
}).as('getMyFriends');
});

View File

@ -9,6 +9,7 @@ import JKGenericNotification from './JKGenericNotification';
import JKFriendRequestNotification from './JKFriendRequestNotification';
import TextMessageNotification from './JKTextMessageNotification';
import classNames from 'classnames';
import { toast } from 'react-toastify';
function JKNotification(props) {
@ -16,11 +17,8 @@ function JKNotification(props) {
const dispatch = useDispatch();
const { currentUser } = useAuth()
const handleOnAccept = () => {
deleteNotification();
}
const deleteNotification = async () => {
const handleOnAccept = async () => {
const options = {
userId: currentUser.id,
notificationId: notification_id
@ -29,13 +27,15 @@ function JKNotification(props) {
await dispatch(removeNotification(options))
.unwrap()
.then(resp => {
toast.success('Friend request accepted');
})
.catch(error => {
toast.error('Error accepting friend request');
})
}
const NOTIFICATION_TYPES = {
TEXT_MESSAGE: 'TEXT_MESSAGE',
FRIEND_REQUEST: 'FRIEND_REQUEST'

View File

@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { Alert, Col, Row, Card, CardBody } from 'reactstrap';
import { Col, Row, Card, CardBody } from 'reactstrap';
import { Link } from 'react-router-dom';
import FalconCardHeader from '../common/FalconCardHeader';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@ -53,9 +54,9 @@ function JKMusicSessions() {
) : (
<Row className="p-card">
<Col>
<Alert color="info" className="mb-0">
{t('no_records', { ns: 'common' })}
</Alert>
{t('list.no_records_1', { ns: 'sessions' })}
<Link to="/sessions/new">{t('list.create_session', { ns: 'sessions' })}</Link>
{t('list.no_records_2', { ns: 'sessions' })}
</Col>
</Row>
)}

View File

@ -14,7 +14,7 @@ import JKPeopleSwiper from './JKPeopleSwiper';
import { getGenres, getInstruments } from '../../helpers/rest';
import { useForm, Controller, useFormState } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPeople, resetState, loadPrefetched, preFetchPeople } from '../../store/features/peopleSlice';
import { fetchPeople, resetState, loadPrefetched, preFetchPeople } from '../../store/features/musiciansSlice';
function JKPeopleFilter() {
const { t } = useTranslation();
@ -35,11 +35,10 @@ function JKPeopleFilter() {
const peopleListRef = useRef();
const people = useSelector(state => state.people.people);
//const hasOffset = useSelector(state => state.people.hasOffset);
const offset = useSelector(state => state.people.offset);
const loadingStatus = useSelector(state => state.people.status);
const prefetched = useSelector(state => state.people.prefetched)
const people = useSelector(state => state.musician.people);
const offset = useSelector(state => state.musician.offset);
const loadingStatus = useSelector(state => state.musician.status);
const prefetched = useSelector(state => state.musician.prefetched)
const { register, handleSubmit, setValue, getValues, control } = useForm({
defaultValues: {

View File

@ -37,7 +37,10 @@
"notes": {
"invited": "YOU WERE INVITED TO THIS SESSION",
"has_friend": "YOU HAVE A FRIEND IN THIS SESSION"
}
},
"no_records_1": "There are no public, open sessions currently available for you to join. We suggest you ",
"create_session": "create a session",
"no_records_2": " that others can join now, or wait a bit and then refresh this page in your browser to see if new public sessions have been started."
},
"lobby": {
"page_title": "Lobby",

View File

@ -0,0 +1,166 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import { getPeople, getPeopleByIds, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
const initialState = {
people: [],
prefetched: [],
status: 'idel',
error: null,
hasOffset: false,
offset: 0
}
export const fetchPeople = createAsyncThunk(
'musician/fetchPeople',
async (options, thunkAPI) => {
const response = await getPeople(options)
return response.json()
}
)
export const preFetchPeople = createAsyncThunk(
'musician/preFetchPeople',
async (options, thunkAPI) => {
const response = await getPeople(options)
return response.json()
}
)
export const fetchPeopleByIds = createAsyncThunk(
'musician/fetchPeopleByIds',
async (options, thunkAPI) => {
const response = await getPeopleByIds(options)
return response.json()
}
)
export const fetchPerson = createAsyncThunk(
'musician/fetchPerson',
async (options, thunkAPI) => {
const {userId} = options
const response = await getPersonById(userId)
return response.json()
}
,{
condition: (options, {getState, extra}) => {
const {people} = getState()
const {userId} = options
const person = people.people.find(person => person.id === userId)
if(person && person.website){
//only proceed if full data set for user has not been fetched. person.website is not included in the initial data fetching (i.e: in friends listing ).
return false;
}
}
}
)
export const acceptFriendRequest = createAsyncThunk(
'musician/acceptFriendRequest',
async(options) => {
const { userId, ...rest } = options
const response = await accept(userId, rest)
return response.json()
}
)
export const musicianSlice = createSlice({
name: 'musician',
initialState,
reducers: {
add: (state, action) => {
state.people.push(action.payload)
},
resetState: (state) => {
return { ...initialState }
},
loadPrefetched: (state, action) => {
if(state.prefetched.length > 0){
const records = [...state.people, ...state.prefetched];
state.people = records;
}
state.prefetched = []
}
},
extraReducers: (builder) => {
builder
.addCase(fetchPeople.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchPeople.fulfilled, (state, action) => {
// const records = [...state.people, ...action.payload.musicians];
// state.people = records
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
//---
const records = new Set([...state.people, ...action.payload.musicians]);
const unique = [];
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
state.people = unique
state.hasOffset = !!action.payload.offset
state.offset = action.payload.offset
state.status = 'succeeded'
})
.addCase(fetchPeople.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(preFetchPeople.pending, (state, action) => {
state.status = 'loading'
})
.addCase(preFetchPeople.fulfilled, (state, action) => {
// const records = [...state.prefetched, ...action.payload.musicians];
// state.prefetched = records
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
//---
const records = new Set([...state.prefetched, ...action.payload.musicians]);
const unique = [];
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
state.people = unique
state.hasOffset = !!action.payload.offset
state.offset = action.payload.offset
state.status = 'succeeded'
})
.addCase(preFetchPeople.rejected, (state, action) => {
state.error = action.error.message
state.status = 'failed'
})
.addCase(acceptFriendRequest.fulfilled, (state, action) => {
})
.addCase(fetchPerson.fulfilled, (state, action) => {
const person = state.people.find(person => person.id === action.payload.id)
if(person){
const updated = {
...person,
...action.payload
}
const objIndex = state.people.findIndex((p => p.id === updated.id));
state.people[objIndex] = updated
}else{
state.people.push(action.payload)
}
})
.addCase(fetchPeopleByIds.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchPeopleByIds.fulfilled, (state, action) => {
const records = new Set([...state.people, ...action.payload.musicians]);
const unique = [];
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
state.people = unique
state.status = 'succeeded'
})
.addCase(fetchPeopleByIds.rejected, (state, action) => {
state.error = action.error.message
state.status = 'failed'
})
}
})
export const selectPersonById = (state, userId) => state.people.find((person) => person.id === userId)
export const { add, resetState, loadPrefetched } = musicianSlice.actions;
export default musicianSlice.reducer;

View File

@ -1,30 +1,31 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import { getPeople, getPeopleByIds, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
//import { getPeople, getPeopleByIds, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
import { getPeopleByIds, getPersonById, acceptFriendRequest as accept } from '../../helpers/rest';
const initialState = {
people: [],
prefetched: [],
//prefetched: [],
status: 'idel',
error: null,
hasOffset: false,
offset: 0
}
export const fetchPeople = createAsyncThunk(
'people/fetchPeople',
async (options, thunkAPI) => {
const response = await getPeople(options)
return response.json()
}
)
// export const fetchPeople = createAsyncThunk(
// 'people/fetchPeople',
// async (options, thunkAPI) => {
// const response = await getPeople(options)
// return response.json()
// }
// )
export const preFetchPeople = createAsyncThunk(
'people/preFetchPeople',
async (options, thunkAPI) => {
const response = await getPeople(options)
return response.json()
}
)
// export const preFetchPeople = createAsyncThunk(
// 'people/preFetchPeople',
// async (options, thunkAPI) => {
// const response = await getPeople(options)
// return response.json()
// }
// )
export const fetchPeopleByIds = createAsyncThunk(
'people/fetchPeopleByIds',
@ -73,60 +74,60 @@ export const peopleSlice = createSlice({
resetState: (state) => {
return { ...initialState }
},
loadPrefetched: (state, action) => {
if(state.prefetched.length > 0){
const records = [...state.people, ...state.prefetched];
state.people = records;
}
state.prefetched = []
}
// loadPrefetched: (state, action) => {
// if(state.prefetched.length > 0){
// const records = [...state.people, ...state.prefetched];
// state.people = records;
// }
// state.prefetched = []
// }
},
extraReducers: (builder) => {
builder
.addCase(fetchPeople.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchPeople.fulfilled, (state, action) => {
// const records = [...state.people, ...action.payload.musicians];
// state.people = records
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
//---
const records = new Set([...state.people, ...action.payload.musicians]);
const unique = [];
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
state.people = unique
state.hasOffset = !!action.payload.offset
state.offset = action.payload.offset
state.status = 'succeeded'
})
.addCase(fetchPeople.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
.addCase(preFetchPeople.pending, (state, action) => {
state.status = 'loading'
})
.addCase(preFetchPeople.fulfilled, (state, action) => {
// const records = [...state.prefetched, ...action.payload.musicians];
// state.prefetched = records
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
//---
const records = new Set([...state.prefetched, ...action.payload.musicians]);
const unique = [];
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
state.people = unique
state.hasOffset = !!action.payload.offset
state.offset = action.payload.offset
state.status = 'succeeded'
})
.addCase(preFetchPeople.rejected, (state, action) => {
state.error = action.error.message
state.status = 'failed'
})
// .addCase(fetchPeople.pending, (state, action) => {
// state.status = 'loading'
// })
// .addCase(fetchPeople.fulfilled, (state, action) => {
// // const records = [...state.people, ...action.payload.musicians];
// // state.people = records
// // state.hasOffset = !!action.payload.offset
// // state.offset = action.payload.offset
// // state.status = 'succeeded'
// //---
// const records = new Set([...state.people, ...action.payload.musicians]);
// const unique = [];
// records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
// state.people = unique
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
// })
// .addCase(fetchPeople.rejected, (state, action) => {
// state.status = 'failed'
// state.error = action.error.message
// })
// .addCase(preFetchPeople.pending, (state, action) => {
// state.status = 'loading'
// })
// .addCase(preFetchPeople.fulfilled, (state, action) => {
// // const records = [...state.prefetched, ...action.payload.musicians];
// // state.prefetched = records
// // state.hasOffset = !!action.payload.offset
// // state.offset = action.payload.offset
// // state.status = 'succeeded'
// //---
// const records = new Set([...state.prefetched, ...action.payload.musicians]);
// const unique = [];
// records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
// state.people = unique
// state.hasOffset = !!action.payload.offset
// state.offset = action.payload.offset
// state.status = 'succeeded'
// })
// .addCase(preFetchPeople.rejected, (state, action) => {
// state.error = action.error.message
// state.status = 'failed'
// })
.addCase(acceptFriendRequest.fulfilled, (state, action) => {
})
.addCase(fetchPerson.fulfilled, (state, action) => {
@ -161,6 +162,6 @@ export const peopleSlice = createSlice({
export const selectPersonById = (state, userId) => state.people.find((person) => person.id === userId)
export const { add, resetState, loadPrefetched } = peopleSlice.actions;
export const { add, resetState } = peopleSlice.actions;
export default peopleSlice.reducer;

View File

@ -2,6 +2,7 @@ import { configureStore } from "@reduxjs/toolkit"
import textMessageReducer from "./features/textMessagesSlice"
import lobbyChatMessagesReducer from "./features/lobbyChatMessagesSlice"
import peopleReducer from "./features/peopleSlice"
import MusicianReducer from "./features/musiciansSlice"
import onlineMusicianReducer from "./features/onlineMusiciansSlice"
import sessionReducer from "./features/sessionsSlice"
import notificationReducer from './features/notificationSlice'
@ -15,6 +16,7 @@ export default configureStore({
reducer: {
textMessage: textMessageReducer,
people: peopleReducer,
musician: MusicianReducer,
notification: notificationReducer,
session: sessionReducer, // this is the slice that holds the currently active sessions
latency: latencyReducer,

View File

@ -545,7 +545,7 @@ class ApiUsersController < ApiController
limit = 20 if limit <= 0
offset = params[:offset].to_i
offset = 0 if offset < 0
query = @notifications = @user.notifications.joins(:source_user)
query = @user.notifications.joins(:source_user)
@unread_total = query.unread.size
@notifications = query.offset(offset).limit(limit)
@next = @notifications.size > 0 ? offset + limit : nil