wip - react components related to musician listing and filtering

This commit is contained in:
Nuwan Chathuranga 2021-08-26 22:53:24 +05:30 committed by Nuwan
parent eb4c327eff
commit 6c88cd1215
54 changed files with 1799 additions and 459 deletions

View File

@ -0,0 +1,79 @@
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"location": "Denver, CO",
"online": true,
"photo_url": null,
"musician": true,
"gender": "M",
"birth_date": null,
"friend_count": 1,
"liker_count": 0,
"follower_count": 0,
"following_count": 0,
"recording_count": 0,
"session_count": 0,
"biography": "Biography of Test User1",
"favorite_count": 0,
"audio_latency": null,
"upcoming_session_count": 0,
"age": null,
"website": "www.testuser1.com",
"skill_level": 2,
"concert_count": 4,
"studio_session_count": 4,
"virtual_band": true,
"virtual_band_commitment": 2,
"traditional_band": true,
"traditional_band_commitment": 4,
"traditional_band_touring": true,
"paid_sessions": true,
"paid_sessions_hourly_rate": 10000,
"paid_sessions_daily_rate": 200000,
"free_sessions": true,
"cowriting": true,
"cowriting_purpose": 2,
"subscribe_email": true,
"is_a_teacher": false,
"is_a_student": false,
"online_presences": [
{ "id": "e1962204-f652-41b0-84d6-1afd7e9172be", "service_type": "soundcloud", "username": "testuser" },
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b0", "service_type": "reverbnation", "username": "testuser" },
{ "id": "2dd22eef-03ba-4743-b65b-5a194591dc86", "service_type": "bandcamp", "username": "testuser" },
{ "id": "d6ae62b4-e1ce-4cf0-90b7-c64033533261", "service_type": "fandalism", "username": "testuser" },
{ "id": "c6e85453-0fa9-40d0-9754-8f372d6e0ed3", "service_type": "youtube", "username": "testuser" },
{ "id": "480ec1ad-ea1d-4990-9c68-d7f9c0174441", "service_type": "facebook", "username": "testuser" },
{ "id": "232b26d5-c75a-4d65-9013-a07b73c8a7ae", "service_type": "twitter", "username": "testuser" }
],
"performance_samples": [],
"genres": [
{ "genre_id": "asian", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "african", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "traditional_band" },
{ "genre_id": "blues", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "soft rock", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "celtic", "player_type": "JamRuby::User", "genre_type": "cowriting" },
{ "genre_id": "tv & movie soundtrack", "player_type": "JamRuby::User", "genre_type": "cowriting" }
],
"bands": [],
"instruments": [
{ "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" },
{ "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" },
{ "description": "Ukulele", "proficiency_level": 3, "priority": 11, "instrument_id": "ukulele" },
{ "description": "Voice", "proficiency_level": 3, "priority": 13, "instrument_id": "voice" },
{ "description": "Piano", "proficiency_level": 2, "priority": 10, "instrument_id": "piano" }
],
"is_friend": true,
"is_following": false,
"is_liking": false,
"pending_friend_request": false,
"my_audio_latency": 5,
"internet_score": null
}

View File

@ -3,32 +3,38 @@
{ {
"id": "1", "id": "1",
"first_name": "Test", "first_name": "Test",
"last_name": "User", "last_name": "User1",
"name": "Test User", "name": "Test User1",
"city": "City", "city": "Denver",
"state": "NC", "state": "CO",
"country": "US", "country": "US",
"online": false, "online": true,
"musician": true, "musician": true,
"photo_url": null, "photo_url": null,
"biography": "", "biography": "Biography of Test User1",
"full_score": null, "full_score": null,
"instruments": [], "instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 },
{ "instrument_id": "keyboard", "description": "Keyboard", "proficiency_level": 3, "priority": 8 },
{ "instrument_id": "ukulele", "description": "Ukulele", "proficiency_level": 3, "priority": 11 },
{ "instrument_id": "voice", "description": "Voice", "proficiency_level": 3, "priority": 13 },
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 }
],
"followings": [], "followings": [],
"is_friend": false, "is_friend": true,
"is_following": false, "is_following": false,
"pending_friend_request": false, "pending_friend_request": false,
"friend_count": 0, "friend_count": 1,
"follow_count": 0, "follow_count": 0,
"recording_count": 0, "recording_count": 0,
"session_count": 0, "session_count": 10,
"audio_latency": null "audio_latency": 5
}, },
{ {
"id": "a09f9a7e-afb7-489d-870d-e13a336e0b97", "id": "2",
"first_name": "Seth", "first_name": "Test",
"last_name": "Call", "last_name": "User2",
"name": "Seth Call", "name": "Test User2",
"city": "Austin", "city": "Austin",
"state": "TX", "state": "TX",
"country": "US", "country": "US",
@ -51,10 +57,10 @@
"audio_latency": null "audio_latency": null
}, },
{ {
"id": "3dfca858-0e7c-4ad4-993a-c39421d93853", "id": "3",
"first_name": "Peter", "first_name": "Test",
"last_name": "Walker", "last_name": "User3",
"name": "Peter Walker", "name": "Test User3",
"city": "Austin", "city": "Austin",
"state": "TX", "state": "TX",
"country": "US", "country": "US",
@ -77,10 +83,10 @@
"audio_latency": null "audio_latency": null
}, },
{ {
"id": "963d5268-66b6-463a-a3ee-c97f274fc23f", "id": "4",
"first_name": "Peter", "first_name": "Test",
"last_name": "Walker", "last_name": "User4",
"name": "Peter Walker", "name": "Test User4",
"city": "Austin", "city": "Austin",
"state": "TX", "state": "TX",
"country": "US", "country": "US",
@ -103,10 +109,10 @@
"audio_latency": null "audio_latency": null
}, },
{ {
"id": "feb671a3-1821-48f0-bc14-aa26cf98bb25", "id": "5",
"first_name": "David", "first_name": "Test",
"last_name": "Wilson", "last_name": "User5",
"name": "David Wilson", "name": "Test User5",
"city": "Austin", "city": "Austin",
"state": "TX", "state": "TX",
"country": "US", "country": "US",
@ -129,10 +135,379 @@
"audio_latency": null "audio_latency": null
}, },
{ {
"id": "b1ddadd0-0263-47c4-bf91-e7767f386970", "id": "6",
"first_name": "Oswald", "first_name": "Test",
"last_name": "Becca", "last_name": "User6",
"name": "Oswald Becca", "name": "Test User6",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "7",
"first_name": "Test",
"last_name": "User7",
"name": "Test User7",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "8",
"first_name": "Test",
"last_name": "User8",
"name": "Test User8",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "9",
"first_name": "Test",
"last_name": "User9",
"name": "Test User9",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "10",
"first_name": "Test",
"last_name": "User10",
"name": "Test User10",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "11",
"first_name": "Test",
"last_name": "User11",
"name": "Test User11",
"city": "Denver",
"state": "CO",
"country": "US",
"online": true,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User1",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 },
{ "instrument_id": "keyboard", "description": "Keyboard", "proficiency_level": 3, "priority": 8 },
{ "instrument_id": "ukulele", "description": "Ukulele", "proficiency_level": 3, "priority": 11 },
{ "instrument_id": "voice", "description": "Voice", "proficiency_level": 3, "priority": 13 },
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 }
],
"followings": [],
"is_friend": true,
"is_following": false,
"pending_friend_request": false,
"friend_count": 1,
"follow_count": 0,
"recording_count": 0,
"session_count": 10,
"audio_latency": 5
},
{
"id": "12",
"first_name": "Test",
"last_name": "User12",
"name": "Test User12",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "13",
"first_name": "Test",
"last_name": "User13",
"name": "Test User13",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "14",
"first_name": "Test",
"last_name": "User14",
"name": "Test User14",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "15",
"first_name": "Test",
"last_name": "User15",
"name": "Test User15",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "16",
"first_name": "Test",
"last_name": "User16",
"name": "Test User16",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "17",
"first_name": "Test",
"last_name": "User17",
"name": "Test User17",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "18",
"first_name": "Test",
"last_name": "User18",
"name": "Test User18",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "19",
"first_name": "Test",
"last_name": "User19",
"name": "Test User19",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null
},
{
"id": "20",
"first_name": "Test",
"last_name": "User20",
"name": "Test User20",
"city": "Austin", "city": "Austin",
"state": "TX", "state": "TX",
"country": "US", "country": "US",
@ -155,7 +530,7 @@
"audio_latency": null "audio_latency": null
} }
], ],
"page_count": 1, "page_count": 2,
"my_audio_latency": 5, "my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}", "filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me", "description": "Current Search: Sort = Latency to Me",

View File

@ -0,0 +1,79 @@
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"location": "Denver, CO",
"online": true,
"photo_url": null,
"musician": true,
"gender": "M",
"birth_date": null,
"friend_count": 1,
"liker_count": 0,
"follower_count": 0,
"following_count": 0,
"recording_count": 0,
"session_count": 0,
"biography": "Biography of Test User1",
"favorite_count": 0,
"audio_latency": null,
"upcoming_session_count": 0,
"age": null,
"website": "www.testuser1.com",
"skill_level": 2,
"concert_count": 4,
"studio_session_count": 4,
"virtual_band": true,
"virtual_band_commitment": 2,
"traditional_band": true,
"traditional_band_commitment": 4,
"traditional_band_touring": true,
"paid_sessions": true,
"paid_sessions_hourly_rate": 10000,
"paid_sessions_daily_rate": 200000,
"free_sessions": true,
"cowriting": true,
"cowriting_purpose": 2,
"subscribe_email": true,
"is_a_teacher": false,
"is_a_student": false,
"online_presences": [
{ "id": "e1962204-f652-41b0-84d6-1afd7e9172be", "service_type": "soundcloud", "username": "testuser" },
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b0", "service_type": "reverbnation", "username": "testuser" },
{ "id": "2dd22eef-03ba-4743-b65b-5a194591dc86", "service_type": "bandcamp", "username": "testuser" },
{ "id": "d6ae62b4-e1ce-4cf0-90b7-c64033533261", "service_type": "fandalism", "username": "testuser" },
{ "id": "c6e85453-0fa9-40d0-9754-8f372d6e0ed3", "service_type": "youtube", "username": "testuser" },
{ "id": "480ec1ad-ea1d-4990-9c68-d7f9c0174441", "service_type": "facebook", "username": "testuser" },
{ "id": "232b26d5-c75a-4d65-9013-a07b73c8a7ae", "service_type": "twitter", "username": "testuser" }
],
"performance_samples": [],
"genres": [
{ "genre_id": "asian", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "african", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "traditional_band" },
{ "genre_id": "blues", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "soft rock", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "celtic", "player_type": "JamRuby::User", "genre_type": "cowriting" },
{ "genre_id": "tv & movie soundtrack", "player_type": "JamRuby::User", "genre_type": "cowriting" }
],
"bands": [],
"instruments": [
{ "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" },
{ "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" },
{ "description": "Ukulele", "proficiency_level": 3, "priority": 11, "instrument_id": "ukulele" },
{ "description": "Voice", "proficiency_level": 3, "priority": 13, "instrument_id": "voice" },
{ "description": "Piano", "proficiency_level": 2, "priority": 10, "instrument_id": "piano" }
],
"is_friend": false,
"is_following": false,
"is_liking": false,
"pending_friend_request": false,
"my_audio_latency": 5,
"internet_score": null
}

View File

@ -1,35 +1,107 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { iteratee } from "lodash" describe('Friends page', () => {
describe("Friends Index page", () => {
describe('For unauthenticated user', () => {
beforeEach(() => { beforeEach(() => {
cy.stubUnauthenticate() cy.stubAuthenticate();
cy.visit('/friends') cy.intercept('POST', /\S+\/filter/, { fixture: 'people' });
}) });
it("should not list musicians", () => { describe('friends list', () => {
cy.contains("Find New Friends").should('exist')
cy.contains("Update Search").should('exist')
cy.contains("Reset Filters").should('exist')
cy.get('[data-testid=peopleListTable]').should('not.exist')
})
})
describe("For authenticated user", () => {
beforeEach(() => { beforeEach(() => {
cy.stubAuthenticate() cy.visit('/friends');
cy.intercept('GET', '/filter', { fixture: 'people' }) });
cy.visit('/friends')
it('lists musicians', () => {
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', 20)
.first()
.contains('Test User1');
});
//TODO: paginate
});
describe('details side panel', () => {
beforeEach(() => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.visit('/friends');
});
it('shows profile side panel', () => {
//open side panel by clicking name
cy.contains('Test User1').click();
cy.get('[data-testid=profileSidePanel]')
.should('be.visible')
.contains('Biography of Test User1');
closeMoreDetailsSidePanel()
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr').first().find('[data-testid=btnMore]').click()
cy.get('[data-testid=profileSidePanel]')
.should('be.visible')
.contains('Biography of Test User1');
closeMoreDetailsSidePanel()
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr').first().find('[data-testid=linkMore]').click()
cy.get('[data-testid=profileSidePanel]')
.should('be.visible')
.contains('Biography of Test User1');
closeMoreDetailsSidePanel()
});
});
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 User1').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
});
it('remove friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { 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')
});
}) })
it("should list musicians", () => { describe('send message', () => {
cy.get('[data-testid=peopleListTable]').should('exist')
})
})
})
});
}) function closeMoreDetailsSidePanel(){
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click()
}

View File

@ -32,9 +32,9 @@ describe("Top Navigation", () => {
it("shows user dropdown", () => { it("shows user dropdown", () => {
showSubscribeToUpdates() showSubscribeToUpdates()
cy.get('[data-testid=navbarTopProfileDropdown]').should('exist') cy.get('[data-testid=navbarTopProfileDropdown]').should('exist')
cy.contains("Peter Pan") cy.contains("Peter Pan").should('exist')
cy.contains("My Profile") cy.contains("My Profile").should('exist')
cy.contains("Sign out") cy.contains("Sign out").should('exist')
}) })
}) })

View File

@ -18,3 +18,6 @@ import './commands'
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')

View File

@ -5906,6 +5906,15 @@
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
}, },
"dom7": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz",
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
"dev": true,
"requires": {
"ssr-window": "^3.0.0-alpha.1"
}
},
"domain-browser": { "domain-browser": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@ -16813,6 +16822,12 @@
"tweetnacl": "~0.14.0" "tweetnacl": "~0.14.0"
} }
}, },
"ssr-window": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz",
"integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==",
"dev": true
},
"ssri": { "ssri": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.1.tgz", "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.1.tgz",
@ -17293,6 +17308,16 @@
"util.promisify": "~1.0.0" "util.promisify": "~1.0.0"
} }
}, },
"swiper": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-6.8.2.tgz",
"integrity": "sha512-VwBZ40NQ8vDzIZO9wApJTf4bDu/o3sgURMQ6fvJVSc9T63NoJV0KLC/mjkrl9GepGbmlCQNLR2tL0Kk/r8NSdw==",
"dev": true,
"requires": {
"dom7": "^3.0.0",
"ssr-window": "^3.0.0"
}
},
"symbol-observable": { "symbol-observable": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",

View File

@ -95,6 +95,7 @@
"gulp-rtlcss": "^1.4.1", "gulp-rtlcss": "^1.4.1",
"gulp-sass": "^4.1.0", "gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.5", "gulp-sourcemaps": "^2.6.5",
"prettier": "1.17.1" "prettier": "1.17.1",
"swiper": "^6.8.2"
} }
} }

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

View File

@ -11,7 +11,6 @@
rel="stylesheet" rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Poppins:100,200,300,400,500,600,700,800,900&display=swap" href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Poppins:100,200,300,400,500,600,700,800,900&display=swap"
/> />
<title>JamKazam</title> <title>JamKazam</title>
</head> </head>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -3,3 +3,10 @@
// user-variables.scss // user-variables.scss
// //
// Place your own variable overrides here, these will override any Bootstrap and theme variables. // Place your own variable overrides here, these will override any Bootstrap and theme variables.
$jk-navigation-link-color: #2c7be5 !default;
$jk-navigation-text-color: #313336 !default;
$dropdown-link-color: rgba($jk-navigation-link-color, 1) !default;
$link-color: rgba($jk-navigation-link-color, 1) !default;
$navbar-light-color: rgba($jk-navigation-link-color, 1) !default;

View File

@ -4,4 +4,6 @@
// //
// Place your own theme CSS or SCSS rules below this line, these rules will override any Bootstrap and theme variables. // Place your own theme CSS or SCSS rules below this line, these rules will override any Bootstrap and theme variables.
@import './custom/user.css'; @import './custom/nav';
@import './custom/user';
@import './custom/form';

View File

@ -0,0 +1,18 @@
.form-check {
display: block;
min-height: 1.5rem;
padding-left: 1.5em;
margin-bottom: 1rem !important;
}
/* -------------------------------------------------------------------------- */
/* Choices */
/* -------------------------------------------------------------------------- */
.choices {
position:relative;
margin-bottom:24px;
font-size:16px
}

View File

@ -0,0 +1,12 @@
.navbar-light{
.navbar-text {
color: $jk-navigation-text-color;
a {
color: $jk-navigation-text-color;
@include hover-focus() {
color: $navbar-light-active-color;
}
}
}
}

View File

@ -4,6 +4,7 @@
--jk-good: #198754; --jk-good: #198754;
--jk-fair: #e0a500; --jk-fair: #e0a500;
--jk-high: #990000; --jk-high: #990000;
--jk-unknown: #8a8787;
} }
.nav-active .nav-link-text{ .nav-active .nav-link-text{
@ -31,7 +32,13 @@
.latency-high { .latency-high {
background-color: var(--jk-high); background-color: var(--jk-high);
color: white; color: rgb(247, 239, 239);
min-width: 50px;
}
.latency-unknown {
background-color: var(--jk-unknown);
color: rgb(253, 251, 251);
min-width: 50px; min-width: 50px;
} }
@ -64,3 +71,35 @@
top: 0; top: 0;
z-index: 2000; z-index: 2000;
} }
.swiper-container {
width: 100%;
height: 100%;
}
.swiper-slide {
// text-align: center;
// font-size: 18px;
// background: #fff;
// /* Center slide text vertically */
// display: -webkit-box;
// display: -ms-flexbox;
// display: -webkit-flex;
// display: flex;
// -webkit-box-pack: center;
// -ms-flex-pack: center;
// -webkit-justify-content: center;
// justify-content: center;
// -webkit-box-align: center;
// -ms-flex-align: center;
// -webkit-align-items: center;
// align-items: center;
}
.swiper-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}

View File

@ -4,7 +4,7 @@
margin: 0; margin: 0;
margin-left: auto; margin-left: auto;
margin-right: initial; margin-right: initial;
max-width: 350px; max-width: 600px;
} }
// .modal-content { // .modal-content {
// border-radius: 0; // border-radius: 0;
@ -27,7 +27,8 @@
border: none; border: none;
border-radius: 0; border-radius: 0;
padding: 0.5rem 1.25rem; padding: 0.5rem 1.25rem;
background-image: linear-gradient(-45deg, #4695ff, #1970e2); border-bottom: solid 1px #ddd;
//background-image: linear-gradient(-45deg, #4695ff, #1970e2);
overflow: hidden; overflow: hidden;
&:before, &:before,
&:after { &:after {
@ -36,7 +37,7 @@
border-radius: 50%; border-radius: 50%;
height: 12.5rem; height: 12.5rem;
width: 12.5rem; width: 12.5rem;
background-image: linear-gradient(45deg, #318aff, #247cef); //background-image: linear-gradient(45deg, #318aff, #247cef);
} }
&:after { &:after {
left: 5.125rem; left: 5.125rem;
@ -51,7 +52,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
color: white; color: #333;
opacity: 0.75; opacity: 0.75;
padding-top: 0.75rem; padding-top: 0.75rem;
margin-top: 0; margin-top: 0;

View File

@ -6,9 +6,9 @@ const JKCurrentUserAvatar = () => {
const { currentUser } = useAuth(); const { currentUser } = useAuth();
if(currentUser && currentUser.photo_url) { if(currentUser && currentUser.photo_url) {
return ( <img className="avatar avatar-xl rounded-circle" src={currentUser.photo_url} /> ); return ( <img className="avatar avatar-xl rounded-circle mr-1" src={currentUser.photo_url} /> );
}else { }else {
return ( <img className="avatar avatar-xl rounded-circle" src={avatar} /> ); return ( <img className="avatar avatar-xl rounded-circle mr-1" src={avatar} /> );
} }
} }

View File

@ -6,40 +6,43 @@ import { Link } from 'react-router-dom';
import JKCurrentUserAvatar from './JKCurrentUserAvatar' import JKCurrentUserAvatar from './JKCurrentUserAvatar'
const JKNavbarTopCurrentUser = () => { const JKNavbarTopCurrentUser = () => {
const { currentUser, setCurrentUser } = useAuth(); const { currentUser } = useAuth();
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState); const toggle = () => setDropdownOpen(prevState => !prevState);
const fetchCurrentUser = () => { // const fetchCurrentUser = () => {
getCurrentUser() // getCurrentUser()
.then(resp => { // .then(resp => {
if (resp.ok) { // if (resp.ok) {
return resp.json(); // return resp.json();
} // }
}) // })
.then(data => { // .then(data => {
console.log('CURRENT_USER', data); // console.log('CURRENT_USER', data);
setCurrentUser(data); // setCurrentUser(data);
}) // })
.catch(error => console.log(error)); // .catch(error => console.log(error));
}; // };
const handleLogout = () => {}; const handleLogout = () => {};
useEffect(() => { // useEffect(() => {
fetchCurrentUser(); // fetchCurrentUser();
}, []); // }, []);
return ( return (
<div> <div>
{currentUser && {currentUser &&
<Dropdown isOpen={dropdownOpen} toggle={toggle} data-testid="navbarTopProfileDropdown"> <Dropdown isOpen={dropdownOpen} toggle={toggle} data-testid="navbarTopProfileDropdown">
<DropdownToggle nav={true} caret> <DropdownToggle nav={true}>
<JKCurrentUserAvatar /> <JKCurrentUserAvatar />
<span className="d-none d-lg-inline navbar-text">
{currentUser.name} {currentUser.name}
</span>
</DropdownToggle> </DropdownToggle>
<DropdownMenu> <DropdownMenu right={true}>
<DropdownItem tag={Link} to="/pages/settings"> <DropdownItem tag={Link} to="/pages/settings">
My Profile My Profile
</DropdownItem> </DropdownItem>

View File

@ -19,7 +19,7 @@ const Logo = ({ at, width, className, ...rest }) => {
className={classNames( className={classNames(
{ {
'align-items-center py-3': at === 'navbar-vertical', 'align-items-center p-2': at === 'navbar-vertical',
'align-items-center': at === 'navbar-top', 'align-items-center': at === 'navbar-top',
'flex-center font-weight-extra-bold fs-5 mb-4': at === 'auth' 'flex-center font-weight-extra-bold fs-5 mb-4': at === 'auth'
}, },

View File

@ -3,11 +3,11 @@ import { Collapse, Navbar, NavItem, Nav } from 'reactstrap';
import classNames from 'classnames'; import classNames from 'classnames';
import AppContext from '../../context/Context'; import AppContext from '../../context/Context';
import Logo from './Logo'; import Logo from './Logo';
import SearchBox from './SearchBox'; //import SearchBox from './SearchBox';
import TopNavRightSideNavItem from './TopNavRightSideNavItem'; import TopNavRightSideNavItem from './TopNavRightSideNavItem';
import NavbarTopDropDownMenus from './NavbarTopDropDownMenus'; //import NavbarTopDropDownMenus from './NavbarTopDropDownMenus';
import { navbarBreakPoint, topNavbarBreakpoint } from '../../config'; import { navbarBreakPoint, topNavbarBreakpoint } from '../../config';
import autoCompleteInitialItem from '../../data/autocomplete/autocomplete'; //import autoCompleteInitialItem from '../../data/autocomplete/autocomplete';
const NavbarTop = () => { const NavbarTop = () => {
const { const {
@ -26,7 +26,7 @@ const NavbarTop = () => {
return ( return (
<Navbar <Navbar
light light
className="navbar-glass fs--1 font-weight-semi-bold row navbar-top sticky-kit" className="navbar-glass fs--1 font-weight-semi-bold row navbar-top sticky-kit mb-3 d-flex"
expand={isTopNav && topNavbarBreakpoint} expand={isTopNav && topNavbarBreakpoint}
> >
<div <div

View File

@ -87,7 +87,7 @@ const NavbarVertical = ({ navbarStyle }) => {
} }
} }
> >
<Nav navbar vertical> <Nav navbar vertical className="mt-3">
<NavbarVerticalMenu routes={routes} /> <NavbarVerticalMenu routes={routes} />
</Nav> </Nav>
<div className="settings px-3 px-xl-0"> <div className="settings px-3 px-xl-0">
@ -101,7 +101,7 @@ const NavbarVertical = ({ navbarStyle }) => {
</Nav> </Nav>
</div> </div>
)} )}
<div className="navbar-vertical-divider"> {/* <div className="navbar-vertical-divider">
<hr className="navbar-vertical-hr my-2" /> <hr className="navbar-vertical-hr my-2" />
</div> </div>
<Button <Button
@ -114,7 +114,7 @@ const NavbarVertical = ({ navbarStyle }) => {
className="my-3 btn-purchase" className="my-3 btn-purchase"
> >
Purchase Purchase
</Button> </Button> */}
</div> </div>
</Collapse> </Collapse>
</Navbar> </Navbar>

View File

@ -11,7 +11,7 @@ const NavbarVerticalMenuItem = ({ route }) => (
<FontAwesomeIcon icon={route.icon} /> <FontAwesomeIcon icon={route.icon} />
</span> </span>
)} )}
<span className="nav-link-text">{route.name}</span> <span className="nav-link-text pl-1">{route.name}</span>
{!!route.badge && ( {!!route.badge && (
<Badge color={route.badge.color || 'soft-success'} pill className="ml-2"> <Badge color={route.badge.color || 'soft-success'} pill className="ml-2">
{route.badge.text} {route.badge.text}

View File

@ -1,9 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; //import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { DropdownItem, DropdownMenu, DropdownToggle, Dropdown } from 'reactstrap'; import { DropdownItem, DropdownMenu, DropdownToggle, Dropdown } from 'reactstrap';
import team3 from '../../assets/img/team/3.jpg';
import Avatar from '../common/Avatar';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
const ProfileDropdown = () => { const ProfileDropdown = () => {
@ -19,7 +17,7 @@ const ProfileDropdown = () => {
// removeCookie("remember_token", { // removeCookie("remember_token", {
// domain: ".jamkazam.local" // domain: ".jamkazam.local"
// }); // });
history.push('/authentication/basic/logout'); // history.push('/authentication/basic/logout');
console.log("signout..."); console.log("signout...");
} }

View File

@ -1,5 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Nav, NavItem, NavLink, UncontrolledTooltip } from 'reactstrap'; import { Navbar, Nav, NavItem, NavLink, NavbarText } from 'reactstrap';
// import ProfileDropdown from './ProfileDropdown'; // import ProfileDropdown from './ProfileDropdown';
// import NotificationDropdown from './NotificationDropdown'; // import NotificationDropdown from './NotificationDropdown';
// import SettingsAnimatedIcon from './SettingsAnimatedIcon'; // import SettingsAnimatedIcon from './SettingsAnimatedIcon';
@ -14,32 +14,17 @@ import JKNavbarTopProfile from './JKNavbarTopProfile';
const TopNavRightSideNavItem = () => { const TopNavRightSideNavItem = () => {
const { isTopNav, isCombo } = useContext(AppContext); const { isTopNav, isCombo } = useContext(AppContext);
return ( return (
<Nav navbar className="navbar-nav-icons ml-auto flex-row align-items-center"> <Navbar expand="md" className="ml-auto">
{/* <NavItem> <Nav className="align-items-center" navbar>
<SettingsAnimatedIcon /> <NavbarText className="d-none d-md-inline">Keep JamKazam Improving:</NavbarText>
</NavItem> */} <NavItem className="d-none d-md-inline mr-5">
{/* {(isCombo || isTopNav) && ( <NavLink>Subscribe</NavLink>
<NavItem className={classNames(`p-2 px-lg-0 cursor-pointer`, { [`d-${navbarBreakPoint}-none`]: isCombo })}>
<NavLink tag={Link} to="/changelog" id="changelog">
<FontAwesomeIcon icon="code-branch" transform="right-6 grow-4" />
</NavLink>
<UncontrolledTooltip autohide={false} placement="left" target="changelog">
Changelog
</UncontrolledTooltip>
</NavItem> </NavItem>
)} */} <NavItem>
{/* <CartNotification /> */}
{/* <NotificationDropdown /> */}
<NavItem className="me-4 d-none d-md-inline">
<div className="navbar-text d-inline">Keep JamKazam Improving:</div>
<NavLink className="text-green d-inline navbar-text nav-link">Subscribe</NavLink>
</NavItem>
<NavItem className="nav-item me-2">
<JKNavbarTopProfile /> <JKNavbarTopProfile />
</NavItem> </NavItem>
</Nav> </Nav>
</Navbar>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react'; import React, { useState, useEffect, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Alert, Card, CardBody, Col, Row, Button } from 'reactstrap'; import { Alert, Card, CardBody, Col, Row, Button, Pagination, PaginationItem, PaginationLink, Form } from 'reactstrap';
import Loader from '../common/Loader'; import Loader from '../common/Loader';
import FalconCardHeader from '../common/FalconCardHeader'; import FalconCardHeader from '../common/FalconCardHeader';
import { isIterableArray } from '../../helpers/utils'; import { isIterableArray } from '../../helpers/utils';
@ -8,9 +8,10 @@ import { isIterableArray } from '../../helpers/utils';
// import rawPeople from '../../data/people/people'; // import rawPeople from '../../data/people/people';
// import peopleCategories from '../../data/people/peopleCategories'; // import peopleCategories from '../../data/people/peopleCategories';
// import apiFetch from '../../helpers/apiFetch'; // import apiFetch from '../../helpers/apiFetch';
import JKPeopleSearch from "./JKPeopleSearch"; import JKPeopleSearch from './JKPeopleSearch';
import JKPeopleList from './JKPeopleList'; import JKPeopleList from './JKPeopleList';
import { getPeople } from "../../helpers/rest"; import { getMusicians, getPeople } from '../../helpers/rest';
import JKPeopleSwiper from './JKPeopleSwiper';
const JKPeople = ({ className }) => { const JKPeople = ({ className }) => {
//const { loading, data: people, setData: setPeople } = useFakeFetch(rawPeople); //const { loading, data: people, setData: setPeople } = useFakeFetch(rawPeople);
@ -18,63 +19,95 @@ const JKPeople = ({ className }) => {
const [people, setPeople] = useState([]); const [people, setPeople] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showSearch, setShowSearch] = useState(false); const [showSearch, setShowSearch] = useState(false);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const fetchPeople = React.useCallback( () => { const fetchPeople = React.useCallback(page => {
getPeople() //getMusicians(page)
console.log("PAGE", page);
getPeople({ page: page })
.then(response => { .then(response => {
if(!response.ok){ if (!response.ok) {
//TODO: handle failure //TODO: handle failure
console.log(response); //console.log(response);
throw new Error('Network response was not ok'); throw new Error('Network response was not ok');
} }
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
console.log('people received', data); console.log('PEOPLE', data.musicians);
setPeople(data.musicians); //const users = new Set([...people, ...data.musicians]);
//console.log("new users", users);
//setPeople(Array.from(users));
setPeople(prev => Array.from(new Set([...prev, ...data.musicians])))
setTotalPages(data.page_count);
}) })
.catch( error => { .catch(error => {
//TODO: handle error //TODO: handle error
console.log(error); console.log(error);
}).finally(() => { })
setLoading(false) .finally(() => {
setLoading(false);
}); });
}, []) }, []);
useEffect(() => { useEffect(() => {
fetchPeople(); fetchPeople(page);
}, [fetchPeople]) }, [page]);
const searchPeople = ({ target }) => { const goNextPage = () => {
const keyword = target.value.toLowerCase(); if (page < totalPages) {
const filteredResult = people.filter( setPage(val => ++val);
person => person.name.toLowerCase().includes(keyword) || person.institution.toLowerCase().includes(keyword) }
);
setPeople(keyword.length ? filteredResult : people);
}; };
const goPrevPage = () => {
if (page > 1) {
setPage(prev => --prev);
}
};
// const searchPeople = ({ target }) => {
// const keyword = target.value.toLowerCase();
// const filteredResult = people.filter(
// person => person.name.toLowerCase().includes(keyword) || person.institution.toLowerCase().includes(keyword)
// );
// setPeople(keyword.length ? filteredResult : people);
// };
return ( return (
<Card> <Card>
<FalconCardHeader title="Find New Friends">
<div className="col-12 col-sm-auto">
<Button color="primary" className="me-2 fs--1" onClick={() => setShowSearch(!showSearch)}>Update Search</Button>
<Button outline disabled color="secondary" className="fs--1">Reset Filters</Button>
</div>
</FalconCardHeader>
<JKPeopleSearch show={showSearch} setShow={setShowSearch} setPeople={setPeople} /> <JKPeopleSearch show={showSearch} setShow={setShowSearch} setPeople={setPeople} />
<FalconCardHeader title="Find New Friends" 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)}>
Update Search
</Button>
<Button outline disabled color="secondary" className="fs--1">
Reset Filters
</Button>
</Form>
</FalconCardHeader>
<CardBody className="pt-0"> <CardBody className="pt-0">
{loading ? ( {loading ? (
<Loader /> <Loader />
) : isIterableArray(people) ? ( ) : isIterableArray(people) ? (
//Start Find Friends table hidden on small screens //Start Find Friends table hidden on small screens
<Fragment>
<Row className="mb-3 justify-content-between d-none d-md-block">
<div className="table-responsive-xl px-2">
<JKPeopleList people={people} goNextPage={goNextPage} page={page} totalPages={totalPages} />
</div>
</Row>
<JKPeopleList people={people} /> <Row className="swiper-container d-block d-md-none">
<JKPeopleSwiper people={people} goNextPage={goNextPage} />
</Row>
</Fragment>
) : ( ) : (
<Row className="p-card"> <Row className="p-card">
<Col> <Col>
@ -85,8 +118,9 @@ const JKPeople = ({ className }) => {
</Row> </Row>
)} )}
</CardBody> </CardBody>
</Card>); </Card>
} );
};
JKPeople.propTypes = { JKPeople.propTypes = {
className: PropTypes.string className: PropTypes.string

View File

@ -1,35 +1,47 @@
import React from "react"; import React from 'react';
import { Table } from 'reactstrap'; import { Table, Row, Col, Button } from 'reactstrap';
import JKPerson from './JKPerson'; import JKPerson from './JKPerson';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
const JKPeopleList = ({people}) => { const JKPeopleList = ({ people, goNextPage, page, totalPages }) => {
return ( return (
<Table className="table-bordered table-striped fs--1" data-testid="peopleListTable"> <>
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900"> <thead className="bg-200 text-900">
<tr> <tr>
<th scope="col">Name</th> <th scope="col">Name</th>
<th scope="col" style={{ minWidth: 250 }}>About</th> <th scope="col" style={{ minWidth: 250 }}>
About
</th>
<th scope="col">Instruments</th> <th scope="col">Instruments</th>
<th scope="col">Genres</th>
<th scope="col">Action</th> <th scope="col">Action</th>
</tr> </tr>
</thead> </thead>
<tbody className="list"> <tbody className="list">
{people.map((person, index) => ( {people.map((person, index) => (
<tr className="align-middle" key={person.id}> <tr className="align-middle" key={person.id}>
<JKPerson {...person} /> <JKPerson person={person} viewMode="list" />
</tr> </tr>
))} ))}
</tbody> </tbody>
</Table> </Table>
<Row>
<Col>
{page < totalPages && (
<a className="ml-2 fw-semi-bold" href="#!" onClick={goNextPage}>
More...
</a>
)}
</Col>
</Row>
</>
); );
} };
JKPeopleList.propTypes = { JKPeopleList.propTypes = {
people: PropTypes.arrayOf( people: PropTypes.arrayOf(PropTypes.instanceOf(Object))
PropTypes.instanceOf(Object) };
)
}
export default JKPeopleList; export default JKPeopleList;

View File

@ -1,9 +1,9 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef, Fragment } from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import Select from 'react-select'; import Select from 'react-select';
import JKTooltip from '../common/JKTooltip'; import JKTooltip from '../common/JKTooltip';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { getGenres, getInstruments, postPeopleSearch } from '../../helpers/rest'; import { getGenres, getInstruments, getPeople } from '../../helpers/rest';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
const JKPeopleSearch = props => { const JKPeopleSearch = props => {
@ -54,7 +54,7 @@ const JKPeopleSearch = props => {
} }
}) })
.then(data => { .then(data => {
console.log(data); //console.log(data);
setGenres( setGenres(
data.map(genre => { data.map(genre => {
return { return {
@ -88,7 +88,7 @@ const JKPeopleSearch = props => {
} }
const updatedData = {...data, genres} const updatedData = {...data, genres}
console.log('submitting...', updatedData); console.log('submitting...', updatedData);
await postPeopleSearch(updatedData) await getPeople(updatedData)
.then(response => { .then(response => {
if(!response.ok){ if(!response.ok){
//TODO: handle failure //TODO: handle failure
@ -121,8 +121,8 @@ const JKPeopleSearch = props => {
]; ];
return ( return (
<div> <Fragment>
<Modal isOpen={show} toggle={toggle} size="xl"> <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">
<ModalHeader toggle={toggle}>Update Search</ModalHeader> <ModalHeader toggle={toggle}>Update Search</ModalHeader>
<ModalBody> <ModalBody>
<div className="px-4 pb-4"> <div className="px-4 pb-4">
@ -212,7 +212,7 @@ const JKPeopleSearch = props => {
Instruments{' '} Instruments{' '}
<JKTooltip title="Select one or more instruments to filter for. If this field is blank, all instruments will be searched for." /> <JKTooltip title="Select one or more instruments to filter for. If this field is blank, all instruments will be searched for." />
</label> </label>
<div> <div className="choices">
<Controller <Controller
name="instruments" name="instruments"
control={control} control={control}
@ -225,7 +225,7 @@ const JKPeopleSearch = props => {
Genres{' '} Genres{' '}
<JKTooltip title="Select one or more genres to filter for. If this field is blank, all genres will be included." /> <JKTooltip title="Select one or more genres to filter for. If this field is blank, all genres will be included." />
</label> </label>
<div> <div className="choices">
<Controller <Controller
name="genres" name="genres"
control={control} control={control}
@ -237,7 +237,7 @@ const JKPeopleSearch = props => {
<label className="form-label" htmlFor="lastActive"> <label className="form-label" htmlFor="lastActive">
Last Active <JKTooltip title="Select onefor when the user was last active on JamKazam." /> Last Active <JKTooltip title="Select onefor when the user was last active on JamKazam." />
</label> </label>
<div> <div className="choices">
<Controller <Controller
name="last_active" name="last_active"
control={control} control={control}
@ -249,7 +249,7 @@ const JKPeopleSearch = props => {
<label className="form-label" htmlFor="joined"> <label className="form-label" htmlFor="joined">
Joined JamKazam <JKTooltip title="Select onefor when the user joined JamKazam." /> Joined JamKazam <JKTooltip title="Select onefor when the user joined JamKazam." />
</label> </label>
<div> <div className="choices">
<Controller <Controller
name="joined" name="joined"
control={control} control={control}
@ -264,7 +264,7 @@ const JKPeopleSearch = props => {
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={toggle}> <Button color="outline-primary" onClick={toggle}>
Cancel Cancel
</Button>{' '} </Button>{' '}
<Button color="primary" onClick={submitForm}> <Button color="primary" onClick={submitForm}>
@ -272,7 +272,7 @@ const JKPeopleSearch = props => {
</Button> </Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
</div> </Fragment>
); );
}; };

View File

@ -0,0 +1,74 @@
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 './JKPerson';
import JKProfileAvatar from '../profile/JKProfileAvatar';
SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
const JKPeopleSwiper = ({ people, 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'
}}
>
{people.map((person, index) => (
<SwiperSlide key={person.id}>
<Card>
<CardHeader className="bg-200">
<div className="avatar avatar-xl d-inline-block me-2 mr-2">
<JKProfileAvatar url={person.photo_url} size="xl"/>
</div>
<h5 className="d-inline-block align-top mt-1">{person.name}</h5>
</CardHeader>
<CardBody>
<JKPerson person={person} 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>
</>
);
};
JKPeopleSwiper.propTypes = {
people: PropTypes.arrayOf(PropTypes.instanceOf(Object))
};
export default JKPeopleSwiper;

View File

@ -1,111 +1,134 @@
import React, { Fragment, useState } from 'react'; import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Row, Col } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import avatar from '../../assets/img/team/avatar.png'; import avatar from '../../assets/img/team/avatar.png';
import JKProfileSidePanel from '../profile/JKProfileSidePanel'; import JKProfileSidePanel from '../profile/JKProfileSidePanel';
import JKProfileAvatar from '../profile/JKProfileAvatar'; import JKProfileAvatar from '../profile/JKProfileAvatar';
import JKProfileInstrumentsList from '../profile/JKProfileInstrumentsList'; import JKProfileInstrumentsList from '../profile/JKProfileInstrumentsList';
import {getUserProfile} from '../../helpers/rest'; import { getPersonById } from '../../helpers/rest';
import JKConnectButton from '../profile/JKConnectButton';
import JKMessageButton from '../profile/JKMessageButton';
import JKLatencyBadge from '../profile/JKLatencyBadge';
import { useAuth } from '../../context/AuthContext';
const JKPerson = ({ id, name, biography, photo_url, instruments }) => { const JKPerson = props => {
const { id, name, biography, photo_url, instruments, latency_data } = props.person;
const viewMode = props.viewMode;
const { currentUser } = useAuth();
const [showSidePanel, setShowSidePanel] = useState(false) const [showSidePanel, setShowSidePanel] = useState(false);
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const fetchPerson = () => { const fetchPerson = async () => {
console.log("fetchPerson called"); //console.log("fetchPerson called");
getUserProfile(id) await getPersonById(id)
.then(response => { .then(response => {
if(response.ok){ if (response.ok) {
return response.json() return response.json();
}else{ } else {
} }
}) })
.then(json => { .then(json => {
console.log("USER", json); console.log('USER', json);
setUser(json) setUser(json);
}) })
.catch(error => console.log(error)) .catch(error => console.log(error));
} };
const toggleMoreDetails = () => { const toggleMoreDetails = () => {
setShowSidePanel(prev => !prev) fetchPerson();
if(!user){ setShowSidePanel(prev => !prev);
fetchPerson() };
} return (
} <>
return( {viewMode === 'list' ? (
<Fragment> <>
<td className="text-nowrap"> <td className="text-nowrap">
<a onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0"> <a onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0">
<div className="avatar avatar-xl"> <div className="avatar avatar-xl">
<JKProfileAvatar url={photo_url} /> <JKProfileAvatar url={photo_url} />
</div> </div>
<div className="ms-2"> <div className="ml-2 ms-2">
<strong>{name}</strong> <strong>{name}</strong>
</div> </div>
</a> </a>
<div> <div>
<strong>Latency To Me:</strong> 24ms <span className="badge latency-good">GOOD</span> <strong>Latency To Me:</strong>
<JKLatencyBadge latencyData={latency_data} />
</div> </div>
<div> <div>
<strong>Last Active:</strong> 1 hour <strong>Last Active:</strong> 1 hour
</div> </div>
</td> </td>
<td> <td>
{ biography } {biography}
{ biography.length > 0 && ( {biography.length > 0 && (
<a onClick={toggleMoreDetails}> <a data-testid="linkMore" onClick={toggleMoreDetails}>
{' '} more » {' '} more »
</a> </a>
)} )}
</td> </td>
<td> <td><JKProfileInstrumentsList instruments={instruments} /></td>
</td>
<td>
<JKProfileInstrumentsList instruments={instruments} />
</td>
<td className="text-nowrap"> <td className="text-nowrap">
<a <JKConnectButton
href="#" currentUser={currentUser}
className="btn fs--1 btn-primary px-2 py-1 mr-1" user={props.person}
data-bs-toggle="tooltip" addContent={<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />}
data-bs-placement="top" removeContent={<FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" />}
title="Connect with This Friend" cssClasses="fs--1 px-2 py-1 mr-1"
> />
<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />
</a>
<a <JKMessageButton currentUser={currentUser} user={props.person} cssClasses="fs--1 px-2 py-1 mr-1">
href="#"
className="btn btn-primary fs--1 px-2 py-1 mr-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Send a Message"
>
<FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" /> <FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" />
</a> </JKMessageButton>
<a onClick={toggleMoreDetails}> <a onClick={toggleMoreDetails} data-testid="btnMore">
<span className="btn btn-primary fs--1 px-2 py-1" data-bs-toggle="tooltip" title="View Profile"> <span className="btn btn-primary fs--1 px-2 py-1" data-bs-toggle="tooltip" title="View Profile">
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" /> <FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</span> </span>
</a> </a>
</td> </td>
</>
) : (
<>
<div>
<strong>Latency To Me:</strong> <JKLatencyBadge latencyData={latency_data} />
</div>
<div>
<strong>Last Active:</strong> 1 hour
</div>
<h5>Instruments</h5>
<JKProfileInstrumentsList instruments={instruments} />
<JKConnectButton
currentUser={currentUser}
user={props.person}
addContent={<FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" />}
removeContent={<FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" />}
cssClasses="fs--1 px-2 py-1 mr-1"
/>
<JKMessageButton currentUser={currentUser} user={props.person} cssClasses="fs--1 px-2 py-1 mr-1">
<FontAwesomeIcon icon="comments" transform="shrink-4 down-1" className="mr-1" />
</JKMessageButton>
<a onClick={toggleMoreDetails} data-testid="btnMore">
<span className="btn btn-primary fs--1 px-2 py-1" data-bs-toggle="tooltip" title="View Profile">
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</span>
</a>
</>
)}
<JKProfileSidePanel user={user} show={showSidePanel} setShow={setShowSidePanel} /> <JKProfileSidePanel user={user} show={showSidePanel} setShow={setShowSidePanel} />
</Fragment> </>
) );
} };
JKPerson.propTypes = { JKPerson.propTypes = {
id: PropTypes.string.isRequired, person: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, viewMode: PropTypes.string
biography: PropTypes.string.isRequired,
photo_url: PropTypes.string,
//instruments: PropTypes.arrayOf(PropTypes.string) //instruments: PropTypes.arrayOf(PropTypes.string)
}; };

View File

@ -0,0 +1,62 @@
import React, {useEffect, useState} from 'react';
import {addFriend as connect, removeFriend as disconnect} from '../../helpers/rest';
const JKConnectButton = (props) => {
const { user, currentUser, addContent, removeContent, cssClasses } = props
const [isFriend, setIsFriend] = useState(false)
const [pendingFriendRequest, setPendingFriendRequest] = useState(false)
useEffect(() => {
setIsFriend(user.is_friend);
setPendingFriendRequest(user.pending_friend_request)
}, [user])
const addFriend = () => {
connect(currentUser.id, user.id)
.then(resp => {
if(resp.ok && resp.status === 201){
setPendingFriendRequest(true)
}
})
.catch(err => console.log(err))
}
const removeFriend = () => {
disconnect(currentUser.id, user.id)
.then(resp => {
if(resp.ok){
setIsFriend(false)
}
})
.catch(err => console.log(err))
}
const buttonTitle = () => {
let title;
if (pendingFriendRequest) {
title = 'You have sent a friend request to this user';
} else if (!isFriend) {
title = 'Send friend request';
} else if (isFriend) {
title = 'Unfriend this person';
}
return title;
};
return (
<>
{ !isFriend ? (
<button className={`btn btn-primary ${cssClasses}`} data-testid="connect" disabled={pendingFriendRequest} onClick={addFriend} title={buttonTitle()}>
{addContent}
</button>)
: (
<button className={`btn btn-primary ${cssClasses}`} data-testid="disconnect" onClick={removeFriend} title={buttonTitle()}>
{removeContent}
</button>
)
}
</>
);
};
export default JKConnectButton;

View File

@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
const JKLatencyBadge = ({ latencyData, showAll }) => {
let label = 'UNKNOWN';
let latency = '';
if (latencyData) {
label = latencyData.label;
if (showAll) {
latency = `${latencyData.ars_internet_latency}ms + ${latencyData.audio_latency}ms`;
} else {
latency = `${latencyData.ars_total_latency}ms`;
}
}
return (
<>
{latency} <span className={`badge latency-${label.toLowerCase()}`}>{label}</span>
</>
);
};
JKLatencyBadge.propTypes = {
latencyData: PropTypes.object,
showAll: PropTypes.bool
};
JKLatencyBadge.defaultProps = {
showAll: false
};
export default JKLatencyBadge;

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import JKMessageModal from './JKMessageModal';
const JKMessageButton = props => {
const { currentUser, user, cssClasses, children } = props;
const [showModal, setShowModal] = useState(false);
const [isFriend, setIsFriend] = useState(false);
const [pendingFriendRequest, setPendingFriendRequest] = useState(false);
useEffect(() => {
setIsFriend(user.is_friend);
setPendingFriendRequest(user.pending_friend_request);
}, [user]);
const buttonTitle = () => {
return isFriend ? 'Send friend request' : 'You can message this user once you are friends.'
};
return (
<>
<JKMessageModal show={showModal} setShow={setShowModal} user={user} currentUser={currentUser} />
<button
onClick={() => setShowModal(!showModal)}
className={`btn btn-primary ${cssClasses}`}
title={buttonTitle()}
data-testid="message"
disabled={!isFriend}
>
{children}
</button>
</>
);
};
export default JKMessageButton;

View File

@ -0,0 +1,84 @@
import React, { useEffect, useState } from 'react';
import { Modal, ModalHeader, ModalBody, Row, Col, Button, ModalFooter } from 'reactstrap';
import { Scrollbar } from 'react-scrollbars-custom';
import JKProfileAvatar from './JKProfileAvatar';
import { getTextMessages, createTextMessage } from '../../helpers/rest';
const JKMessageModal = props => {
const { show, setShow, user } = props;
const [offset, setOffset] = useState(0);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const toggle = () => setShow(!show);
const LIMIT = 20;
const fetchMessages = async () => {
await getTextMessages({
target_user_id: user.id,
offset: offset,
limit: LIMIT
})
.then(resp => {
if (resp.ok) {
return resp.json();
} else {
}
})
.then(json => {
console.log(json);
setMessages(json);
})
.catch(error => console.log(error));
}
const sendMessage = () => {
const params = { message: newMessage, target_user_id: user.id }
console.log("Sending new message", params);
createTextMessage(params)
.then(resp => console.log(resp))
.catch(error => console.log(error))
}
useEffect(() => {
if (show) {
console.log('JKMessageModal User', user.id);
fetchMessages();
}
}, [show]);
return (
<>
<Modal isOpen={show} toggle={toggle}>
<ModalHeader toggle={toggle}>Conversation with {user.name}</ModalHeader>
<ModalBody>
<Scrollbar style={{ width: '100%', height: 400 }}>
{messages.map((message, index) => (
<div className="d-flex mb-2 mr-1" key={message.id}>
<div className="avatar avatar-2xl d-inline-block me-2 mr-2">
<JKProfileAvatar url={user.photo_url} />
</div>
<div className="d-inline-block ml-2 ms-2 mb-0">
<p className="mb-0">{message.message}</p>
<time className="notification-time">{message.created_at}</time>
</div>
</div>
))}
</Scrollbar>
<Row>
<Col>
<textarea style={{ width: '100%' }} value={newMessage} onChange={(e) => setNewMessage(e.target.value) } />
</Col>
</Row>
</ModalBody>
<ModalFooter>
<Button onClick={toggle}>Close</Button>
<Button color="primary" onClick={sendMessage}>Send</Button>
</ModalFooter>
</Modal>
</>
);
};
export default JKMessageModal;

View File

@ -1,15 +1,27 @@
import React from "react"; import React from 'react';
import avatar from "../../assets/img/team/avatar.png"; import PropTypes from 'prop-types';
import defaultAvatarUrl from '../../assets/img/team/avatar.png';
import Avatar from '../common/Avatar';
const JKProfileAvatar = ({url}) => { const JKProfileAvatar = ({ url, size }) => {
const avatarUrl = () => {
if (url) {
if(url) { return url;
return ( <img className="avatar avatar-xl rounded-circle" src={url} /> ); } else {
}else { return defaultAvatarUrl;
return ( <img className="avatar avatar-xl rounded-circle" src={avatar} /> );
} }
};
} return <Avatar src={avatarUrl()} size={size} />;
};
JKProfileAvatar.propTypes = {
url: PropTypes.string,
size: PropTypes.string
};
JKProfileAvatar.defaultProps = {
size: 'l'
};
export default JKProfileAvatar; export default JKProfileAvatar;

View File

@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import ScrollBarCustom from '../common/ScrollBarCustom'; import ScrollBarCustom from '../common/ScrollBarCustom';
@ -11,27 +11,31 @@ import JKProfileOnlinePresence from './JKProfileOnlinePresence';
import JKProfileInterests from './JKProfileInterests'; import JKProfileInterests from './JKProfileInterests';
import JKProfileGenres from './JKProfileGenres'; import JKProfileGenres from './JKProfileGenres';
import JKProfilePerformanceSamples from './JKProfilePerformanceSamples'; import JKProfilePerformanceSamples from './JKProfilePerformanceSamples';
import { useAuth } from '../../context/AuthContext';
import JKConnectButton from './JKConnectButton';
import JKLatencyBadge from './JKLatencyBadge';
const JKProfileSidePanel = props => { const JKProfileSidePanel = props => {
const { show, setShow, user } = props; const { show, setShow, user } = props;
const {currentUser} = useAuth()
const toggle = () => setShow(!show); const toggle = () => setShow(!show);
return ( return (
<Modal <Modal
isOpen={show} isOpen={show}
toggle={toggle} toggle={toggle}
modalClassName="overflow-hidden modal-fixed-right modal-theme" modalClassName="overflow-hidden modal-profile modal-fixed-right w-100 modal-theme"
className="modal-dialog-vertical" className="modal-dialog-vertical"
contentClassName="vh-100 border-0" contentClassName="vh-100 border-0"
data-testid="profileSidePanel"
> >
<ModalHeader tag="div" toggle={toggle} className="modal-header-settings"> <ModalHeader tag="div" toggle={toggle} className="modal-header-settings">
{user && ( {user && (
<Fragment> <Fragment>
<div className="avatar avatar-2xl d-inline-block me-2"> <div className="avatar avatar-2xl d-inline-block me-2 mr-2">
<JKProfileAvatar url={user.photo_url} /> <JKProfileAvatar url={user.photo_url} size="2xl" />
</div> </div>
<h4 className="d-inline-block align-middle mt-1">{user.name}</h4> <h4 className="d-inline-block align-middle mt-n3 pt-0">{user.name}</h4>
</Fragment> </Fragment>
)} )}
</ModalHeader> </ModalHeader>
@ -43,12 +47,11 @@ const JKProfileSidePanel = props => {
) )
}} }}
> >
<ModalBody> <ModalBody className="pb-5">
{user && ( {user && (
<div> <div>
<p> <p>
<strong>Latency to Me:</strong> 18ms Internet + 8ms Audio{' '} <strong>Latency to Me:</strong> <JKLatencyBadge latencyData={user.latencyData} showAll={true} />
<span className="badge latency-good">GOOD</span>
<br /> <br />
<strong>Location:</strong> {`${user.city}, ${user.country}`} <strong>Location:</strong> {`${user.city}, ${user.country}`}
<br /> <br />
@ -115,14 +118,16 @@ const JKProfileSidePanel = props => {
<h5>Interests</h5> <h5>Interests</h5>
<JKProfileInterests user={user} /> <JKProfileInterests user={user} />
{ currentUser &&
<div className="p-3 bg-white border-top fixed-bottom"> <div className="p-3 bg-white border-top fixed-bottom">
<button className="btn btn-primary"> <JKConnectButton currentUser={currentUser} user={user} addContent={<><FontAwesomeIcon icon="plus" transform="shrink-4 down-1" className="mr-1" /> Add Friend </>} removeContent={<><FontAwesomeIcon icon="minus" transform="shrink-4 down-1" className="mr-1" /> Disconnect</>} />
<span className="fas fa-plus" /> Add Friend {' '}
</button>{' '} <button className="btn btn-outline-primary" data-testid="message">
<button className="btn btn-outline-primary">
<span className="fas fa-comment" /> Send Message <span className="fas fa-comment" /> Send Message
</button> </button>
</div> </div>
}
</div> </div>
)} )}
</ModalBody> </ModalBody>

View File

@ -1,15 +1,22 @@
import { reject } from "lodash";
import apiFetch from "./apiFetch"; import apiFetch from "./apiFetch";
export const getPeople = () => { export const getMusicians = (page) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
apiFetch("/search/musicians?results=true") apiFetch(`/search/musicians?results=true`)
.then(response => resolve(response)) .then(response => resolve(response))
.catch(error => reject(error)) .catch(error => reject(error))
}) })
} }
export const getUserProfile = (id) => { // export const getPeople = (page) => {
// return new Promise((resolve, reject) => {
// apiFetch(`/filter?page=${page}`)
// .then(response => resolve(response))
// .catch(error => reject(error))
// })
// }
export const getPersonById = (id) => {
return new Promise((resolve, reject) => ( return new Promise((resolve, reject) => (
apiFetch(`/users/${id}/profile?show_teacher=true`) apiFetch(`/users/${id}/profile?show_teacher=true`)
.then(response => resolve(response)) .then(response => resolve(response))
@ -17,9 +24,9 @@ export const getUserProfile = (id) => {
)) ))
} }
export const postPeopleSearch = (data) => { export const getPeople = ({ data, page } = {}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
apiFetch("/filter", { apiFetch(`/filter?page=${page}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(data) body: JSON.stringify(data)
}) })
@ -51,3 +58,43 @@ export const getCurrentUser = () => {
.catch(error => reject(error)) .catch(error => reject(error))
}) })
} }
export const addFriend = (userId, friendId) => {
return new Promise((resolve, reject) => {
apiFetch(`/users/${userId}/friend_requests`, {
method: 'POST',
body: JSON.stringify({ friend_id: friendId })
})
.then(response => resolve(response))
.catch(error => reject(error))
})
}
export const removeFriend = (userId, friendId) => {
return new Promise((resolve, reject) => {
apiFetch(`/users/${userId}/friends/${friendId}`, {
method: 'DELETE'
})
.then(response => resolve(response))
.catch(error => reject(error))
})
}
export const getTextMessages = (options = {}) => {
return new Promise((resolve, reject) => {
apiFetch(`/text_messages?${new URLSearchParams(options)}`)
.then(response => resolve(response))
.catch(error => reject(error))
})
}
export const createTextMessage = (options) => {
return new Promise((resolve, reject) => {
apiFetch(`/text_messages`, {
method: "POST",
body: JSON.stringify(options)
})
.then(response => resolve(response))
.catch(error => reject(error))
})
}

View File

@ -1,8 +1,10 @@
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Route, Switch, Redirect } from 'react-router-dom'; import { Route, Switch, Redirect, NavLink } from 'react-router-dom';
import { Card, CardBody, Row, Col, Button } from "reactstrap";
import Dashboard from '../components/dashboard/Dashboard'; import Logo from '../components/navbar/Logo';
import Section from '../components/common/Section';
//import Dashboard from '../components/dashboard/Dashboard';
import JKDashboard from '../components/dashboard/JkDashboard'; import JKDashboard from '../components/dashboard/JkDashboard';
//import DashboardAlt from '../components/dashboard-alt/DashboardAlt'; //import DashboardAlt from '../components/dashboard-alt/DashboardAlt';
@ -17,6 +19,7 @@ import ProductProvider from '../components/e-commerce/ProductProvider';
import { getPageName } from '../helpers/utils'; import { getPageName } from '../helpers/utils';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { getCurrentUser } from '../helpers/rest';
const DashboardRoutes = loadable(() => import('./DashboardRoutes')); const DashboardRoutes = loadable(() => import('./DashboardRoutes'));
@ -25,6 +28,26 @@ const DashboardLayout = ({ location }) => {
const isKanban = getPageName('kanban'); const isKanban = getPageName('kanban');
const {currentUser, setCurrentUser} = useAuth()
const fetchCurrentUser = () => {
getCurrentUser()
.then(resp => {
if (resp.ok) {
return resp.json();
}
})
.then(data => {
console.log('layout CURRENT_USER', data);
setCurrentUser(data);
})
.catch(error => console.log(error));
};
useEffect(() => {
fetchCurrentUser()
}, [])
useEffect(() => { useEffect(() => {
DashboardRoutes.preload(); DashboardRoutes.preload();
}, []); }, []);
@ -34,6 +57,26 @@ const DashboardLayout = ({ location }) => {
}, [location.pathname]); }, [location.pathname]);
return ( return (
<>
{ currentUser == null ? (
<Section className="py-0">
<Row className="flex-center min-vh-100 py-6">
<Col sm={10} md={8} lg={6} xl={5} className="col-xxl-4">
<Logo/>
<Card>
<CardBody className="fs--1 font-weight-normal p-5">
<Row className="justify-content-center">
<h3 className="mt-3 mt-md-4 font-weight-normal fs-2">Signin to begin</h3>
<p>Please login to your jamkazam account before accessing this interface.</p>
<a className="btn btn-primary" href="https://www.jamkazam.com/signin">Signin</a>
</Row>
</CardBody>
</Card>
</Col>
</Row>
</Section>
) : (
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}> <div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
{isVertical && <NavbarVertical isKanban={isKanban} navbarStyle={navbarStyle} />} {isVertical && <NavbarVertical isKanban={isKanban} navbarStyle={navbarStyle} />}
<ProductProvider> <ProductProvider>
@ -51,6 +94,9 @@ const DashboardLayout = ({ location }) => {
{/* <SidePanelModal path={location.pathname} /> */} {/* <SidePanelModal path={location.pathname} /> */}
</ProductProvider> </ProductProvider>
</div> </div>
)}
</>
); );
}; };

View File

@ -1,15 +1,16 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { toast, ToastContainer } from 'react-toastify'; import { toast, ToastContainer } from 'react-toastify';
import { CloseButton, Fade } from '../components/common/Toast'; import { CloseButton, Fade } from '../components/common/Toast';
import DashboardLayout from './DashboardLayout'; import DashboardLayout from './DashboardLayout';
import ErrorLayout from './ErrorLayout'; import ErrorLayout from './ErrorLayout';
import loadable from '@loadable/component';
const AuthBasicLayout = loadable(() => import('./AuthBasicLayout')); // import loadable from '@loadable/component';
//const AuthBasicLayout = loadable(() => import('./AuthBasicLayout'));
//const Landing = loadable(() => import('../components/landing/Landing')); //const Landing = loadable(() => import('../components/landing/Landing'));
//const WizardLayout = loadable(() => import('../components/auth/wizard/WizardLayout')); //const WizardLayout = loadable(() => import('../components/auth/wizard/WizardLayout'));
//const AuthCardRoutes = loadable(() => import('../components/auth/card/AuthCardRoutes')); //const AuthCardRoutes = loadable(() => import('../components/auth/card/AuthCardRoutes'));
@ -17,31 +18,16 @@ const AuthBasicLayout = loadable(() => import('./AuthBasicLayout'));
const Layout = () => { const Layout = () => {
useEffect(() => { useEffect(() => {
AuthBasicLayout.preload(); //AuthBasicLayout.preload();
//Landing.preload(); //Landing.preload();
//WizardLayout.preload(); //WizardLayout.preload();
//AuthCardRoutes.preload(); //AuthCardRoutes.preload();
//AuthSplitRoutes.preload(); //AuthSplitRoutes.preload();
}, []); }, []);
// async function fetchUserData(){
// await apiFetch('/users/current_user_data')
// .then(resp => {
// if(!resp.ok){
// //handle error
// console.log("fetchUserData failed", resp);
// }else{
// return resp.json()
// }
// })
// .then(json => {
// setCurrentUser(json)
// console.log("USER>>>>>>>", json);
// })
// .catch(error => console.log(error))
// }
return ( return (
<Router fallback={<span />}> <Router fallback={<span />}>
<Switch> <Switch>
@ -51,10 +37,9 @@ const Layout = () => {
<Route path="/authentication/split" component={AuthSplitRoutes} /> <Route path="/authentication/split" component={AuthSplitRoutes} />
<Route path="/authentication/wizard" component={WizardLayout} /> */} <Route path="/authentication/wizard" component={WizardLayout} /> */}
<Route path="/errors" component={ErrorLayout} /> <Route path="/errors" component={ErrorLayout} />
<Route path="/authentication/basic" component={AuthBasicLayout} /> {/* <Route path="/authentication/basic" component={AuthBasicLayout} /> */}
<Route component={DashboardLayout} /> <Route component={DashboardLayout} />
</Switch> </Switch>
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_LEFT} /> <ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_LEFT} />
@ -62,6 +47,4 @@ const Layout = () => {
); );
}; };
export default Layout; export default Layout;

1
web/.gitignore vendored
View File

@ -43,3 +43,4 @@ public/uploads
/log/*.out /log/*.out
BUILD_NUMBER BUILD_NUMBER
.byebug_history .byebug_history
.ruby-version

View File

@ -5,6 +5,8 @@ class ApiSearchController < ApiController
respond_to :json respond_to :json
include LatencyHelper
def index def index
if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i
query = params.clone query = params.clone
@ -93,8 +95,8 @@ class ApiSearchController < ApiController
end end
end end
#filter users by first fetching users from latency graph database #Filter users by first fetching users from latency graph database
#for the latency filter options and then quering the relational #for latency specific filter options and then query the postgresql relational
#database for other filter options #database for other filter options
def filter def filter
latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
@ -102,7 +104,9 @@ class ApiSearchController < ApiController
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
begin begin
user_ids = user_ids_by_latency(latency_good, latency_fair, latency_high) @latency_data = users_latency_data(latency_good, latency_fair, latency_high)
#debugger
user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
filter_params = { filter_params = {
"sort_order"=>"latency", "sort_order"=>"latency",
@ -168,58 +172,73 @@ private
"#{Rails.application.config.latency_data_host}/search_users" "#{Rails.application.config.latency_data_host}/search_users"
end end
def user_ids_by_latency(latency_good, latency_fair, latency_high) # def users_latency_data(latency_good, latency_fair, latency_high)
user_ids = [] # latency_data = []
if latency_good || latency_fair || latency_high # if latency_good || latency_fair || latency_high
uri = URI(filter_latency_url) # uri = URI(filter_latency_url)
begin # begin
http = Net::HTTP.new(uri.host, uri.port) # http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://") # http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
req = Net::HTTP::Post.new(uri) # req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}" # req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
req["Content-Type"] = "application/json" # req["Content-Type"] = "application/json"
req.body = { # req.body = {
my_user_id: current_user.id, # my_user_id: current_user.id,
my_public_ip: request.remote_ip, # my_public_ip: request.remote_ip,
my_device_id: nil, # my_device_id: nil,
my_client_id: nil # my_client_id: nil
}.to_json # }.to_json
response = http.request(req) # response = http.request(req)
if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess) # if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
graph_db_users = JSON.parse(response.body)["users"] # graph_db_users = JSON.parse(response.body)["users"]
if latency_good || latency_fair || latency_high # if latency_good || latency_fair || latency_high
graph_db_users.select! do |user| # graph_db_users.select! do |user|
total_latency = user["ars"]["total_latency"].to_f # total_latency = user["ars"]["total_latency"].to_f
(total_latency <= 40 && latency_good) || # (total_latency >= 0 && total_latency <= 40 && latency_good) ||
(total_latency > 40 && total_latency <= 80 && latency_fair) || # (total_latency > 40 && total_latency <= 80 && latency_fair) ||
(total_latency > 80 && latency_high) # (total_latency > 80 && latency_high)
end # end
end # end
user_ids = graph_db_users.map { | user | user["user_id"] }.uniq # latency_data = graph_db_users.map { | user |
# total = user["ars"]["total_latency"].to_f
return user_ids # label = if total >= 0 && total <= 40
else # 'good'
logger.debug("Latency response failed: #{response}") # elsif total > 40 && total <= 80
Bugsnag.notify("LatencyResponseFailed") do |report| # 'fair'
report.severity = "faliure" # else
report.add_tab(:latency, { # 'high'
user_id: current_user.id, # end
name: current_user.name, # {
params: params, # user_id: user["user_id"],
url: filter_latency_url, # audio_latency: user["audio_latency"].to_f,
code: response.code, # ars_total_latency: user["ars"]["total_latency"].to_f,
body: response.body, # ars_internet_latency: user["ars"]["internet_latency"].to_f
}) # }
end # }.uniq
end # #debugger
rescue => exception # return latency_data
raise exception # else
end # logger.debug("Latency response failed: #{response}")
end # Bugsnag.notify("LatencyResponseFailed") do |report|
user_ids # report.severity = "faliure"
end # report.add_tab(:latency, {
# user_id: current_user.id,
# name: current_user.name,
# params: params,
# url: filter_latency_url,
# code: response.code,
# body: response.body,
# })
# end
# end
# rescue => exception
# raise exception
# end
# end
# latency_data
# end
end end

View File

@ -34,7 +34,7 @@ class ApiUsersController < ApiController
end end
def me def me
render json: { first_name: current_user.first_name, last_name: current_user.last_name, name: current_user.name, photo_url: current_user.photo_url }, status: 200 render json: { id: current_user.id, first_name: current_user.first_name, last_name: current_user.last_name, name: current_user.name, photo_url: current_user.photo_url }, status: 200
end end
def show def show

View File

@ -0,0 +1,153 @@
module LatencyHelper
LATENCY_SCORES = {
good: { label: 'GOOD', min: 0, max: 40 },
fair: { label: 'FAIR', min: 40, max: 80 },
high: { label: 'HIGH', min: 80, max: 10000000 },
me: { label: 'ME', min: -1, max: -1 },
unknown: { label: 'UNKNOWN', min: -2, max: -2 }
}
# def users_latency_data(latency_good, latency_fair, latency_high)
# latency_data = []
# if latency_good || latency_fair || latency_high
# uri = URI(filter_latency_url)
# begin
# http = Net::HTTP.new(uri.host, uri.port)
# http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
# req = Net::HTTP::Post.new(uri)
# req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
# req["Content-Type"] = "application/json"
# req.body = {
# my_user_id: current_user.id,
# my_public_ip: request.remote_ip,
# my_device_id: nil,
# my_client_id: nil
# }.to_json
# response = http.request(req)
# if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
# graph_db_users = JSON.parse(response.body)["users"]
# if latency_good || latency_fair || latency_high
# graph_db_users.select! do |user|
# total_latency = user["ars"]["total_latency"].to_f
# (total_latency >= LATENCY_SCORES[:good][:min] && total_latency <= LATENCY_SCORES[:good][:max] && latency_good) ||
# (total_latency > LATENCY_SCORES[:fair][:min] && total_latency <= LATENCY_SCORES[:fair][:max] && latency_fair) ||
# (total_latency > LATENCY_SCORES[:high][:min] && latency_high)
# end
# end
# latency_data = graph_db_users.map { | user |
# total = user["ars"]["total_latency"].to_f
# label = if total >= LATENCY_SCORES[:good][:min] && total <= LATENCY_SCORES[:good][:max]
# LATENCY_SCORES[:good][:label]
# elsif total > LATENCY_SCORES[:fair][:min] && total <= LATENCY_SCORES[:fair][:max]
# LATENCY_SCORES[:fair][:label]
# elsif total > LATENCY_SCORES[:high][:min]
# LATENCY_SCORES[:high][:label]
# else
# LATENCY_SCORES[:unknown][:label]
# end
# {
# user_id: user["user_id"],
# audio_latency: user["audio_latency"].to_f,
# ars_total_latency: user["ars"]["total_latency"].to_f,
# ars_internet_latency: user["ars"]["internet_latency"].to_f,
# label: label
# }
# }.uniq
# #debugger
# return latency_data
# else
# logger.debug("Latency response failed: #{response}")
# Bugsnag.notify("LatencyResponseFailed") do |report|
# report.severity = "faliure"
# report.add_tab(:latency, {
# user_id: current_user.id,
# name: current_user.name,
# params: params,
# url: filter_latency_url,
# code: response.code,
# body: response.body,
# })
# end
# end
# rescue => exception
# raise exception
# end
# end
# latency_data
# end
def users_latency_data(latency_good, latency_fair, latency_high)
latency_data = []
if latency_good || latency_fair || latency_high
uri = URI(filter_latency_url)
begin
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}"
req["Content-Type"] = "application/json"
req.body = {
my_user_id: current_user.id,
my_public_ip: request.remote_ip,
my_device_id: nil,
my_client_id: nil
}.to_json
response = http.request(req)
if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
graph_db_users = JSON.parse(response.body)["users"]
if latency_good || latency_fair || latency_high
graph_db_users.select! do |user|
total_latency = user["ars"]["total_latency"].to_f
(total_latency >= LATENCY_SCORES[:good][:min] && total_latency <= LATENCY_SCORES[:good][:max] && latency_good) ||
(total_latency > LATENCY_SCORES[:fair][:min] && total_latency <= LATENCY_SCORES[:fair][:max] && latency_fair) ||
(total_latency > LATENCY_SCORES[:high][:min] && latency_high)
end
end
latency_data = graph_db_users.map { | user |
total = user["ars"]["total_latency"].to_f
label = if total >= LATENCY_SCORES[:good][:min] && total <= LATENCY_SCORES[:good][:max]
LATENCY_SCORES[:good][:label]
elsif total > LATENCY_SCORES[:fair][:min] && total <= LATENCY_SCORES[:fair][:max]
LATENCY_SCORES[:fair][:label]
elsif total > LATENCY_SCORES[:high][:min]
LATENCY_SCORES[:high][:label]
else
LATENCY_SCORES[:unknown][:label]
end
{
user_id: user["user_id"],
audio_latency: user["audio_latency"].to_f,
ars_total_latency: user["ars"]["total_latency"].to_f,
ars_internet_latency: user["ars"]["internet_latency"].to_f,
label: label
}
}.uniq
return latency_data
else
logger.debug("Latency response failed: #{response}")
Bugsnag.notify("LatencyResponseFailed") do |report|
report.severity = "faliure"
report.add_tab(:latency, {
user_id: current_user.id,
name: current_user.name,
params: params,
url: filter_latency_url,
code: response.code,
body: response.body,
})
end
end
rescue => exception
raise exception
end
end
latency_data
end
end

View File

@ -60,6 +60,17 @@ if @search.is_a?(BaseSearch)
node :audio_latency do |musician| node :audio_latency do |musician|
last_jam_audio_latency(musician) last_jam_audio_latency(musician)
end end
node :latency_data do |musician|
if latency = @latency_data.detect{|l_data| l_data[:user_id] == musician.id }
{
audio_latency: latency[:audio_latency],
ars_internet_latency: latency[:ars_internet_latency],
ars_total_latency: latency[:ars_total_latency]
}
end if @latency_data
end
} }
elsif @search.is_a?(BandSearch) elsif @search.is_a?(BandSearch)

View File

@ -4,7 +4,7 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do
resource '*', resource '*',
headers: :any, headers: :any,
methods: [:get, :post, :options], methods: [:get, :post, :delete, :options],
credentials: true credentials: true
end end
end end

View File

@ -10,16 +10,18 @@ describe "Musician Filter API", type: :request do
let(:user4) { FactoryGirl.create(:user) } let(:user4) { FactoryGirl.create(:user) }
let(:user5) { FactoryGirl.create(:user) } let(:user5) { FactoryGirl.create(:user) }
let(:user6) { FactoryGirl.create(:user) } let(:user6) { FactoryGirl.create(:user) }
let(:user7) { FactoryGirl.create(:user) }
let(:latency_data_uri) { /\S+\/search_users/ } let(:latency_data_uri) { /\S+\/search_users/ }
let(:response_body) { mock_latency_response([ let(:response_body) { mock_latency_response([
{ user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD { user: user1, ars_total_latency: 1.0, ars_internet_latency: 0.4, audio_latency: 0.6 }, #GOOD
{ user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD { user: user2, ars_total_latency: 40.0, ars_internet_latency: 25.0, audio_latency: 15.0 }, #GOOD
{ user: user3, ars_total_latency: 41.0, ars_internet_latency: 25, audio_latency: 16 }, #FAIR { user: user3, ars_total_latency: 40.1, ars_internet_latency: 25, audio_latency: 15.1 }, #FAIR
{ user: user4, ars_total_latency: 80.0, ars_internet_latency: 40, audio_latency: 40.0 }, #FAIR { user: user4, ars_total_latency: 80.0, ars_internet_latency: 40, audio_latency: 40.0 }, #FAIR
{ user: user5, ars_total_latency: 81.0, ars_internet_latency: 41, audio_latency: 40 }, #HIGH { user: user5, ars_total_latency: 80.1, ars_internet_latency: 40.1, audio_latency: 40 }, #HIGH
{ user: user6, ars_total_latency: 100.0, ars_internet_latency: 50.0, audio_latency: 50.0 } #HIGH { user: user6, ars_total_latency: 100.0, ars_internet_latency: 50.0, audio_latency: 50.0 }, #HIGH
{ user: user7, ars_total_latency: -2, ars_internet_latency: -1, audio_latency: -1 } #UNKNOWN
]) ])
} }
@ -28,7 +30,6 @@ describe "Musician Filter API", type: :request do
let(:rock) { Genre.find_by_id('rock') } let(:rock) { Genre.find_by_id('rock') }
before(:each) do before(:each) do
#ActiveMusicSession.delete_all
User.delete_all User.delete_all
stub_request(:post, latency_data_uri) stub_request(:post, latency_data_uri)
.with(:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}) .with(:headers => {'Accept'=>'*/*', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'})
@ -45,20 +46,27 @@ describe "Musician Filter API", type: :request do
it "get all musicians" do it "get all musicians" do
get '/api/search/musicians.json?results=true' get '/api/search/musicians.json?results=true'
expect(JSON.parse(response.body)["musicians"].size).to eq(6) expect(JSON.parse(response.body)["musicians"].size).to eq(7)
end end
it "filter all musicians for all latency types" do it "filter all musicians for all latency types" do
post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false } post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false }
expect(JSON.parse(response.body)["musicians"].size).to eq(6) expect(JSON.parse(response.body)["musicians"].size).to eq(7)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).to eq(nil)
end end
it "filter GOOD latency users" do it "filter GOOD latency users", focus: true do
post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false } post '/api/filter.json', { latency_good: true, latency_fair: false, latency_high: false }
expect(response.content_type).to eq("application/json") expect(response.content_type).to eq("application/json")
expect(response).to render_template(:index) expect(response).to render_template(:index)
expect(response).to have_http_status(:created) expect(response).to have_http_status(:created)
expect(JSON.parse(response.body)["musicians"].size).to eq(2) expect(JSON.parse(response.body)["musicians"].size).to eq(2)
#test latency data
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).not_to eq(nil)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["audio_latency"]).not_to eq(nil)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["ars_internet_latency"]).not_to eq(nil)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["ars_total_latency"]).not_to eq(nil)
end end
it "filter FAIR latency musicians" do it "filter FAIR latency musicians" do
@ -120,7 +128,7 @@ describe "Musician Filter API", type: :request do
end end
it "filter musicians by instruments they play", focus: true do it "filter musicians by instruments they play" do
user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('drums'), proficiency_level: 1 ) user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('drums'), proficiency_level: 1 )
user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('violin'), proficiency_level: 2 ) user1.musician_instruments << FactoryGirl.create(:musician_instrument, player: user1, instrument: JamRuby::Instrument.find('violin'), proficiency_level: 2 )
user1.save! user1.save!
@ -144,7 +152,7 @@ describe "Musician Filter API", type: :request do
expect(JSON.parse(response.body)["musicians"].size).to eq(0) expect(JSON.parse(response.body)["musicians"].size).to eq(0)
end end
it "filter musicians by joined within day" do it "filter musicians by days ago that they joined" do
user1.created_at = 1.day.ago user1.created_at = 1.day.ago
user1.save! user1.save!

View File

@ -26,3 +26,4 @@ target
vendor vendor
BUILD_NUMBER BUILD_NUMBER
.byebug_history .byebug_history
.ruby-version