initial release of my friends page
This commit is contained in:
parent
703309aa65
commit
20eb17b044
|
|
@ -0,0 +1,607 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
const showSidePanelContent = () => {
|
||||||
|
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
|
||||||
|
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => {
|
||||||
|
cy.contains('Location: Denver, CO, US')
|
||||||
|
.and('contain', 'Skill Level: Professional')
|
||||||
|
.and('contain', 'Joined JamKazam: 08-26-2021')
|
||||||
|
.and('contain', 'Last Active:')
|
||||||
|
.and('contain', 'Latency To Me:');
|
||||||
|
cy.get('.latency-badge').contains('UNKNOWN');
|
||||||
|
|
||||||
|
cy.get('[data-testid=biography]').contains('Biography of Test User1');
|
||||||
|
|
||||||
|
//instruments
|
||||||
|
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
|
||||||
|
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
|
||||||
|
|
||||||
|
//genres
|
||||||
|
cy.get('[data-testid=genres]').contains('classical, blues');
|
||||||
|
|
||||||
|
//bands
|
||||||
|
cy.get('[data-testid=bands]').contains('The Band');
|
||||||
|
|
||||||
|
//performance_samples
|
||||||
|
cy.get('[data-testid=performance_samples]').contains('Test Recording1')//.should('have.attr', 'href').and('eq', 'https://www.jamkazam.com/test-recording1');
|
||||||
|
|
||||||
|
//online presence
|
||||||
|
cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser');
|
||||||
|
cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser');
|
||||||
|
cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser');
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeSidePanel = () => {
|
||||||
|
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
describe.only('Friends page without data', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.stubAuthenticate();
|
||||||
|
// cy.intercept('POST', /\S+\/filter/, {
|
||||||
|
// musicians: []
|
||||||
|
// }).as("getPeople_empty");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows no records found alert', () => {
|
||||||
|
cy.visit('/friends/my');
|
||||||
|
|
||||||
|
//default api call with from_location parameter turned off
|
||||||
|
// cy.wait('@getPeople_empty')
|
||||||
|
// .then(interception => {
|
||||||
|
// assert.isNotNull(interception.response.body, '1st API call');
|
||||||
|
// })
|
||||||
|
// .its('request.body')
|
||||||
|
// .should('deep.contain', {
|
||||||
|
// from_location: false
|
||||||
|
// });
|
||||||
|
|
||||||
|
// //now it automatically turns on from_location parameter and fetches again
|
||||||
|
// cy.wait('@getPeople_empty')
|
||||||
|
// .then(interception => {
|
||||||
|
// assert.isNotNull(interception.response.body, '2nd API call - (prefetched)');
|
||||||
|
// })
|
||||||
|
// .its('request.body')
|
||||||
|
// .should('deep.contain', {
|
||||||
|
// from_location: true
|
||||||
|
// });
|
||||||
|
|
||||||
|
// cy.contains('No Records!');
|
||||||
|
|
||||||
|
// cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
// cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').check();
|
||||||
|
// cy.wait(1000);
|
||||||
|
// cy.get('[data-testid=btnSubmitSearch]').click();
|
||||||
|
|
||||||
|
// //default api call with from_location parameter turned on
|
||||||
|
// cy.wait('@getPeople_empty')
|
||||||
|
// .then(interception => {
|
||||||
|
// assert.isNotNull(interception.response.body, '3th API call');
|
||||||
|
// })
|
||||||
|
// .its('request.body')
|
||||||
|
// .should('deep.contain', {
|
||||||
|
// from_location: true
|
||||||
|
// });
|
||||||
|
|
||||||
|
// cy.contains('No Records!');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Friends page with data', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
|
||||||
|
cy.intercept('POST', /\S+\/filter\?offset=0/, { fixture: 'people_page1' }).as('getPeople_page1');
|
||||||
|
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' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('listing users', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/friends');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in desktop', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport('macbook-13');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('paginate', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show profiles', () => {
|
||||||
|
cy.contains('Find New Friends').should('exist');
|
||||||
|
cy.contains('Update Search').should('exist');
|
||||||
|
cy.contains('Reset Filters').should('exist');
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.should('have.length', 10)
|
||||||
|
.first()
|
||||||
|
.contains('Test User1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click profile name', () => {
|
||||||
|
//open side panel by clicking name
|
||||||
|
cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => {
|
||||||
|
cy.contains("Test User1").click()
|
||||||
|
})
|
||||||
|
//cy.get('[data-testid=peopleListTable]').find('.person-link').first().click()
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click more button', () => {
|
||||||
|
//open side panel by clicking more button
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=btnMore]')
|
||||||
|
.click();
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click more link', () => {
|
||||||
|
//open side panel by clicking more link
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=linkMore]')
|
||||||
|
.click();
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click description more link', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.first()
|
||||||
|
.find('td[data-testid=biography-col]')
|
||||||
|
.within(() => {
|
||||||
|
cy.contains('More').click();
|
||||||
|
});
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click instruments more link', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=instrumentList]')
|
||||||
|
.within(() => {
|
||||||
|
cy.get('[data-testid=instrument]').should('have.length', 4); //show only 4 instruments plus more link
|
||||||
|
cy.contains('Acoustic Guitar: Expert');
|
||||||
|
cy.contains('Keyboard: Expert');
|
||||||
|
cy.contains('Ukulele: Expert');
|
||||||
|
cy.contains('Voice: Expert');
|
||||||
|
cy.contains('More').click();
|
||||||
|
});
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click genres more link', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.first()
|
||||||
|
.find('td[data-testid=genres-col]')
|
||||||
|
.within(() => {
|
||||||
|
cy.contains('More').click();
|
||||||
|
});
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in mobile', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport('iphone-6');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show profile', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 10);
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.eq(0)
|
||||||
|
.contains('Test User1');
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.eq(0)
|
||||||
|
.should('be.visible');
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.eq(2)
|
||||||
|
.should('not.be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show all profile description', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=mobBiography]')
|
||||||
|
.should('not.contain', 'More');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show all instruments', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=instrumentList] div')
|
||||||
|
.its('length')
|
||||||
|
.should('be.gte', 1);
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=instrumentList]')
|
||||||
|
.should('not.contain', 'More');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show all genres', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=genreList] div')
|
||||||
|
.its('length')
|
||||||
|
.should('be.gte', 1);
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=genreList]')
|
||||||
|
.should('not.contain', 'More');
|
||||||
|
});
|
||||||
|
|
||||||
|
//it.skip('click connect button', () => {});
|
||||||
|
|
||||||
|
//it.skip('click message button', () => {});
|
||||||
|
|
||||||
|
it.skip('paginate', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
|
||||||
|
for (let i = 0; i < 19; i++) {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
|
||||||
|
cy.wait(500);
|
||||||
|
}
|
||||||
|
cy.wait(500);
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click more button', () => {
|
||||||
|
cy.get('[data-testid=peopleSwiper] .swiper-slide')
|
||||||
|
.first()
|
||||||
|
.find('[data-testid=btnMore]')
|
||||||
|
.click();
|
||||||
|
showSidePanelContent();
|
||||||
|
closeSidePanel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('making friendship', () => {
|
||||||
|
it('add friend', () => {
|
||||||
|
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
|
||||||
|
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
|
||||||
|
|
||||||
|
cy.visit('/friends');
|
||||||
|
cy.contains('Test User2').click();
|
||||||
|
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=connect]')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
// cy.get('[data-testid=confirmFriendRequestModal]').within(() => {
|
||||||
|
// cy.contains('Send a friend request to Test User2');
|
||||||
|
// cy.contains('Yes').click();
|
||||||
|
// });
|
||||||
|
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=connect]')
|
||||||
|
.should('be.disabled');
|
||||||
|
cy.contains('Success! Your friend request has been sent to Test User2.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remove friend', () => {
|
||||||
|
cy.intercept('GET', /\S+\/profile/, { fixture: 'friend' });
|
||||||
|
cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } });
|
||||||
|
|
||||||
|
cy.visit('/friends');
|
||||||
|
cy.contains('Test User1').click();
|
||||||
|
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=disconnect]')
|
||||||
|
.should('exist')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=disconnect]')
|
||||||
|
.should('not.exist');
|
||||||
|
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=connect]')
|
||||||
|
.should('be.exist')
|
||||||
|
.should('not.be.disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('chat window', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/friends');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is not disabled for friends', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(0)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.trigger('mouseover');
|
||||||
|
cy.contains('Send a message').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is not disabled for non friends', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(1)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.should('not.be.disabled');
|
||||||
|
//cy.contains('You can message this user once you are friends').should('exist')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lists text messages', () => {
|
||||||
|
//initially show the most recent messages on openning chat window modal
|
||||||
|
let numberOfMessages = 10;
|
||||||
|
cy.fixture('text_messages_page1').then(json => {
|
||||||
|
cy.intercept('GET', /\S+\/text_messages\S+/, json).as('getTextMessages');
|
||||||
|
});
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(0)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.click();
|
||||||
|
cy.wait('@getTextMessages');
|
||||||
|
cy.get('[data-testid=textMessageModal]')
|
||||||
|
.should('be.visible')
|
||||||
|
.within(() => {
|
||||||
|
cy.get('.text-message-row').should('have.length', numberOfMessages);
|
||||||
|
|
||||||
|
//display previous messages by scrolling up
|
||||||
|
const messageFixtures = ['text_messages_page2', 'text_messages_page3'];
|
||||||
|
messageFixtures.forEach(fixture => {
|
||||||
|
cy.fixture(fixture).then(json => {
|
||||||
|
cy.intercept('GET', /\S+\/text_messages\S+/, json);
|
||||||
|
cy.get('.modal-body .ScrollbarsCustom-Scroller')
|
||||||
|
.trigger('mouseover')
|
||||||
|
.scrollTo('bottom');
|
||||||
|
cy.get('.modal-body .ScrollbarsCustom-Scroller')
|
||||||
|
.trigger('mouseover')
|
||||||
|
.scrollTo('top');
|
||||||
|
numberOfMessages = numberOfMessages + 10;
|
||||||
|
cy.get('.text-message-row').should('have.length', numberOfMessages);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Close')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends message by clicking send button', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(0)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=textMessageModal]').within(() => {
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Send')
|
||||||
|
.should('be.disabled');
|
||||||
|
cy.get('textarea').type('Hello');
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Send')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
cy.get('textarea').should('have.value', '');
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Send')
|
||||||
|
.should('be.disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends message by pressing enter key', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(0)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=textMessageModal]').within(() => {
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Send')
|
||||||
|
.should('be.disabled');
|
||||||
|
cy.get('textarea').type('Hello{enter}');
|
||||||
|
cy.get('textarea').should('have.value', '');
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Send')
|
||||||
|
.should('be.disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('goes away by clicking close button', () => {
|
||||||
|
cy.get('[data-testid=peopleListTable] > tbody tr')
|
||||||
|
.eq(0)
|
||||||
|
.find('[data-testid=message]')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=textMessageModal]').within(() => {
|
||||||
|
cy.get('button')
|
||||||
|
.contains('Close')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('shows received message by other user', () => {
|
||||||
|
//TODO: this should be test in e2e test
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('coming from email links', () => {
|
||||||
|
it("opens details sidebar", () => {
|
||||||
|
cy.visit('/friends?open=details&id=1');
|
||||||
|
showSidePanelContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens chat window", () => {
|
||||||
|
cy.visit('/friends?open=message&id=1');
|
||||||
|
cy.get('[data-testid=textMessageModal]')
|
||||||
|
.should('be.visible')
|
||||||
|
cy.contains('Send Message to Test User1').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends friend request", () => {
|
||||||
|
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
|
||||||
|
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
|
||||||
|
cy.visit('/friends?open=connect&id=1');
|
||||||
|
cy.get('[data-testid=profileSidePanel]')
|
||||||
|
.find('[data-testid=connect]')
|
||||||
|
.should('be.disabled');
|
||||||
|
cy.contains('Success! Your friend request has been sent to Test User1.');
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('filter', () => {
|
||||||
|
const fillFilterForm = () => {
|
||||||
|
//cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=latency_fair]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=latency_high]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_beginner]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_intermediate]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').uncheck();
|
||||||
|
cy.get('#selInstruments')
|
||||||
|
.type('Drums{enter}')
|
||||||
|
.click();
|
||||||
|
cy.get('#selGenres')
|
||||||
|
.type('Pop{enter}')
|
||||||
|
.click();
|
||||||
|
cy.get('#selLastActive').type('Within last 1 Day{enter}');
|
||||||
|
cy.get('#selJoinedWithin').type('Within last 7 Day{enter}');
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/friends');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('open and close filter modal', () => {
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]').should('be.visible');
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]')
|
||||||
|
.contains('Cancel')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]').should('not.be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render filter form', () => {
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]').within(() => {
|
||||||
|
cy.get('input[name=latency_good]')
|
||||||
|
.should('be.checked')
|
||||||
|
.next()
|
||||||
|
.contains('Good (less than 40ms)');
|
||||||
|
cy.get('input[name=latency_fair]')
|
||||||
|
.should('be.checked')
|
||||||
|
.next()
|
||||||
|
.contains('Fair (40-60ms)');
|
||||||
|
cy.get('input[name=latency_high]')
|
||||||
|
.should('not.be.checked')
|
||||||
|
.next()
|
||||||
|
.contains('Poor (more than 60ms)');
|
||||||
|
cy.get('input[name=from_location]')
|
||||||
|
.should('not.be.checked')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reset filters', () => {
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
fillFilterForm();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]')
|
||||||
|
.contains('Cancel')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked');
|
||||||
|
cy.get('[data-testid=modalUpdateSearch]')
|
||||||
|
.contains('Cancel')
|
||||||
|
.click();
|
||||||
|
cy.get('[data-testid=btnResetSearch]').click(); //click reset button
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submit filter form with params', () => {
|
||||||
|
//wait for stubbed request sent to fetch data for initial page load
|
||||||
|
cy.wait('@getPeople_page1').then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '1st API call has data');
|
||||||
|
});
|
||||||
|
//the subsequent request sent to perfetch data and store in redux prefetched buffer
|
||||||
|
cy.wait('@getPeople_page2').then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '1st API call has data - (prefethed)');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('[data-testid=btnSubmitSearch]').click();
|
||||||
|
|
||||||
|
//wait for stubbed request sent by submitting search form without filling any form field
|
||||||
|
cy.wait('@getPeople_page1')
|
||||||
|
.then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '3rd API call has data');
|
||||||
|
})
|
||||||
|
.its('request.body')
|
||||||
|
.should('deep.contain', {
|
||||||
|
latency_good: true,
|
||||||
|
latency_fair: true,
|
||||||
|
latency_high: false,
|
||||||
|
proficiency_beginner: true,
|
||||||
|
proficiency_intermediate: true,
|
||||||
|
proficiency_expert: true,
|
||||||
|
instruments: [],
|
||||||
|
genres: [],
|
||||||
|
from_location: false
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.wait('@getPeople_page2').then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '4th API call has data - (prefethed)');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-testid=btnUpdateSearch]').click();
|
||||||
|
fillFilterForm(); // change filter options
|
||||||
|
cy.get('[data-testid=btnSubmitSearch]').click();
|
||||||
|
|
||||||
|
//wait for stubbed request sent by submitting search form again. but this time fill form fields
|
||||||
|
cy.wait('@getPeople_page1')
|
||||||
|
.then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '5th API call has data');
|
||||||
|
})
|
||||||
|
.its('request.body')
|
||||||
|
.should('deep.contain', {
|
||||||
|
latency_good: false,
|
||||||
|
latency_fair: false,
|
||||||
|
latency_high: false,
|
||||||
|
proficiency_beginner: false,
|
||||||
|
proficiency_intermediate: false,
|
||||||
|
proficiency_expert: false,
|
||||||
|
instruments: [{ value: 'drums', label: 'Drums' }],
|
||||||
|
genres: ['pop'],
|
||||||
|
from_location: false
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.wait('@getPeople_page2').then(interception => {
|
||||||
|
assert.isNotNull(interception.response.body, '6th API call has data - (prefethed)');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
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
|
|
@ -5,4 +5,8 @@
|
||||||
.even-row {
|
.even-row {
|
||||||
border: 1px solid $gray-400;
|
border: 1px solid $gray-400;
|
||||||
background-color: red;
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.borderless td, .borderless th {
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
@ -114,4 +114,13 @@
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.person-row{
|
||||||
|
.offline{
|
||||||
|
color: #cac7c7;
|
||||||
|
&:hover{
|
||||||
|
color: #959090;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import JKHelp from '../page/JKHelp';
|
||||||
import JKPrivacy from '../page/JKPrivacy';
|
import JKPrivacy from '../page/JKPrivacy';
|
||||||
//import JKPeople from '../page/JKPeople';
|
//import JKPeople from '../page/JKPeople';
|
||||||
import JKPeopleFilter from '../page/JKPeopleFilter';
|
import JKPeopleFilter from '../page/JKPeopleFilter';
|
||||||
|
import JKMyFriends from '../page/JKMyFriends';
|
||||||
import JKNotifications from '../page/JKNotifications';
|
import JKNotifications from '../page/JKNotifications';
|
||||||
import JKMessageModal from '../profile/JKMessageModal';
|
import JKMessageModal from '../profile/JKMessageModal';
|
||||||
import JKUnsubscribe from '../page/JKUnsubscribe';
|
import JKUnsubscribe from '../page/JKUnsubscribe';
|
||||||
|
|
@ -255,6 +256,7 @@ function JKDashboardMain() {
|
||||||
<Route path="/privacy" component={JKPrivacy} />
|
<Route path="/privacy" component={JKPrivacy} />
|
||||||
<Route path="/help" component={JKHelp} />
|
<Route path="/help" component={JKHelp} />
|
||||||
<Route path="/unsubscribe" exact component={JKUnsubscribe} />
|
<Route path="/unsubscribe" exact component={JKUnsubscribe} />
|
||||||
|
<PrivateRoute path="/friends/my" component={JKMyFriends} />
|
||||||
<PrivateRoute path="/friends" component={JKPeopleFilter} />
|
<PrivateRoute path="/friends" component={JKPeopleFilter} />
|
||||||
<PrivateRoute path="/sessions/new" component={JKNewMusicSession} />
|
<PrivateRoute path="/sessions/new" component={JKNewMusicSession} />
|
||||||
<PrivateRoute path="/sessions/lobby" component={JKMusicSessionsLobby} />
|
<PrivateRoute path="/sessions/lobby" component={JKMusicSessionsLobby} />
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ function JKMusicSessionsLobby() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loadingStatus === 'succeeded' && onlineMusicians.length > 0) {
|
if (loadingStatus === 'succeeded' && onlineMusicians.length > 0) {
|
||||||
const userIds = onlineMusicians.map(p => p.id);
|
const otherUserIds = onlineMusicians.map(p => p.id);
|
||||||
const options = { currentUserId: currentUser.id, participantIds: userIds };
|
const options = { currentUserId: currentUser.id, otherUserIds };
|
||||||
dispatch(fetchUserLatencies(options));
|
dispatch(fetchUserLatencies(options));
|
||||||
}
|
}
|
||||||
}, [loadingStatus]);
|
}, [loadingStatus]);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { Row, Col, Alert, Card, CardBody } from 'reactstrap';
|
||||||
|
import FalconCardHeader from '../common/FalconCardHeader';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||||
|
import Loader from '../common/Loader';
|
||||||
|
import { isIterableArray } from '../../helpers/utils';
|
||||||
|
import JKFriendsList from '../people/JKFriendsList';
|
||||||
|
import JKFriendsSwiper from '../people/JKFriendsSwiper';
|
||||||
|
import { useAuth } from '../../context/UserAuth';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { getFriends } from '../../helpers/rest';
|
||||||
|
import { filterFriendsByIds, sortFriends } from '../../store/features/friendsSlice';
|
||||||
|
import { fetchUserLatencies } from '../../store/features/latencySlice';
|
||||||
|
|
||||||
|
export const JKMyFriends = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { greaterThan, lessThan } = useResponsive();
|
||||||
|
const friendListRef = useRef();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [showLoadMore, setShowLoadMore] = useState(false);
|
||||||
|
const { currentUser } = useAuth();
|
||||||
|
|
||||||
|
const friends = useSelector(state => state.friend.friends);
|
||||||
|
const loadingStatus = useSelector(state => state.friend.loadingStatus);
|
||||||
|
|
||||||
|
const goNextPage = () => {};
|
||||||
|
|
||||||
|
const sortOptions = [
|
||||||
|
{ value: 'online', label: 'Online' },
|
||||||
|
{ value: 'alphabetical', label: 'Alphabetical' },
|
||||||
|
{ value: 'last_active', label: 'Last Active' }
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser) {
|
||||||
|
getFriends(currentUser.id)
|
||||||
|
.then(async response => {
|
||||||
|
const data = await response.json();
|
||||||
|
const friendIds = data.map(friend => friend.id);
|
||||||
|
//fetch friends with associated data
|
||||||
|
dispatch(filterFriendsByIds({ userId: currentUser.id, ids: friendIds }));
|
||||||
|
//fetch latencies for friends
|
||||||
|
dispatch(fetchUserLatencies({ currentUserId: currentUser.id, otherUserIds: friendIds }));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentUser]);
|
||||||
|
|
||||||
|
const sortResults = e => {
|
||||||
|
const sortValue = e.target.value;
|
||||||
|
dispatch(sortFriends(sortValue));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<FalconCardHeader title={t('page_title', { ns: 'friends' })} titleClass="font-weight-bold">
|
||||||
|
<div className={`d-flex ${ lessThan.sm? 'pt-2' : ''}`} >
|
||||||
|
<span className="mr-2">Sort By:</span>
|
||||||
|
<select onChange={sortResults}>
|
||||||
|
{sortOptions.map(option => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</FalconCardHeader>
|
||||||
|
<CardBody className="pt-0">
|
||||||
|
<>
|
||||||
|
{loadingStatus === 'loading' && friends.length === 0 ? (
|
||||||
|
<Loader />
|
||||||
|
) : isIterableArray(friends) ? (
|
||||||
|
<>
|
||||||
|
{greaterThan.sm ? (
|
||||||
|
<Row className="mb-3 justify-content-between d-none d-md-block">
|
||||||
|
<div className="table-responsive-xl px-2" ref={friendListRef}>
|
||||||
|
<JKFriendsList friends={friends} isLoading={loadingStatus === 'loading'} />
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
) : (
|
||||||
|
<Row className="swiper-container d-block d-md-none" data-testid="friendsSwiper">
|
||||||
|
<JKFriendsSwiper friends={friends} goNextPage={goNextPage} />
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
loadingStatus === 'succeeded' &&
|
||||||
|
friends.length ===
|
||||||
|
0(
|
||||||
|
<Row className="p-card">
|
||||||
|
<Col>
|
||||||
|
<Alert color="info" className="mb-0">
|
||||||
|
No Records!
|
||||||
|
</Alert>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JKMyFriends;
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Alert, Card, CardBody, Col, Row, Button, Form } from 'reactstrap';
|
|
||||||
import Loader from '../common/Loader';
|
|
||||||
import FalconCardHeader from '../common/FalconCardHeader';
|
|
||||||
import { isIterableArray } from '../../helpers/utils';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { fetchPeople } from '../../store/features/peopleSlice';
|
|
||||||
|
|
||||||
import JKPeopleSearch from './JKPeopleSearch';
|
|
||||||
import JKPeopleList from './JKPeopleList';
|
|
||||||
import JKPeopleSwiper from './JKPeopleSwiper';
|
|
||||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
|
||||||
|
|
||||||
const JKPeople = ({ className }) => {
|
|
||||||
const [showSearch, setShowSearch] = useState(false);
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [resetFilter, setResetFilter] = useState(false);
|
|
||||||
const peopleListRef = useRef();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const people = useSelector(state => state.people.people);
|
|
||||||
const totalPages = useSelector(state => state.people.totalPages);
|
|
||||||
const loadingStatus = useSelector(state => state.people.status);
|
|
||||||
|
|
||||||
const { greaterThan } = useResponsive();
|
|
||||||
|
|
||||||
const loadPeople = React.useCallback(() => {
|
|
||||||
if (totalPages !== 0 && page > totalPages) {
|
|
||||||
setPage(totalPages + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
console.log('BEFORE fetching people');
|
|
||||||
dispatch(fetchPeople({ page }));
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Error fetching people', error);
|
|
||||||
}
|
|
||||||
}, [page, totalPages, dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadPeople();
|
|
||||||
}, [page]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (loadingStatus === 'succeeded' && peopleListRef.current && page !== 1) {
|
|
||||||
// }
|
|
||||||
// }, [loadingStatus]);
|
|
||||||
|
|
||||||
const goNextPage = () => {
|
|
||||||
setPage(val => ++val);
|
|
||||||
};
|
|
||||||
|
|
||||||
const goPrevPage = () => {
|
|
||||||
if (page > 1) {
|
|
||||||
setPage(prev => --prev);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
|
||||||
goNextPage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', handleScroll);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<JKPeopleSearch
|
|
||||||
show={showSearch}
|
|
||||||
setShow={setShowSearch}
|
|
||||||
resetFilter={resetFilter}
|
|
||||||
setResetFilter={setResetFilter}
|
|
||||||
/>
|
|
||||||
<FalconCardHeader title={t('page_title', { ns: 'people' })} titleClass="font-weight-bold">
|
|
||||||
<Form inline className="mt-md-0 mt-3">
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={() => setShowSearch(!showSearch)}
|
|
||||||
data-testid="btnUpdateSearch"
|
|
||||||
>
|
|
||||||
{t('update_search', { ns: 'people' })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
outline
|
|
||||||
color="secondary"
|
|
||||||
className="fs--1"
|
|
||||||
data-testid="btnResetSearch"
|
|
||||||
onClick={() => setResetFilter(true)}
|
|
||||||
>
|
|
||||||
{t('reset_filters', { ns: 'people' })}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</FalconCardHeader>
|
|
||||||
|
|
||||||
<CardBody className="pt-0">
|
|
||||||
{loadingStatus === 'loading' && people.length === 0 ? (
|
|
||||||
<Loader />
|
|
||||||
) : isIterableArray(people) ? (
|
|
||||||
//Start Find Friends table hidden on small screens
|
|
||||||
<>
|
|
||||||
{greaterThan.xs ? (
|
|
||||||
<Row className="mb-3 justify-content-between d-none d-md-block">
|
|
||||||
<div className="table-responsive-xl px-2" ref={peopleListRef}>
|
|
||||||
<JKPeopleList people={people} />
|
|
||||||
{loadingStatus === 'loading' && people.length !== 0 && <span>loading...</span>}
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
) : (
|
|
||||||
<Row className="swiper-container d-block d-md-none" data-testid="peopleSwiper">
|
|
||||||
<JKPeopleSwiper people={people} goNextPage={goNextPage} />
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Row className="p-card">
|
|
||||||
<Col>
|
|
||||||
<Alert color="info" className="mb-0">
|
|
||||||
No Records!
|
|
||||||
</Alert>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
JKPeople.propTypes = {
|
|
||||||
className: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
JKPeople.defaultProps = {
|
|
||||||
className: 'col-6 col-md-4 col-lg-3 col-xxl-2'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default JKPeople;
|
|
||||||
|
|
@ -1,372 +0,0 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
|
||||||
import { Button, Card, CardBody, Form, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
|
||||||
import FalconCardHeader from '../common/FalconCardHeader';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import Select from 'react-select';
|
|
||||||
import JKTooltip from '../common/JKTooltip';
|
|
||||||
|
|
||||||
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 JKPeople from './JKPeople';
|
|
||||||
|
|
||||||
function JKPeopleFilter() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [ show, setShow ] = useState(false);
|
|
||||||
const [resetFilter, setResetFilter] = useState(false);
|
|
||||||
const [instruments, setInstruments] = useState([]);
|
|
||||||
const [genres, setGenres] = useState([]);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const pageToRequest = useRef(1)
|
|
||||||
const totalPages = useSelector(state => state.people.totalPages);
|
|
||||||
|
|
||||||
const { register, handleSubmit, setValue, control } = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
latency_good: true,
|
|
||||||
latency_fair: true,
|
|
||||||
latency_high: false,
|
|
||||||
proficiency_beginner: true,
|
|
||||||
proficiency_intermediate: true,
|
|
||||||
proficiency_expert: true,
|
|
||||||
instruments: [],
|
|
||||||
genres: [],
|
|
||||||
joined_within_days: '-1',
|
|
||||||
active_within_days: '-1'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isDirty } = useFormState({ control });
|
|
||||||
|
|
||||||
const toggle = () => setShow(!show);
|
|
||||||
|
|
||||||
const fetchInstruments = async () => {
|
|
||||||
await getInstruments()
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
setInstruments(
|
|
||||||
data.map(instrument => {
|
|
||||||
return {
|
|
||||||
value: instrument.id,
|
|
||||||
label: instrument.description
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(error => console.log(error));
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchGenres = async () => {
|
|
||||||
await getGenres()
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
setGenres(
|
|
||||||
data.map(genre => {
|
|
||||||
return {
|
|
||||||
value: genre.id,
|
|
||||||
label: genre.description
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (resetFilter) {
|
|
||||||
clearFilterOpts();
|
|
||||||
setResetFilter(false);
|
|
||||||
dispatch(resetState());
|
|
||||||
pageToRequest.current = 1
|
|
||||||
handleSubmit(onSubmit)()
|
|
||||||
}
|
|
||||||
}, [resetFilter]);
|
|
||||||
|
|
||||||
|
|
||||||
const clearFilterOpts = () => {
|
|
||||||
setValue('latency_good', true)
|
|
||||||
setValue('latency_fair', true)
|
|
||||||
setValue('latency_high', false)
|
|
||||||
setValue('proficiency_beginner', true)
|
|
||||||
setValue('proficiency_intermediate', true)
|
|
||||||
setValue('proficiency_expert', true)
|
|
||||||
setValue('instruments', null)
|
|
||||||
setValue('genres', null)
|
|
||||||
setValue('joined_within_days', -1)
|
|
||||||
setValue('active_within_days', -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchGenres();
|
|
||||||
fetchInstruments();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const submitForm = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
pageToRequest.current = 1
|
|
||||||
dispatch(resetState());
|
|
||||||
handleSubmit(onSubmit)(pageToRequest.current);
|
|
||||||
setShow(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitPageQuery = (page, hasNextPage) => {
|
|
||||||
handleSubmit(onSubmit)(page, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = (data, page, hasNextPage = true) => {
|
|
||||||
let _page = pageToRequest.current > page ? pageToRequest.current : page
|
|
||||||
|
|
||||||
let genres = [];
|
|
||||||
let joined_within_days = '';
|
|
||||||
let active_within_days = '';
|
|
||||||
|
|
||||||
if (data.genres) {
|
|
||||||
genres = data.genres.map(genre => genre.value);
|
|
||||||
}
|
|
||||||
if(data.joined_within_days){
|
|
||||||
joined_within_days = data.joined_within_days.value;
|
|
||||||
}
|
|
||||||
if(data.active_within_days){
|
|
||||||
active_within_days = data.active_within_days.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = { ...data, genres, joined_within_days, active_within_days };
|
|
||||||
|
|
||||||
console.log("---DEBUG--- _page", _page);
|
|
||||||
console.log("---DEBUG--- totalPages", totalPages);
|
|
||||||
|
|
||||||
try {
|
|
||||||
dispatch(loadPrefetched())
|
|
||||||
if(totalPages && _page > totalPages){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dispatch(fetchPeople({ data: params, page: _page }));
|
|
||||||
pageToRequest.current++
|
|
||||||
if(hasNextPage){
|
|
||||||
dispatch(preFetchPeople({ data: params, page: _page + 1 }))
|
|
||||||
pageToRequest.current = pageToRequest.current + 2
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error fetching people', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const lastActiveOpts = [
|
|
||||||
{ value: '', label: 'Any Range' },
|
|
||||||
{ value: '1', label: 'Within Last 1 Days' },
|
|
||||||
{ value: '7', label: 'Within Last 7 Days' },
|
|
||||||
{ value: '30', label: 'Within Last 30 Days' },
|
|
||||||
{ value: '90', label: 'Within Last 90 Days' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const joinedOpts = [
|
|
||||||
{ value: '', label: 'Any Range' },
|
|
||||||
{ value: '1', label: 'Within Last 1 Days' },
|
|
||||||
{ value: '7', label: 'Within Last 7 Days' },
|
|
||||||
{ value: '30', label: 'Within Last 30 Days' },
|
|
||||||
{ value: '90', label: 'Within Last 90 Days' }
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<FalconCardHeader title={t('page_title', { ns: 'people' })} titleClass="font-weight-bold">
|
|
||||||
<Form inline className="mt-md-0 mt-3">
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={() => setShow(true)}
|
|
||||||
data-testid="btnUpdateSearch"
|
|
||||||
>
|
|
||||||
{t('update_search', { ns: 'people' })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
outline
|
|
||||||
color="secondary"
|
|
||||||
className="fs--1"
|
|
||||||
data-testid="btnResetSearch"
|
|
||||||
onClick={() => setResetFilter(true) }
|
|
||||||
>
|
|
||||||
{t('reset_filters', { ns: 'people' })}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</FalconCardHeader>
|
|
||||||
<CardBody className="pt-0">
|
|
||||||
<Modal
|
|
||||||
isOpen={show}
|
|
||||||
toggle={toggle}
|
|
||||||
className="mw-100 mx-1 mr-1 ml-1 mx-md-5 mr-md-5 ml-md-5 mx-xl-10 mr-xl-10 ml-xl-10"
|
|
||||||
data-testid="modalUpdateSearch"
|
|
||||||
>
|
|
||||||
<ModalHeader toggle={toggle}>Update Search</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<div className="px-4 pb-4">
|
|
||||||
<form>
|
|
||||||
<div className="row justify-content-start mt-2">
|
|
||||||
{/* first column */}
|
|
||||||
<div className="col-12 col-md-6 mb-3 mb-md-0">
|
|
||||||
<div className="row justify-content-start">
|
|
||||||
<div className="col-6">
|
|
||||||
<label className="form-label">
|
|
||||||
Latency{' '}
|
|
||||||
<JKTooltip title="Use these checkboxes to search for other musicians by the estimated amount of latency between you and them. Latency is the amount of time it takes for each of your computers to process audio, plus the time it takes for this digital audio to move over the Internet between you." />
|
|
||||||
</label>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('latency_good')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
defaultChecked={!isDirty}
|
|
||||||
onChange={e => setValue('latency_good', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Good (less than 40ms)</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('latency_fair')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
defaultChecked={!isDirty}
|
|
||||||
onChange={e => setValue('latency_fair', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Fair (40-60ms)</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('latency_high')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
onChange={e => setValue('latency_high', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Poor (more than 60ms)</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-6">
|
|
||||||
<label className="form-label">
|
|
||||||
Skill Level{' '}
|
|
||||||
<JKTooltip title="Use these checkboxes to search for other musicians by their skill level." />
|
|
||||||
</label>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('proficiency_beginner')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
defaultChecked={!isDirty}
|
|
||||||
onChange={e => setValue('proficiency_beginner', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Beginner</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('proficiency_intermediate')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
defaultChecked={!isDirty}
|
|
||||||
onChange={e => setValue('proficiency_intermediate', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Intermediate</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-check">
|
|
||||||
<input
|
|
||||||
{...register('proficiency_expert')}
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
defaultChecked={!isDirty}
|
|
||||||
onChange={e => setValue('proficiency_expert', e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label">Expert</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* second column */}
|
|
||||||
<div className="col-12 col-md-6">
|
|
||||||
<label className="form-label" htmlFor="instruments">
|
|
||||||
Instruments{' '}
|
|
||||||
<JKTooltip title="Use these checkboxes to search for other musicians who play particular instruments. If you do not select any instruments, we search for any/all instruments. If you select multiple instruments, we search for musicians who play any of these instruments." />
|
|
||||||
</label>
|
|
||||||
<div className="choices">
|
|
||||||
<Controller
|
|
||||||
name="instruments"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
options={instruments}
|
|
||||||
isMulti
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
id="selInstruments"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<label className="form-label" htmlFor="genres">
|
|
||||||
Genres{' '}
|
|
||||||
<JKTooltip title="Use these checkboxes to search for other musicians who enjoy playing particular musical genres/styles. If you do not select any genres, we search for any/all genres. If you select multiple genres, we search for musicians who play any of these genres." />
|
|
||||||
</label>
|
|
||||||
<div className="choices">
|
|
||||||
<Controller
|
|
||||||
name="genres"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Select {...field} options={genres} isMulti closeMenuOnSelect={false} id="selGenres" />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<label className="form-label" htmlFor="lastActive">
|
|
||||||
Last Active{' '}
|
|
||||||
<JKTooltip title="Use this list to search for other musicians who have been active on JamKazam within a specified time period. More recent activity makes it more likely they will respond if you message or request to connect." />
|
|
||||||
</label>
|
|
||||||
<div className="choices">
|
|
||||||
<Controller
|
|
||||||
name="active_within_days"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => <Select {...field} options={lastActiveOpts} id="selLastActive" />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<label className="form-label" htmlFor="joined">
|
|
||||||
Joined JamKazam{' '}
|
|
||||||
<JKTooltip title="Use this list to search for other musicians based on when they joined JamKazam. This can be useful for finding and connecting with newer users." />
|
|
||||||
</label>
|
|
||||||
<div className="choices">
|
|
||||||
<Controller
|
|
||||||
name="joined_within_days"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => <Select {...field} options={joinedOpts} id="selJoinedWithin" />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button color="outline-primary" onClick={toggle}>
|
|
||||||
Cancel
|
|
||||||
</Button>{' '}
|
|
||||||
<Button color="primary" onClick={submitForm} data-testid="btnSubmitSearch">
|
|
||||||
Search
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</Modal>
|
|
||||||
<JKPeople onPageChange={submitPageQuery} />
|
|
||||||
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default JKPeopleFilter;
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Table } from 'reactstrap';
|
|
||||||
import JKPerson from './JKPerson';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const JKPeopleList = ({ people }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Table striped bordered className="fs--1" data-testid="peopleListTable">
|
|
||||||
<thead className="bg-200 text-900">
|
|
||||||
<tr>
|
|
||||||
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
|
|
||||||
<th scope="col" style={{ minWidth: 250 }}>
|
|
||||||
{t('person_attributes.about', { ns: 'people' })}
|
|
||||||
</th>
|
|
||||||
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
|
|
||||||
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
|
|
||||||
<th scope="col">{t('actions', { ns: 'common' })}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="list">
|
|
||||||
{people.map((person) => (
|
|
||||||
// <tr className="align-middle" key={`people-list-item-${person.id}`}>
|
|
||||||
<JKPerson person={person} viewMode="list" key={`jk-person-${person.id}`} />
|
|
||||||
// </tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
JKPeopleList.propTypes = {
|
|
||||||
people: PropTypes.arrayOf(PropTypes.instanceOf(Object))
|
|
||||||
};
|
|
||||||
|
|
||||||
export default JKPeopleList;
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Row, Col } from 'reactstrap';
|
import { Row, Col } from 'reactstrap';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
@ -20,13 +20,16 @@ import JKLatencyBadge from '../profile/JKLatencyBadge';
|
||||||
import JKLastActiveAgo from '../profile/JKLastActiveAgo';
|
import JKLastActiveAgo from '../profile/JKLastActiveAgo';
|
||||||
|
|
||||||
const JKPerson = props => {
|
const JKPerson = props => {
|
||||||
const { id, name, biography, photo_url, instruments, genres, latency_data, last_active_timestamp } = props.person;
|
const { id, name, biography, photo_url, instruments, genres, latency_data, last_active_timestamp, online } = props.person;
|
||||||
const { currentUser } = useAuth();
|
const { currentUser } = useAuth();
|
||||||
const [showSidePanel, setShowSidePanel] = useState(false);
|
const [showSidePanel, setShowSidePanel] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const user = useSelector(state => state.people.people.find(p => p.id === id));
|
const user = useSelector(state => state.people.people.find(p => p.id === id));
|
||||||
const { greaterThan } = useResponsive()
|
const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === id));
|
||||||
|
const { greaterThan } = useResponsive();
|
||||||
|
console.log('latency_data', latency_data);
|
||||||
|
console.log('latencyData', latencyData);
|
||||||
|
|
||||||
const toggleMoreDetails = async e => {
|
const toggleMoreDetails = async e => {
|
||||||
if(e)
|
if(e)
|
||||||
|
|
@ -51,14 +54,23 @@ const JKPerson = props => {
|
||||||
toggleMoreDetails()
|
toggleMoreDetails()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const linkClass = useMemo(() => {
|
||||||
|
if (!online) {
|
||||||
|
return `d-flex align-items-center mb-1 fs-0 person-link offline`;
|
||||||
|
}
|
||||||
|
return `d-flex align-items-center mb-1 fs-0 person-link`;
|
||||||
|
}, [online]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{greaterThan.sm ? (
|
{greaterThan.sm ? (
|
||||||
<tr className="align-middle" key={`people-list-item-${id}`}>
|
<tr className="align-middle person-row" key={`people-list-item-${id}`}>
|
||||||
<td className="text-nowrap">
|
<td className="text-nowrap">
|
||||||
<a href="/#" onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0 person-link">
|
<a href="/#" onClick={toggleMoreDetails} className={linkClass}>
|
||||||
<div className="avatar avatar-xl">
|
<div className="avatar avatar-xl">
|
||||||
<JKProfileAvatar url={photo_url} />
|
<JKProfileAvatar url={photo_url} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -67,11 +79,15 @@ const JKPerson = props => {
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div>
|
<div>
|
||||||
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>
|
<strong className='mr-1'>{t('person_attributes.status', { ns: 'people' })}:</strong>
|
||||||
<JKLatencyBadge latencyData={latency_data} />
|
<span>{ online ? 'Online' : 'Offline'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
|
<strong className='mr-1'>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>
|
||||||
|
<JKLatencyBadge latencyData={latency_data || latencyData } />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong className='mr-1'>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
|
||||||
<JKLastActiveAgo timestamp={last_active_timestamp} />
|
<JKLastActiveAgo timestamp={last_active_timestamp} />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -118,11 +134,15 @@ const JKPerson = props => {
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<div>
|
<div>
|
||||||
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
|
<strong className='mr-1'>{t('person_attributes.status', { ns: 'people' })}:</strong>
|
||||||
|
<span>{ online ? 'Online' : 'Offline'}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong className='mr-1'>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
|
||||||
<JKLatencyBadge latencyData={latency_data} />
|
<JKLatencyBadge latencyData={latency_data} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
|
<strong className='mr-1'>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
|
||||||
<JKLastActiveAgo timestamp={last_active_timestamp} />
|
<JKLastActiveAgo timestamp={last_active_timestamp} />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Table, Button } from 'reactstrap';
|
||||||
|
import JKPerson from '../page/JKPerson';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const JKFriendsList = ({ friends, isLoading }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table striped bordered className="fs--1" data-testid="peopleListTable">
|
||||||
|
<thead className="bg-200 text-900">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
|
||||||
|
<th scope="col" style={{ minWidth: 250 }}>
|
||||||
|
{t('person_attributes.about', { ns: 'people' })}
|
||||||
|
</th>
|
||||||
|
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
|
||||||
|
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
|
||||||
|
<th scope="col">{t('actions', { ns: 'common' })}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="list">
|
||||||
|
{friends.map(friend => (
|
||||||
|
<JKPerson person={friend} viewMode="list" key={`jk-person-${friend.id}`} />
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{/* {hasNext && (
|
||||||
|
<Button color="primary" outline={true} onClick={() => goNextPage()} disabled={isLoading} data-testid="paginate-next-page">
|
||||||
|
{isLoading ? <span>Loading...</span> : <span>Load More</span>}
|
||||||
|
</Button>
|
||||||
|
)} */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JKFriendsList.propTypes = {
|
||||||
|
friends: PropTypes.arrayOf(PropTypes.instanceOf(Object)),
|
||||||
|
isLoading: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JKFriendsList;
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// 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 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';
|
||||||
|
|
||||||
|
import JKPerson from '../page/JKPerson';
|
||||||
|
import JKProfileAvatar from '../profile/JKProfileAvatar';
|
||||||
|
|
||||||
|
SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
|
||||||
|
|
||||||
|
const JKFriendsSwiper = ({ friends, goNextPage }) => {
|
||||||
|
|
||||||
|
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'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{friends.map((friend, index) => (
|
||||||
|
<SwiperSlide key={`friends-swiper-item-${friend.id}`}>
|
||||||
|
<Card className="swiper-person-card">
|
||||||
|
<CardHeader className="bg-200">
|
||||||
|
<div className="avatar avatar-xl d-inline-block me-2 mr-2">
|
||||||
|
<JKProfileAvatar url={friend.photo_url} size="xl"/>
|
||||||
|
</div>
|
||||||
|
<h5 className="d-inline-block align-top mt-1">{friend.name}</h5>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<JKPerson person={friend} 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JKFriendsSwiper.propTypes = {
|
||||||
|
friends: PropTypes.arrayOf(PropTypes.instanceOf(Object)),
|
||||||
|
goNextPage: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JKFriendsSwiper;
|
||||||
|
|
@ -12,7 +12,7 @@ const JKConnectButton = props => {
|
||||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(user){
|
if (user) {
|
||||||
setIsFriend(user.is_friend);
|
setIsFriend(user.is_friend);
|
||||||
setPendingFriendRequest(user.pending_friend_request);
|
setPendingFriendRequest(user.pending_friend_request);
|
||||||
}
|
}
|
||||||
|
|
@ -22,16 +22,15 @@ const JKConnectButton = props => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const openWin = queryString.get('open');
|
const openWin = queryString.get('open');
|
||||||
const userId = queryString.get('id')
|
const userId = queryString.get('id');
|
||||||
//sending friend request if directly reqested to do so
|
//sending friend request if directly reqested to do so
|
||||||
//by query string params (coming from weekly new user match email link)
|
//by query string params (coming from weekly new user match email link)
|
||||||
if(openWin && userId && userId === user.id && !user.isFriend && !user.pending_friend_request){
|
if (openWin && userId && userId === user.id && !user.isFriend && !user.pending_friend_request) {
|
||||||
if(openWin === 'connect'){
|
if (openWin === 'connect') {
|
||||||
addFriend();
|
addFriend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const addFriend = () => {
|
const addFriend = () => {
|
||||||
setShowConfirmModal(!showConfirmModal);
|
setShowConfirmModal(!showConfirmModal);
|
||||||
|
|
@ -71,7 +70,7 @@ const JKConnectButton = props => {
|
||||||
const buttonTitle = () => {
|
const buttonTitle = () => {
|
||||||
let title;
|
let title;
|
||||||
if (pendingFriendRequest) {
|
if (pendingFriendRequest) {
|
||||||
title = 'There is a pending friend request';
|
title = 'Delete pending friend request';
|
||||||
} else if (!isFriend) {
|
} else if (!isFriend) {
|
||||||
title = 'Send friend request';
|
title = 'Send friend request';
|
||||||
} else if (isFriend) {
|
} else if (isFriend) {
|
||||||
|
|
@ -101,14 +100,22 @@ const JKConnectButton = props => {
|
||||||
/> */}
|
/> */}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<>
|
||||||
className={`btn btn-primary ${cssClasses}`}
|
<button
|
||||||
data-testid="disconnect"
|
className={`btn btn-primary ${cssClasses}`}
|
||||||
onClick={removeFriend}
|
data-testid="disconnect"
|
||||||
title={buttonTitle()}
|
onClick={() => setShowConfirmModal(!showConfirmModal)}
|
||||||
>
|
title={buttonTitle()}
|
||||||
{removeContent}
|
>
|
||||||
</button>
|
{removeContent}
|
||||||
|
</button>
|
||||||
|
<RemoveConfirmModal
|
||||||
|
show={showConfirmModal}
|
||||||
|
setShow={setShowConfirmModal}
|
||||||
|
user={user}
|
||||||
|
handleOnConfirm={removeFriend}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -133,7 +140,7 @@ JKConnectButton.propTypes = {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// return (
|
// return (
|
||||||
|
|
||||||
// <Modal isOpen={show} toggle={toggle} className={className} centered={true} data-testid="confirmFriendRequestModal">
|
// <Modal isOpen={show} toggle={toggle} className={className} centered={true} data-testid="confirmFriendRequestModal">
|
||||||
// <ModalHeader toggle={toggle}>Send Friend Request</ModalHeader>
|
// <ModalHeader toggle={toggle}>Send Friend Request</ModalHeader>
|
||||||
// <ModalBody>Send a friend request to {user.name}.</ModalBody>
|
// <ModalBody>Send a friend request to {user.name}.</ModalBody>
|
||||||
|
|
@ -146,8 +153,33 @@ JKConnectButton.propTypes = {
|
||||||
// </Button>
|
// </Button>
|
||||||
// </ModalFooter>
|
// </ModalFooter>
|
||||||
// </Modal>
|
// </Modal>
|
||||||
|
|
||||||
// );
|
// );
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
const RemoveConfirmModal = props => {
|
||||||
|
const { className, show, setShow, user, handleOnConfirm } = props;
|
||||||
|
|
||||||
|
const toggle = () => setShow(!show);
|
||||||
|
|
||||||
|
const accept = () => {
|
||||||
|
handleOnConfirm();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={show} toggle={toggle} className={className} centered={true} data-testid="confirmRemoveFriendModal">
|
||||||
|
<ModalHeader toggle={toggle}>Remove Friend</ModalHeader>
|
||||||
|
<ModalBody>Are you sure to remove {user.name} from your friend list?</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={accept}>
|
||||||
|
Yes
|
||||||
|
</Button>{' '}
|
||||||
|
<Button color="secondary" onClick={toggle}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default JKConnectButton;
|
export default JKConnectButton;
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ function JKSession({ session }) {
|
||||||
const { setNativeAppUnavailable } = useNativeApp();
|
const { setNativeAppUnavailable } = useNativeApp();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const participantIds = session.participants.map(p => p.user.id);
|
const otherUserIds = session.participants.map(p => p.user.id);
|
||||||
const options = { currentUserId: currentUser.id, participantIds };
|
const options = { currentUserId: currentUser.id, otherUserIds };
|
||||||
dispatch(fetchUserLatencies(options));
|
dispatch(fetchUserLatencies(options));
|
||||||
}, [session.id]);
|
}, [session.id]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,17 @@ export const getPeople = ({ data, offset, limit } = {}) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPeopleByIds = ({ userId, ids }) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
apiFetch(`/users/${userId}/filter_by_ids`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ user_ids: ids })
|
||||||
|
})
|
||||||
|
.then(response => resolve(response))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getPeopleIndex = () => {
|
export const getPeopleIndex = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
apiFetch(`/users`)
|
apiFetch(`/users`)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { initReactI18next } from "react-i18next";
|
||||||
import commonTranslationsEN from './locales/en/common.json'
|
import commonTranslationsEN from './locales/en/common.json'
|
||||||
import homeTranslationsEN from './locales/en/home.json'
|
import homeTranslationsEN from './locales/en/home.json'
|
||||||
import peopleTranslationsEN from './locales/en/people.json'
|
import peopleTranslationsEN from './locales/en/people.json'
|
||||||
|
import friendsTranslationsEN from './locales/en/friends.json'
|
||||||
import authTranslationsEN from './locales/en/auth.json'
|
import authTranslationsEN from './locales/en/auth.json'
|
||||||
import sessTranslationsEN from './locales/en/sessions.json'
|
import sessTranslationsEN from './locales/en/sessions.json'
|
||||||
import unsubscribeTranslationsEN from './locales/en/unsubscribe.json'
|
import unsubscribeTranslationsEN from './locales/en/unsubscribe.json'
|
||||||
|
|
@ -13,6 +14,7 @@ import accountEN from './locales/en/account.json'
|
||||||
import commonTranslationsES from './locales/es/common.json'
|
import commonTranslationsES from './locales/es/common.json'
|
||||||
import homeTranslationsES from './locales/es/home.json'
|
import homeTranslationsES from './locales/es/home.json'
|
||||||
import peopleTranslationsES from './locales/es/people.json'
|
import peopleTranslationsES from './locales/es/people.json'
|
||||||
|
import friendsTranslationsES from './locales/es/friends.json'
|
||||||
import authTranslationsES from './locales/es/auth.json'
|
import authTranslationsES from './locales/es/auth.json'
|
||||||
import sessTranslationsES from './locales/es/sessions.json'
|
import sessTranslationsES from './locales/es/sessions.json'
|
||||||
import unsubscribeTranslationsES from './locales/es/unsubscribe.json'
|
import unsubscribeTranslationsES from './locales/es/unsubscribe.json'
|
||||||
|
|
@ -32,7 +34,8 @@ i18n.use(initReactI18next).init({
|
||||||
sessions: sessTranslationsEN,
|
sessions: sessTranslationsEN,
|
||||||
unsubscribe: unsubscribeTranslationsEN,
|
unsubscribe: unsubscribeTranslationsEN,
|
||||||
profile: profileEN,
|
profile: profileEN,
|
||||||
account: accountEN
|
account: accountEN,
|
||||||
|
friends: friendsTranslationsEN
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
//translations: require('./locales/es/translations.json')
|
//translations: require('./locales/es/translations.json')
|
||||||
|
|
@ -43,7 +46,8 @@ i18n.use(initReactI18next).init({
|
||||||
sessions: sessTranslationsES,
|
sessions: sessTranslationsES,
|
||||||
unsubscribe: unsubscribeTranslationsES,
|
unsubscribe: unsubscribeTranslationsES,
|
||||||
profile: profileES,
|
profile: profileES,
|
||||||
account: accountES
|
account: accountES,
|
||||||
|
friends: friendsTranslationsES
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//ns: ['translations'],
|
//ns: ['translations'],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"page_title": "My Friends"
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"bands": "Bands",
|
"bands": "Bands",
|
||||||
"performance_samples": "Performance Samples",
|
"performance_samples": "Performance Samples",
|
||||||
"online_presence": "Online Presence",
|
"online_presence": "Online Presence",
|
||||||
"interests": "Interests"
|
"interests": "Interests",
|
||||||
|
"status": "Status"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"page_title": "Mis amigos"
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ export const friendsRoute = {
|
||||||
exact: true,
|
exact: true,
|
||||||
icon: 'users',
|
icon: 'users',
|
||||||
children: [
|
children: [
|
||||||
{ to: '/friends', name: 'My Friends' },
|
{ to: '/friends/my', name: 'My Friends' },
|
||||||
{ to: '/friends/find', name: 'Find Friends' }
|
{ to: '/friends', name: 'Find Friends' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import { getFriends, getPeopleByIds } from '../../helpers/rest'
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
friends: [],
|
||||||
|
status: 'idel',
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchFriends = createAsyncThunk(
|
||||||
|
"friend/fetchFriends",
|
||||||
|
async (options, thunkAPI) => {
|
||||||
|
const { userId } = options
|
||||||
|
const response = await getFriends(userId);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
export const filterFriendsByIds = createAsyncThunk(
|
||||||
|
"friend/filterFriendsByIds",
|
||||||
|
async (options, thunkAPI) => {
|
||||||
|
const { userId, ids } = options;
|
||||||
|
const response = await getPeopleByIds({ userId, ids });
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const FriendSlice = createSlice({
|
||||||
|
name: "friend",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
addFriend: (state) => {},
|
||||||
|
updateFriend: (state) => {},
|
||||||
|
deleteFriend: (state) => {},
|
||||||
|
sortFriends: (state, action) => {
|
||||||
|
console.log('sorting friends', action.payload)
|
||||||
|
const sortValue = action.payload;
|
||||||
|
if(sortValue === 'online'){
|
||||||
|
state.friends = state.friends.sort((a, b) => a.online - b.online).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
} else if(sortValue === 'alphabetical'){
|
||||||
|
state.friends = state.friends.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
} else if(sortValue === 'last_active'){
|
||||||
|
state.friends = state.friends.sort((a, b) => b.last_active_timestamp - a.last_active_timestamp);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(filterFriendsByIds.pending, (state, action) => {
|
||||||
|
state.status = "loading";
|
||||||
|
})
|
||||||
|
.addCase(filterFriendsByIds.fulfilled, (state, action) => {
|
||||||
|
state.status = "succeeded";
|
||||||
|
state.friends = action.payload.musicians.sort((a, b) => a.online - b.online).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
})
|
||||||
|
.addCase(filterFriendsByIds.rejected, (state, action) => {
|
||||||
|
state.status = 'failed'
|
||||||
|
state.error = action.error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { sortFriends } = FriendSlice.actions;
|
||||||
|
export default FriendSlice.reducer;
|
||||||
|
|
@ -10,8 +10,8 @@ const initialState = {
|
||||||
export const fetchUserLatencies = createAsyncThunk(
|
export const fetchUserLatencies = createAsyncThunk(
|
||||||
'latency/fetchUserLatencies',
|
'latency/fetchUserLatencies',
|
||||||
async (options, thunkAPI) => {
|
async (options, thunkAPI) => {
|
||||||
const { currentUserId, participantIds } = options
|
const { currentUserId, otherUserIds } = options
|
||||||
const response = await getLatencyToUsers(currentUserId, participantIds)
|
const response = await getLatencyToUsers(currentUserId, otherUserIds)
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -37,7 +37,6 @@ export const latencySlice = createSlice({
|
||||||
state.status = 'succeeded'
|
state.status = 'succeeded'
|
||||||
})
|
})
|
||||||
.addCase(fetchUserLatencies.rejected, (state, action) => {
|
.addCase(fetchUserLatencies.rejected, (state, action) => {
|
||||||
console.log("_DEBUG_ fail fetchUserLatencies", action.payload);
|
|
||||||
state.status = 'failed'
|
state.status = 'failed'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import onlineMusicianReducer from "./features/onlineMusiciansSlice"
|
||||||
import sessionReducer from "./features/sessionsSlice"
|
import sessionReducer from "./features/sessionsSlice"
|
||||||
import notificationReducer from './features/notificationSlice'
|
import notificationReducer from './features/notificationSlice'
|
||||||
import latencyReducer from "./features/latencySlice"
|
import latencyReducer from "./features/latencySlice"
|
||||||
|
import friendReducer from "./features/friendsSlice"
|
||||||
|
|
||||||
export default configureStore({
|
export default configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
|
|
@ -15,6 +16,7 @@ export default configureStore({
|
||||||
session: sessionReducer,
|
session: sessionReducer,
|
||||||
latency: latencyReducer,
|
latency: latencyReducer,
|
||||||
onlineMusician: onlineMusicianReducer,
|
onlineMusician: onlineMusicianReducer,
|
||||||
lobbyChat: lobbyChatMessagesReducer
|
lobbyChat: lobbyChatMessagesReducer,
|
||||||
|
friend: friendReducer
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -31,6 +31,15 @@ class ApiUsersController < ApiController
|
||||||
respond_with @users, responder: ApiResponder, :status => 200
|
respond_with @users, responder: ApiResponder, :status => 200
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_by_ids
|
||||||
|
user = User.find(params[:id])
|
||||||
|
user_ids = []
|
||||||
|
user_ids = params[:user_ids].split(',') if params[:user_ids]
|
||||||
|
sobj = JamRuby::MusicianSearch.user_search_filter(user)
|
||||||
|
@search = sobj.user_search_results(user_ids)
|
||||||
|
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
|
||||||
|
end
|
||||||
|
|
||||||
def calendar
|
def calendar
|
||||||
#@user=lookup_user
|
#@user=lookup_user
|
||||||
#ics = CalendarManager.new.create_ics_feed(@user)
|
#ics = CalendarManager.new.create_ics_feed(@user)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ object @search
|
||||||
|
|
||||||
node :offset do
|
node :offset do
|
||||||
@nextOffset
|
@nextOffset
|
||||||
end
|
end if @nextOffset
|
||||||
|
|
||||||
# node :page_count do |foo|
|
# node :page_count do |foo|
|
||||||
# @search.page_count
|
# @search.page_count
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,8 @@ Rails.application.routes.draw do
|
||||||
match '/users/:id/broadcast_notification' => 'api_users#broadcast_notification', :via => :get
|
match '/users/:id/broadcast_notification' => 'api_users#broadcast_notification', :via => :get
|
||||||
match '/users/:id/broadcast_notification/:broadcast_id/quiet' => 'api_users#quiet_broadcast_notification', :via => :post
|
match '/users/:id/broadcast_notification/:broadcast_id/quiet' => 'api_users#quiet_broadcast_notification', :via => :post
|
||||||
|
|
||||||
|
match '/users/:id/filter_by_ids' => 'api_users#filter_by_ids', :via => [:post]
|
||||||
|
|
||||||
# session chat
|
# session chat
|
||||||
match '/chat' => 'api_chats#create', :via => :post
|
match '/chat' => 'api_chats#create', :via => :post
|
||||||
match '/chat' => 'api_chats#index', :via => :get
|
match '/chat' => 'api_chats#index', :via => :get
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue