UI changes as per the requests by David

This commit is contained in:
Nuwan 2021-11-24 09:51:28 +05:30
parent e769eb0531
commit 2110c4b1ce
44 changed files with 715 additions and 420 deletions

View File

@ -18,7 +18,8 @@
{ "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 }
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 },
{ "instrument_id": "banjo", "description": "Banjo", "proficiency_level": 2, "priority": 4}
],
"followings": [],
"is_friend": true,
@ -29,7 +30,33 @@
"recording_count": 0,
"session_count": 10,
"audio_latency": 5,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": [
{
"description": "Asian"
},
{
"description": "Classical"
},
{
"description": "Hard Rock"
},
{
"description": "Jazz"
},
{
"description": "Latin"
},
{
"description": "Oldies"
},
{
"description": "Pop"
},
{
"description": "Soft Rock"
}
]
},
{
"id": "2",
@ -56,7 +83,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "3",
@ -83,7 +111,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "4",
@ -110,7 +139,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "5",
@ -137,7 +167,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "6",
@ -164,7 +195,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "7",
@ -191,7 +223,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "8",
@ -218,7 +251,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "9",
@ -245,7 +279,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "10",
@ -272,7 +307,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
@ -304,7 +340,8 @@
"recording_count": 0,
"session_count": 10,
"audio_latency": 5,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "12",
@ -331,7 +368,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "13",
@ -358,7 +396,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "14",
@ -385,7 +424,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "15",
@ -412,7 +452,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "16",
@ -439,7 +480,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "17",
@ -466,7 +508,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "18",
@ -493,7 +536,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "19",
@ -520,7 +564,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "20",
@ -547,7 +592,8 @@
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641
"last_active_timestamp": 1629916641,
"genres": []
}
],
"page_count": 2,

View File

@ -1,5 +1,43 @@
/// <reference types="cypress" />
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => {
cy.contains('Location: Denver, US')
.and('contain', 'Location: Denver, US')
.and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:')
.and('contain', 'Latency To Me:');
cy.get('.latency-badge').contains('UNKNOWN');
});
cy.get('[data-testid=profileSidePanel] .modal-body').within(() => {
cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
//genres
cy.get('[data-testid=genres]').contains('classical, blues');
//bands
cy.get('[data-testid=bands]').contains('The Band');
//performance_samples
//cy.get('[data-testid=performance_samples]').contains('The Band')
//online presence
cy.get('[data-testid=online_presences]').contains('Soundcloud');
cy.get('[data-testid=online_presences]').contains('Reverbnation');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
describe('Friends page without data', () => {
beforeEach(() => {
cy.stubAuthenticate();
@ -18,6 +56,7 @@ describe('Friends page with data', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter/, { fixture: 'people' }).as('getPeople');
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
});
describe('listing users', () => {
@ -25,88 +64,138 @@ describe('Friends page with data', () => {
cy.visit('/friends');
});
it('lists musician users', () => {
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');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('show profiles', () => {
cy.contains('Find New Friends').should('exist');
cy.contains('Update Search').should('exist');
cy.contains('Reset Filters').should('exist');
cy.get('[data-testid=peopleListTable] > tbody tr')
.should('have.length', 20)
.first()
.contains('Test User1');
});
it('click profile name', () => {
//open side panel by clicking name
cy.get('[data-testid=peopleListTable]').within(() => {
cy.contains('Test User1').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click more button', () => {
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click more link', () => {
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click description more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=biography-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click instruments more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=instruments-col]')
.within(() => {
cy.get('div').should('have.length', 4); //show only 4 instruments plus more link
cy.contains('Acoustic Guitar: Expert');
cy.contains('Keyboard: Expert');
cy.contains('Ukulele: Expert');
cy.contains('Voice: Expert');
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click genres more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=genres-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
})
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('show profile', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 20)
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(0).contains('Test User1')
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(0).should('be.visible')
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(2).should('not.be.visible')
})
it('show all profile description', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=mobBiography]').should('not.contain', 'More')
})
it('show all instruments', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first().find('[data-testid=instrumentList] div').its('length').should('be.gte', 1)
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=instrumentList]').should('not.contain', 'More')
})
it('show all genres', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first().find('[data-testid=genreList] div').its('length').should('be.gte', 1)
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=genreList]').should('not.contain', 'More')
})
it('click more button', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
})
it('click connect button', () => {})
it('click message button', () => {})
})
//TODO: paginate
});
describe('user details side panel', () => {
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => {
cy.contains('Location: Denver, US')
.and('contain', 'Location: Denver, US')
.and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:')
.and('contain', 'Latency to Me:');
cy.get('.latency-badge').contains('UNKNOWN');
});
cy.get('[data-testid=profileSidePanel] .modal-body').within(() => {
cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
//genres
cy.get('[data-testid=genres]').contains('classical, blues');
//bands
cy.get('[data-testid=bands]').contains('The Band');
//performance_samples
//cy.get('[data-testid=performance_samples]').contains('The Band')
//online presence
cy.get('[data-testid=online_presences]').contains('Soundcloud');
cy.get('[data-testid=online_presences]').contains('Reverbnation');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
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();
showSidePanelContent();
closeSidePanel();
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
});
describe('making friendship', () => {
describe.only('making friendship', () => {
it('add friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
@ -118,16 +207,15 @@ describe('Friends page with data', () => {
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
cy.get('[data-testid=confirmFriendRequestModal]')
.within(() => {
cy.contains('Send a friend request to Test User2')
cy.contains('Yes').click()
})
// cy.get('[data-testid=confirmFriendRequestModal]').within(() => {
// cy.contains('Send a friend request to Test User2');
// cy.contains('Yes').click();
// });
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Friend request was sent');
cy.contains('Success! Your friend request has been sent to Test User2.');
});
it('remove friend', () => {
@ -291,7 +379,8 @@ describe('Friends page with data', () => {
.click();
cy.get('#selLastActive').type('Within last 1 Day{enter}');
cy.get('#selJoinedWithin').type('Within last 7 Day{enter}');
}
};
beforeEach(() => {
cy.visit('/friends');
});
@ -305,21 +394,30 @@ describe('Friends page with data', () => {
cy.get('[data-testid=modalUpdateSearch]').should('not.be.visible');
});
it('render filter form', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').within(() => {
cy.get('input[name=latency_good]').should('be.checked').next().contains('Good (less than 40ms)')
cy.get('input[name=latency_fair]').should('be.checked').next().contains('Fair (40-60ms)')
cy.get('input[name=latency_high]').should('not.be.checked').next().contains('Poor (more than 60ms)')
})
})
it('reset filters', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm()
fillFilterForm();
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
.contains('Cancel')
.click();
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked')
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnResetSearch]').click() //click reset button
.contains('Cancel')
.click();
cy.get('[data-testid=btnResetSearch]').click(); //click reset button
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked')
})
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked');
});
it('submit filter form with params', () => {
//wait for stubbed request sent to fetch data for initial page load
@ -338,7 +436,7 @@ describe('Friends page with data', () => {
.should('deep.equal', {
latency_good: true,
latency_fair: true,
latency_high: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
@ -346,8 +444,8 @@ describe('Friends page with data', () => {
genres: []
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm()
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm();
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form again. but this time fill form fields
@ -370,4 +468,6 @@ describe('Friends page with data', () => {
});
});
});
});

View File

@ -11,18 +11,25 @@ describe("Top Navigation", () => {
cy.get('[data-testid=navbarTopProfileDropdown]').should('exist')
cy.contains("Peter Pan").should('exist')
//cy.contains("My Profile").should('exist')
cy.contains("Sign out").should('exist')
cy.contains("Sign Out").should('exist')
}
describe("when user has not logged in", () => {
beforeEach(() => {
cy.stubUnauthenticate()
cy.visit('/')
});
it("does not show user dropdown", () => {
cy.contains("Sign in to begin")
cy.get('a.btn').should('have.text', 'Sign in')
it('shows homepage', () => {
cy.visit('/')
cy.contains('Home').should('exist')
showSubscribeToUpdates()
})
it("not allowed to projected page", () => {
cy.visit('/friends')
cy.url().should('include', '/authentication/basic/login')
cy.contains("Sign in")
cy.get('button').should('have.text', 'Sign in')
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist')
});
@ -36,17 +43,15 @@ describe("Top Navigation", () => {
});
it("shows user dropdown", () => {
showSubscribeToUpdates()
showProfileDropdown()
})
it('sign out', () => {
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Peter Pan').trigger('mouseover')
cy.stubUnauthenticate()
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Sign out').click()
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Sign Out').click()
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist')
cy.contains("Sign in to begin")
cy.contains("Home")
})
})
@ -75,12 +80,12 @@ describe("Top Navigation", () => {
})
it("translate", () => {
cy.get('.card-header').contains('Home page')
cy.get('.card-header').contains('Home')
cy.get('[data-testid=langSwitch]').contains('ES').click()
cy.get('.card-header').contains('Página de inicio')
cy.get('.card-header').should('not.contain', 'Home page')
cy.get('.card-header').should('not.contain', 'Home')
cy.get('[data-testid=langSwitch]').contains('EN').click()
cy.get('.card-header').contains('Home page')
cy.get('.card-header').contains('Home')
cy.get('.card-header').should('not.contain', 'Página de inicio')
})
})

View File

@ -1264,6 +1264,15 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@farfetch/react-context-responsive": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@farfetch/react-context-responsive/-/react-context-responsive-1.5.0.tgz",
"integrity": "sha512-vroOutgYik9ts/RxCKcK0O+0QUOZNe5yU2OSbvuzsT9OvNprmHUFa502DQP3BcKF5mwdPQCI9V7LDUzQyxraMw==",
"requires": {
"css-mediaquery": "^0.1.2",
"prop-types": "^15.6.1"
}
},
"@fortawesome/fontawesome-common-types": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
@ -6134,6 +6143,11 @@
}
}
},
"css-mediaquery": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
"integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA="
},
"css-prefers-color-scheme": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz",

View File

@ -3,6 +3,7 @@
"version": "2.10.2",
"private": true,
"dependencies": {
"@farfetch/react-context-responsive": "^1.5.0",
"@fortawesome/fontawesome-free": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-brands-svg-icons": "^5.14.0",

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

@ -8,8 +8,25 @@ import { getItemFromStore, setItemToStore, themeColors } from './helpers/utils';
import store from './store/store';
import { Provider } from 'react-redux';
import { CookiesProvider } from 'react-cookie';
import { ResponsiveProvider } from '@farfetch/react-context-responsive';
const Main = props => {
const breakpoints = {
xs: '320px',
sm: '576px',
md: '960px',
lg: '1280px',
xl: '1800px'
};
const breakpointsMax = {
xs: '319px',
sm: '575px',
md: '959px',
lg: '1279px',
xl: '1799px'
};
const [isFluid, setIsFluid] = useState(getItemFromStore('isFluid', settings.isFluid));
const [isRTL, setIsRTL] = useState(getItemFromStore('isRTL', settings.isRTL));
const [isDark, setIsDark] = useState(getItemFromStore('isDark', settings.isDark));
@ -120,9 +137,11 @@ const Main = props => {
return (
<AppContext.Provider value={value}>
<CookiesProvider>
<Provider store={store}>{props.children}</Provider>
</CookiesProvider>
<ResponsiveProvider breakpoints={breakpoints} breakpointsMax={breakpointsMax}>
<CookiesProvider>
<Provider store={store}>{props.children}</Provider>
</CookiesProvider>
</ResponsiveProvider>
</AppContext.Provider>
);
};

View File

@ -5,8 +5,10 @@
// 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;
$jk-navigation-text-color: #5e6e82 !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;
$navbar-light-color: rgba($jk-navigation-link-color, 1) !default;
$navbar-light-hover-color: darken($jk-navigation-link-color, 15) !default;

View File

@ -6,4 +6,4 @@
@import './custom/nav';
@import './custom/user';
@import './custom/form';
@import './custom/form';

View File

@ -10,4 +10,11 @@
}
}
}
}
}
// .navbar-top .navbar-nav a{
// &:hover{
// color: #1a993a !important;
// }
// }

View File

@ -29,6 +29,8 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
setCurrentUser(user)
//localStorage.setItem('user', user)
toast.success(`Signed in as ${email}`);
setEmail('')
setPassword('')
setRedirect(true)
}else{
toast.error("Incorrect email or password");
@ -45,6 +47,7 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
<FormGroup>
{hasLabel && <Label>Email address</Label>}
<Input
data-testid="email"
placeholder={!hasLabel ? 'Email address' : ''}
value={email}
onChange={({ target }) => setEmail(target.value)}
@ -54,6 +57,7 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
<FormGroup>
{hasLabel && <Label>Password</Label>}
<Input
data-testid="password"
placeholder={!hasLabel ? 'Password' : ''}
value={password}
onChange={({ target }) => setPassword(target.value)}
@ -77,8 +81,8 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
</Col>
</Row>
<FormGroup>
<Button color="primary" block className="mt-3" disabled={isDisabled}>
Log in
<Button color="primary" block className="mt-3" data-testid="submit" disabled={isDisabled}>
Sign in
</Button>
</FormGroup>
{/* <Divider className="mt-4">or log in with</Divider>

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { Switch, Route, Redirect } from 'react-router-dom';
import PrivateRoute from '../../helpers/privateRoute';
import NavbarTop from '../navbar/JKNavbarTop';
@ -16,10 +16,16 @@ import { add as addNotification } from '../../store/features/notificationSlice';
import { useAuth } from '../../context/UserAuth';
import Home from './JKHomePage';
import HomePage from '../page/JKHomePage';
import loadable from '@loadable/component';
const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
import JKHelp from '../page/JKHelp';
import JKPrivacy from '../page/JKPrivacy';
import JKPeople from '../page/JKPeople';
import JKNotifications from '../page/JKNotifications';
//import loadable from '@loadable/component';
//const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
//const PublicRoutes = loadable(() => import('../../layouts/JKPublicRoutes'))
function JKDashboard() {
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
@ -28,7 +34,8 @@ function JKDashboard() {
const { isAuthenticated, currentUser, setCurrentUser, logout } = useAuth();
useEffect(() => {
DashboardRoutes.preload();
//DashboardRoutes.preload();
//PublicRoutes.preload();
}, []);
const dispatch = useDispatch();
@ -124,27 +131,25 @@ function JKDashboard() {
useScript(`${process.env.REACT_APP_LEGACY_BASE_URL}/client_scripts`, initJKScripts);
return (
<>
{isAuthenticated ? (
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
{isVertical && <NavbarVertical isKanban={isKanban} navbarStyle={navbarStyle} />}
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
{isVertical && <NavbarVertical isKanban={isKanban} navbarStyle={navbarStyle} />}
<div className="content">
<NavbarTop />
<Switch>
<PrivateRoute path="/" exact component={Home} />
<DashboardRoutes />
</Switch>
{!isKanban && <Footer />}
</div>
{/* <SidePanelModal path={location.pathname} /> */}
</div>
) : (
<div className="content">
<NavbarTop />
<Switch>
<PrivateRoute path="/" exact component={Home} />
<Route path="/" exact component={HomePage} />
<Route path="/privacy" component={JKPrivacy} />
<Route path="/help" component={JKHelp} />
<PrivateRoute path="/friends" component={JKPeople} />
<PrivateRoute path="/notifications" component={JKNotifications} />
{/*Redirect*/}
<Redirect to="/errors/404" />
</Switch>
)}
</>
{!isKanban && <Footer />}
</div>
{/* <SidePanelModal path={location.pathname} /> */}
</div>
);
}

View File

@ -1,18 +0,0 @@
import React from 'react';
import { Card, CardBody } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
import { useTranslation } from "react-i18next";
const JKHome = () => {
const {t} = useTranslation()
return (
<Card>
<FalconCardHeader title={t('page_title', {ns: 'home'})} titleClass="font-weight-semi-bold" />
<CardBody className="pt-0">
</CardBody>
</Card>
);
};
export default JKHome;

View File

@ -1,33 +1,52 @@
import React from 'react';
import { Col, Row } from 'reactstrap';
import { version } from '../../config';
import { useTranslation } from 'react-i18next';
const JKFooter = () => {
const {t} = useTranslation();
return (
<footer>
<Row noGutters className="justify-content-between text-center fs--1 mt-4 mb-3">
<Col sm="auto">
<p className="mb-0 text-600">
Copyright &copy; {new Date().getFullYear()} JamKazam, Inc. All Rights Reserved
Copyright &copy; {new Date().getFullYear()} JamKazam, Inc. All Rights Reserved.
<span className="ml-2 mb-0 text-600">(v{version})</span>
</p>
</Col>
<Col sm="auto">
<p className="mb-0 text-600">
<p className="mb-0 text-600 text-lowercase">
{' '}
<a href="https://www.jamkazam.com/corp/about" target="_blank">
about
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/about`} target="_blank">
{t('navigation.about', {ns: 'common'})}
</a>{' '}
|{' '}
<a href="https://www.jamkazam.com/corp/privacy" target="_blank">
privacy
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/news`} target="_blank">
{t('navigation.news', {ns: 'common'})}
</a>{' '}
|{' '}
<a href="https://www.jamkazam.com/corp/terms" target="_blank">
terms of service
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/media_center`} target="_blank">
{t('navigation.media', {ns: 'common'})}
</a>{' '}
|{' '}
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/contact`} target="_blank">
{t('navigation.contact', {ns: 'common'})}
</a>{' '}
|{' '}
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/privacy`} target="_blank">
{t('navigation.privacy', {ns: 'common'})}
</a>{' '}
|{' '}
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/terms`} target="_blank">
{t('navigation.terms', {ns: 'common'})}
</a>
|{' '}
<a href={`${process.env.REACT_APP_LEGACY_BASE_URL}/corp/help`} target="_blank">
{t('navigation.help', {ns: 'common'})}
</a>
</p>
{/* <p className="mb-0 text-600">v{version}</p> */}
</Col>
</Row>
</footer>

View File

@ -14,7 +14,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useAuth } from '../../context/UserAuth';
const JKNotificationDropdown = () => {
const { currentUser } = useAuth();
const { currentUser, isAuthenticated } = useAuth();
const dispatch = useDispatch();
const notifications = useSelector(state => state.notification.notifications.slice(0, 5));
@ -52,6 +52,8 @@ const JKNotificationDropdown = () => {
}, [isOpen]);
return (
<>
{isAuthenticated &&
<Dropdown
nav
inNavbar
@ -98,6 +100,8 @@ const JKNotificationDropdown = () => {
</Card>
</DropdownMenu>
</Dropdown>
}
</>
);
};

View File

@ -5,8 +5,10 @@ import { useAuth } from '../../context/UserAuth';
import JKProfileAvatar from '../profile/JKProfileAvatar';
import { useCookies } from 'react-cookie';
import { useHistory } from "react-router-dom";
import { useTranslation } from 'react-i18next';
const ProfileDropdown = () => {
const { t } = useTranslation();
const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState);
const { isAuthenticated, currentUser, setCurrentUser, logout } = useAuth();
@ -50,7 +52,7 @@ const ProfileDropdown = () => {
{/* <DropdownItem tag={Link} to="/pages/settings">
My Profile
</DropdownItem> */}
<DropdownItem onClick={handleLogout}>Sign out</DropdownItem>
<DropdownItem onClick={handleLogout}>{t('signout', { ns: 'auth' })}</DropdownItem>
</div>
</DropdownMenu>
</Dropdown>

View File

@ -5,45 +5,50 @@ import NotificationDropdown from './JKNotificationDropdown';
import LangSwitch from './JKLangSwitch';
//import SettingsAnimatedIcon from './SettingsAnimatedIcon';
//import CartNotification from './CartNotification';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
//import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'react-router-dom';
import AppContext from '../../context/Context';
import classNames from 'classnames';
import { navbarBreakPoint } from '../../config';
//import classNames from 'classnames';
//import { navbarBreakPoint } from '../../config';
//import { useSelector } from 'react-redux';
import { useTranslation } from "react-i18next";
import { useTranslation } from 'react-i18next';
import { useAuth } from '../../context/UserAuth';
const TopNavRightSideNavItem = () => {
//const notifications = useSelector(state => state.notification.notifications.slice(0, 5));
const {t} = useTranslation();
const { t } = useTranslation();
const { isTopNav, isCombo } = useContext(AppContext);
const { isAuthenticated } = useAuth();
//const { isTopNav, isCombo } = useContext(AppContext);
return (
<Nav navbar className="navbar-nav-icons ml-auto flex-row align-items-center">
{/* <NavItem>
<SettingsAnimatedIcon />
</NavItem> */}
<NavbarText className="d-none d-md-inline">{t('keep_jamkazam_improving', {ns: 'common'})}:</NavbarText>
<NavItem className="d-none d-md-inline ml-1 mr-6">
<NavLink>{t('subscribe', {ns: 'common'})}</NavLink>
</NavItem>
{(isCombo || isTopNav) && (
<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" />
{!isAuthenticated && (
<>
<NavbarText className="d-none d-md-inline">{t('keep_jamkazam_improving', { ns: 'common' })}:</NavbarText>
<NavItem className="d-none d-md-inline ml-1 mr-6">
<a className="nav-link" href={`${process.env.REACT_APP_LEGACY_BASE_URL}/signup`} target="_blank">{t('subscribe', { ns: 'common' })}</a>
</NavItem>
</>
)}
<LangSwitch />
{isAuthenticated ? (
<>
<NotificationDropdown />
<ProfileDropdown />
</>
) : (
<NavItem className="d-none d-md-inline ml-6 mr-2">
<NavLink tag={Link} to="/authentication/basic/login">
{t('signin', { ns: 'auth' })}
</NavLink>
<UncontrolledTooltip autohide={false} placement="left" target="changelog">
Changelog
</UncontrolledTooltip>
</NavItem>
)}
{/* <CartNotification /> */}
<LangSwitch />
<NotificationDropdown />
<ProfileDropdown />
</Nav>
);
};

View File

@ -4,6 +4,7 @@ import { NavLink, withRouter } from 'react-router-dom';
import { Collapse, Nav, NavItem, NavLink as BootstrapNavLink } from 'reactstrap';
import AppContext from '../../context/Context';
import NavbarVerticalMenuItem from './NavbarVerticalMenuItem';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const NavbarVerticalMenu = ({ routes, location }) => {
const [openedIndex, setOpenedIndex] = useState(null);
@ -66,7 +67,7 @@ const NavbarVerticalMenu = ({ routes, location }) => {
</NavItem>
</Fragment>
);
});
})
};
NavbarVerticalMenu.propTypes = {

View File

@ -12,34 +12,34 @@ import { fetchPeople } from '../../store/features/peopleSlice';
import JKPeopleSearch from './JKPeopleSearch';
import JKPeopleList from './JKPeopleList';
import JKPeopleSwiper from './JKPeopleSwiper';
import { useResponsive } from '@farfetch/react-context-responsive';
const JKPeople = ({ className }) => {
const [showSearch, setShowSearch] = useState(false);
const [page, setPage] = useState(1);
const [resetFilter, setResetFilter] = useState(false)
const [resetFilter, setResetFilter] = useState(false);
const peopleListRef = useRef();
const dispatch = useDispatch();
const {t} = useTranslation();
const { t } = useTranslation();
const people = useSelector(state => state.people.people);
const totalPages = useSelector(state => state.people.totalPages);
const loadingStatus = useSelector(state => state.people.status);
const loadPeople = React.useCallback(
() => {
if(totalPages !== 0 && page > totalPages){
setPage(totalPages + 1);
return;
}
try {
console.log('BEFORE fetching people');
dispatch(fetchPeople({ page }));
} catch (error) {
console.log('Error fetching people', error);
}
},
[page, totalPages, dispatch]
);
const { greaterThan } = useResponsive();
const loadPeople = React.useCallback(() => {
if (totalPages !== 0 && page > totalPages) {
setPage(totalPages + 1);
return;
}
try {
console.log('BEFORE fetching people');
dispatch(fetchPeople({ page }));
} catch (error) {
console.log('Error fetching people', error);
}
}, [page, totalPages, dispatch]);
useEffect(() => {
loadPeople();
@ -66,8 +66,6 @@ const JKPeople = ({ className }) => {
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
@ -77,17 +75,30 @@ const JKPeople = ({ className }) => {
return (
<Card>
<JKPeopleSearch show={showSearch} setShow={setShowSearch} resetFilter={resetFilter} setResetFilter={setResetFilter} />
<FalconCardHeader
title={t('page_title', {ns: 'people'})}
titleClass="font-weight-bold"
>
<JKPeopleSearch
show={showSearch}
setShow={setShowSearch}
resetFilter={resetFilter}
setResetFilter={setResetFilter}
/>
<FalconCardHeader title={t('page_title', { ns: 'people' })} titleClass="font-weight-bold">
<Form inline className="mt-md-0 mt-3">
<Button color="primary" className="me-2 mr-2 fs--1" onClick={() => setShowSearch(!showSearch)} data-testid="btnUpdateSearch">
{t('update_search', {ns: 'people'})}
<Button
color="primary"
className="me-2 mr-2 fs--1"
onClick={() => setShowSearch(!showSearch)}
data-testid="btnUpdateSearch"
>
{t('update_search', { ns: 'people' })}
</Button>
<Button outline color="secondary" className="fs--1" data-testid="btnResetSearch" onClick={() => setResetFilter(true)}>
{t('reset_filters', {ns: 'people'})}
<Button
outline
color="secondary"
className="fs--1"
data-testid="btnResetSearch"
onClick={() => setResetFilter(true)}
>
{t('reset_filters', { ns: 'people' })}
</Button>
</Form>
</FalconCardHeader>
@ -98,18 +109,18 @@ const JKPeople = ({ className }) => {
) : isIterableArray(people) ? (
//Start Find Friends table hidden on small screens
<>
<Row className="mb-3 justify-content-between d-none d-md-block">
<div className="table-responsive-xl px-2" ref={peopleListRef}>
<JKPeopleList people={people} />
{
loadingStatus === 'loading' && people.length !== 0 &&
<span>loading...</span>
}
</div>
</Row>
<Row className="swiper-container d-block d-md-none">
<JKPeopleSwiper people={people} goNextPage={goNextPage} />
</Row>
{greaterThan.xs ? (
<Row className="mb-3 justify-content-between d-none d-md-block">
<div className="table-responsive-xl px-2" ref={peopleListRef}>
<JKPeopleList people={people} />
{loadingStatus === 'loading' && people.length !== 0 && <span>loading...</span>}
</div>
</Row>
) : (
<Row className="swiper-container d-block d-md-none" data-testid="peopleSwiper">
<JKPeopleSwiper people={people} goNextPage={goNextPage} />
</Row>
)}
</>
) : (
<Row className="p-card">

View File

@ -2,43 +2,32 @@ import React from 'react';
import { Table } from 'reactstrap';
import JKPerson from './JKPerson';
import PropTypes from 'prop-types';
import {useTranslation} from 'react-i18next'
import { useTranslation } from 'react-i18next';
const JKPeopleList = ({ people }) => {
const {t} = useTranslation()
const { t } = useTranslation();
return (
<>
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900">
<tr>
<th scope="col">{t('person_attributes.name', {ns: 'people'})}</th>
<th scope="col" style={{ minWidth: 250 }}>
{t('person_attributes.about', {ns: 'people'})}
</th>
<th scope="col">{t('person_attributes.instruments', {ns: 'people'})}</th>
<th scope="col">{t('actions', {ns: 'common'})}</th>
</tr>
</thead>
<tbody className="list">
{people.map((person, index) => (
<tr className="align-middle" key={`people-list-item-${person.id}`}>
<JKPerson person={person} viewMode="list" />
</tr>
))}
</tbody>
</Table>
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900">
<tr>
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
<th scope="col" style={{ minWidth: 250 }}>
{t('person_attributes.about', { ns: 'people' })}
</th>
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
<th scope="col">{t('actions', { ns: 'common' })}</th>
</tr>
</thead>
<tbody className="list">
{people.map((person) => (
// <tr className="align-middle" key={`people-list-item-${person.id}`}>
<JKPerson person={person} viewMode="list" key={`jk-person-${person.id}`} />
// </tr>
))}
</tbody>
</Table>
{/* <Row>
<Col>
{page < totalPages && (
<a className="ml-2 fw-semi-bold" href="#!" onClick={goNextPage}>
More...
</a>
)}
</Col>
</Row> */}
</>
);
};

View File

@ -19,7 +19,7 @@ const JKPeopleSearch = props => {
defaultValues: {
latency_good: true,
latency_fair: true,
latency_high: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
@ -117,18 +117,18 @@ const JKPeopleSearch = props => {
const lastActiveOpts = [
{ value: '', label: 'Any Range' },
{ value: '1', label: 'Within Last 1 Day' },
{ value: '7', label: 'Within Last 7 Day' },
{ value: '30', label: 'Within Last 30 Day' },
{ value: '90', label: 'Within Last 90 Day' }
{ value: '1', label: 'Within Last 1 Days' },
{ value: '7', label: 'Within Last 7 Days' },
{ value: '30', label: 'Within Last 30 Days' },
{ value: '90', label: 'Within Last 90 Days' }
];
const joinedOpts = [
{ value: '', label: 'Any Range' },
{ value: '1', label: 'Within Last 1 Day' },
{ value: '7', label: 'Within Last 7 Day' },
{ value: '30', label: 'Within Last 30 Day' },
{ value: '90', label: 'Within Last 90 Day' }
{ value: '1', label: 'Within Last 1 Days' },
{ value: '7', label: 'Within Last 7 Days' },
{ value: '30', label: 'Within Last 30 Days' },
{ value: '90', label: 'Within Last 90 Days' }
];
return (
@ -145,7 +145,7 @@ const JKPeopleSearch = props => {
<div className="col-6">
<label className="form-label">
Latency{' '}
<JKTooltip title="Latency is the round-trip travel time over the Internet between you and your friend in a session. JamKazam works best with the lowest latency, measured in milliseconds (ms)" />
<JKTooltip title="Use these checkboxes to search for other musicians by the estimated amount of latency between you and them. Latency is the amount of time it takes for each of your computers to process audio, plus the time it takes for this digital audio to move over the Internet between you." />
</label>
<div className="form-check">
<input
@ -155,7 +155,7 @@ const JKPeopleSearch = props => {
defaultChecked={!isDirty}
onChange={e => setValue('latency_good', e.target.checked)}
/>
<label className="form-check-label">Good (40ms or less)</label>
<label className="form-check-label">Good (less than 40ms)</label>
</div>
<div className="form-check">
<input
@ -165,23 +165,23 @@ const JKPeopleSearch = props => {
defaultChecked={!isDirty}
onChange={e => setValue('latency_fair', e.target.checked)}
/>
<label className="form-check-label">Fair (41-80ms)</label>
<label className="form-check-label">Fair (40-60ms)</label>
</div>
<div className="form-check">
<input
{...register('latency_high')}
type="checkbox"
className="form-check-input"
defaultChecked={!isDirty}
defaultChecked={isDirty}
onChange={e => setValue('latency_high', e.target.checked)}
/>
<label className="form-check-label">High (81ms +)</label>
<label className="form-check-label">Poor (more than 60ms)</label>
</div>
</div>
<div className="col-6">
<label className="form-label">
Skill Level <JKTooltip title="Select the skill levels you want to filter for." />
Skill Level <JKTooltip title="Use these checkboxes to search for other musicians by their skill level." />
</label>
<div className="form-check">
<input
@ -221,7 +221,7 @@ const JKPeopleSearch = props => {
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="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="Use these checkboxes to search for other musicians who play particular instruments. If you do not select any instruments, we search for any/all instruments. If you select multiple instruments, we search for musicians who play any of these instruments." />
</label>
<div className="choices">
<Controller
@ -234,7 +234,7 @@ const JKPeopleSearch = props => {
</div>
<label className="form-label" htmlFor="genres">
Genres{' '}
<JKTooltip title="Select one or more genres to filter for. If this field is blank, all genres will be included." />
<JKTooltip title="Use these checkboxes to search for other musicians who enjoy playing particular musical genres/styles. If you do not select any genres, we search for any/all genres. If you select multiple genres, we search for musicians who play any of these genres." />
</label>
<div className="choices">
<Controller
@ -246,7 +246,7 @@ const JKPeopleSearch = props => {
/>
</div>
<label className="form-label" htmlFor="lastActive">
Last Active <JKTooltip title="Select onefor when the user was last active on JamKazam." />
Last Active <JKTooltip title="Use this list to search for other musicians who have been active on JamKazam within a specified time period. More recent activity makes it more likely they will respond if you message or request to connect." />
</label>
<div className="choices">
<Controller
@ -258,7 +258,7 @@ const JKPeopleSearch = props => {
/>
</div>
<label className="form-label" htmlFor="joined">
Joined JamKazam <JKTooltip title="Select onefor when the user joined JamKazam." />
Joined JamKazam <JKTooltip title="Use this list to search for other musicians based on when they joined JamKazam. This can be useful for finding and connecting with newer users." />
</label>
<div className="choices">
<Controller

View File

@ -4,42 +4,44 @@ import { Row, Col } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useAuth } from '../../context/UserAuth';
import { truncate } from '../../helpers/utils';
import { fetchPerson } from '../../store/features/peopleSlice'
import { useDispatch, useSelector } from 'react-redux'
import { useTranslation } from "react-i18next";
import { fetchPerson } from '../../store/features/peopleSlice';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { useResponsive } from '@farfetch/react-context-responsive';
import JKProfileSidePanel from '../profile/JKProfileSidePanel';
import JKProfileAvatar from '../profile/JKProfileAvatar';
import JKProfileInstrumentsList from '../profile/JKProfileInstrumentsList';
import JKProfileGenres from '../profile/JKProfileGenres';
import JKConnectButton from '../profile/JKConnectButton';
import JKMessageButton from '../profile/JKMessageButton';
import JKLatencyBadge from '../profile/JKLatencyBadge';
import JKLastActiveAgo from '../profile/JKLastActiveAgo';
const JKPerson = props => {
const { id, name, biography, photo_url, instruments, latency_data, last_active_timestamp } = props.person;
const viewMode = props.viewMode;
const { id, name, biography, photo_url, instruments, genres, latency_data, last_active_timestamp } = props.person;
const { currentUser } = useAuth();
const [showSidePanel, setShowSidePanel] = useState(false);
const dispatch = useDispatch()
const {t} = useTranslation()
const user = useSelector(state => state.people.people.find(p => p.id === id))
const dispatch = useDispatch();
const { t } = useTranslation();
const user = useSelector(state => state.people.people.find(p => p.id === id));
const { greaterThan } = useResponsive()
const toggleMoreDetails = async (e) => {
const toggleMoreDetails = async e => {
e.preventDefault();
try {
await dispatch(fetchPerson({userId: id})).unwrap()
await dispatch(fetchPerson({ userId: id })).unwrap();
} catch (error) {
console.log(error);
}
setShowSidePanel(prev => !prev);
};
return (
<>
{viewMode === 'list' ? (
<>
{greaterThan.xs ? (
<tr className="align-middle" key={`people-list-item-${id}`}>
<td className="text-nowrap">
<a href="/#" onClick={toggleMoreDetails} className="d-flex align-items-center mb-1 fs-0">
<div className="avatar avatar-xl">
@ -50,25 +52,28 @@ const JKPerson = props => {
</div>
</a>
<div>
<strong>{t('person_attributes.latency_to_me', {ns: 'people'})}:</strong>
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>
<JKLatencyBadge latencyData={latency_data} />
</div>
<div>
<strong>{t('person_attributes.last_active', {ns: 'people'})}:</strong> <JKLastActiveAgo timestamp={last_active_timestamp} />
<strong>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
<JKLastActiveAgo timestamp={last_active_timestamp} />
</div>
</td>
<td>
{truncate(biography, 200)}
<td data-testid="biography-col">
{truncate(biography, 310)}
{biography && biography.length > 200 && (
<a href="/#" data-testid="linkMore" onClick={toggleMoreDetails}>
{' '} {t('more', {ns: 'common'})} »
{' '} {t('more', { ns: 'common' })} »
</a>
)}
</td>
<td>
<JKProfileInstrumentsList instruments={instruments} />
<td data-testid="instruments-col">
<JKProfileInstrumentsList instruments={instruments} toggleMoreDetails={toggleMoreDetails} />
</td>
<td data-testid="genres-col">
<JKProfileGenres genres={genres} toggleMoreDetails={toggleMoreDetails} />
</td>
<td className="text-nowrap">
<JKConnectButton
currentUser={currentUser}
@ -83,34 +88,50 @@ const JKPerson = props => {
</JKMessageButton>
<a href="/#" onClick={toggleMoreDetails} data-testid="btnMore">
<span className="btn btn-primary fs--1 px-2 py-1" data-bs-toggle="tooltip" title={t('view_profile', {ns: 'people'})}>
<span
className="btn btn-primary fs--1 px-2 py-1"
data-bs-toggle="tooltip"
title={t('view_profile', { ns: 'people' })}
>
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</span>
</a>
</td>
</>
</tr>
) : (
<>
<Row>
<Col>
<div>
<strong>{t('person_attributes.latency_to_me', {ns: 'people'})}:</strong> <JKLatencyBadge latencyData={latency_data} />
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
<JKLatencyBadge latencyData={latency_data} />
</div>
<div>
<strong>{t('person_attributes.last_active', {ns: 'people'})}:</strong> <JKLastActiveAgo timestamp={last_active_timestamp} />
<strong>{t('person_attributes.last_active', { ns: 'people' })}:</strong>{' '}
<JKLastActiveAgo timestamp={last_active_timestamp} />
</div>
</Col>
<Col className="d-none d-sm-block">
<h5>{t('person_attributes.instruments', {ns: 'people'})}</h5>
<JKProfileInstrumentsList instruments={instruments} />
<h5>{t('person_attributes.instruments', { ns: 'people' })}</h5>
<JKProfileInstrumentsList instruments={instruments} toggleMoreDetails={toggleMoreDetails} />
</Col>
</Row>
<Row className="d-block d-sm-none mt-3">
<Col>
<h5>{t('person_attributes.instruments', {ns: 'people'})}</h5>
<JKProfileInstrumentsList instruments={instruments} />
<h5>{t('person_attributes.instruments', { ns: 'people' })}</h5>
<JKProfileInstrumentsList instruments={instruments} toggleMoreDetails={toggleMoreDetails} />
</Col>
</Row>
<Row className="d-block d-sm-none mt-3">
<Col>
<h5>{t('person_attributes.genres', { ns: 'people' })}</h5>
<JKProfileGenres genres={genres} toggleMoreDetails={toggleMoreDetails} />
</Col>
</Row>
<br />
<Row data-testid="mobBiography">
<Col>{biography}</Col>
</Row>
<br />
<JKConnectButton
@ -126,7 +147,11 @@ const JKPerson = props => {
</JKMessageButton>
<a href="/#" onClick={toggleMoreDetails} data-testid="btnMore">
<span className="btn btn-primary fs--1 px-2 py-1" data-bs-toggle="tooltip" title={t('view_profile', {ns: 'people'})}>
<span
className="btn btn-primary fs--1 px-2 py-1"
data-bs-toggle="tooltip"
title={t('view_profile', { ns: 'people' })}
>
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
</span>
</a>
@ -138,8 +163,7 @@ const JKPerson = props => {
};
JKPerson.propTypes = {
person: PropTypes.object.isRequired,
viewMode: PropTypes.string
person: PropTypes.object.isRequired
};
export default JKPerson;

View File

@ -24,11 +24,11 @@ const JKConnectButton = props => {
)
.then(resp => {
if (resp.ok && resp.status === 201) {
toast.success('Friend request was sent successfully');
toast.success(`Success! Your friend request has been sent to ${user.name}.`);
}
})
.catch(err => {
toast.error('An error encountered when sending friend request');
toast.error('An error encountered when sending friend request.');
setPendingFriendRequest(false);
});
};
@ -70,17 +70,17 @@ const JKConnectButton = props => {
className={`btn btn-primary ${cssClasses}`}
data-testid="connect"
disabled={pendingFriendRequest}
onClick={confirm}
onClick={addFriend}
title={buttonTitle()}
>
{addContent}
</button>
<ConnectConfirmModal
{/* <ConnectConfirmModal
show={showConfirmModal}
setShow={setShowConfirmModal}
user={user}
handleOnConfirm={addFriend}
/>
/> */}
</>
) : (
<button
@ -103,33 +103,33 @@ JKConnectButton.propTypes = {
removeContent: PropTypes.element.isRequired
};
const ConnectConfirmModal = props => {
const { className, show, setShow, user, handleOnConfirm } = props;
// const ConnectConfirmModal = props => {
// const { className, show, setShow, user, handleOnConfirm } = props;
//const [modal, setModal] = useState(show);
// //const [modal, setModal] = useState(show);
const toggle = () => setShow(!show);
// const toggle = () => setShow(!show);
const accept = () => {
handleOnConfirm();
};
// const accept = () => {
// handleOnConfirm();
// };
return (
// return (
<Modal isOpen={show} toggle={toggle} className={className} centered={true} data-testid="confirmFriendRequestModal">
<ModalHeader toggle={toggle}>Send Friend Request</ModalHeader>
<ModalBody>Send a friend request to {user.name}.</ModalBody>
<ModalFooter>
<Button color="primary" onClick={accept}>
Yes
</Button>{' '}
<Button color="secondary" onClick={toggle}>
No
</Button>
</ModalFooter>
</Modal>
// <Modal isOpen={show} toggle={toggle} className={className} centered={true} data-testid="confirmFriendRequestModal">
// <ModalHeader toggle={toggle}>Send Friend Request</ModalHeader>
// <ModalBody>Send a friend request to {user.name}.</ModalBody>
// <ModalFooter>
// <Button color="primary" onClick={accept}>
// Yes
// </Button>{' '}
// <Button color="secondary" onClick={toggle}>
// No
// </Button>
// </ModalFooter>
// </Modal>
);
};
// );
// };
export default JKConnectButton;

View File

@ -6,7 +6,7 @@ const JKLatencyBadge = ({ latencyData, showAll }) => {
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 },
me: { label: 'ME', min: -1, max: -1 },
unknown: { label: 'UNKNOWN', min: -2, max: -2 }
};

View File

@ -1,18 +1,36 @@
import React from "react";
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useResponsive } from '@farfetch/react-context-responsive';
const JKProfileGenres = ({genres}) => {
let items = []
const getGenresNames = () => {
for(let genre of genres){
items.push(genre.genre_id)
}
return items.join(', ');
}
return (
<div>{getGenresNames()}</div>
);
}
export default JKProfileGenres;
const JKProfileGenres = ({ genres, showAll, toggleMoreDetails }) => {
const LIMIT = 4;
const [genresToShow, setGenresToShow] = useState([]);
const { greaterThan } = useResponsive();
useEffect(() => {
showAll || !greaterThan.xs ? setGenresToShow(genres) : setGenresToShow(genres.slice(0, LIMIT));
}, [showAll]);
return (
<div data-testid="genreList">
{ genresToShow && <div className="text-capitalize">{genresToShow.map(g => g.description ? g.description : g.genre_id).join(', ')}</div>}
{((!showAll && greaterThan.xs) || (showAll && !greaterThan.xs)) && genres.length > LIMIT && (
<a href="#/" onClick={e => toggleMoreDetails(e)}>
More »
</a>
)}
</div>
);
};
JKProfileGenres.propTypes = {
genres: PropTypes.arrayOf(PropTypes.object).isRequired,
showAll: PropTypes.bool,
toggleMoreDetails: PropTypes.func
};
JKProfileGenres.defaltProps = {
showAll: false
};
export default JKProfileGenres;

View File

@ -1,24 +1,50 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useResponsive, useIsMobile } from '@farfetch/react-context-responsive';
const JKPersonInstrumentsList = ({ instruments }) => {
const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => {
const proficiencies = {
'1': 'Beginner',
'2': 'Intermediate',
'3': 'Expert'
};
const LIMIT = 4;
const [instrumentsToShow, setInstrumentsToShow] = useState([]);
const { greaterThan } = useResponsive();
const { isMobile } = useIsMobile();
useEffect(() => {
showAll || !greaterThan.xs ? setInstrumentsToShow(instruments) : setInstrumentsToShow(instruments.slice(0, LIMIT));
}, [showAll]);
return (
instruments &&
instruments.map(instrument => (
<div key={instrument.instrument_id} className="text-nowrap">
<strong>{instrument.description}:</strong> {proficiencies[instrument.proficiency_level]}
</div>
))
<div data-testid="instrumentList">
{instrumentsToShow &&
instrumentsToShow.map(instrument => (
<div key={instrument.instrument_id} className="text-nowrap">
<strong>{instrument.description}:</strong> {proficiencies[instrument.proficiency_level]}
</div>
))}
{((!showAll && greaterThan.xs) || (showAll && !greaterThan.xs)) && instruments.length > LIMIT && (
<a href="#/" onClick={e => toggleMoreDetails(e)}>
More »
</a>
)}
</div>
);
};
JKPersonInstrumentsList.propTypes = {
instruments: PropTypes.arrayOf(PropTypes.object)
}
instruments: PropTypes.arrayOf(PropTypes.object).isRequired,
showAll: PropTypes.bool,
toggleMoreDetails: PropTypes.func
};
export default JKPersonInstrumentsList
JKPersonInstrumentsList.defaltProps = {
showAll: false
};
export default JKPersonInstrumentsList;

View File

@ -85,7 +85,7 @@ const JKProfileSidePanel = props => {
{user.instruments && user.instruments.length && (
<div data-testid="instruments">
<h5>{t('person_attributes.instruments', {ns: 'people'})}</h5>
<JKProfileInstrumentsList instruments={user.instruments} />
<JKProfileInstrumentsList instruments={user.instruments} showAll={true} />
</div>
)}
@ -93,7 +93,7 @@ const JKProfileSidePanel = props => {
<div data-testid="genres">
<br />
<h5>{t('person_attributes.genres', {ns: 'people'})}</h5>
<JKProfileGenres genres={user.genres} />
<JKProfileGenres genres={user.genres} showAll={true} />
</div>
)}

View File

@ -1,4 +1,5 @@
{
"signup": "Sign up",
"signin": "Sign in"
"signup": "Sign Up",
"signin": "Sign In",
"signout": "Sign Out"
}

View File

@ -7,6 +7,12 @@
"navigation": {
"home": "Home",
"friends": "Friends",
"help": "Help"
"help": "Help",
"about": "About",
"news": "News",
"media": "Media",
"contact": "Contact",
"privacy": "Privacy",
"terms": "Terms of Service"
}
}

View File

@ -1,3 +1,3 @@
{
"page_title": "Home page"
"page_title": "Home"
}

View File

@ -3,7 +3,7 @@
"update_search": "Update Search",
"reset_filters": "Reset Filters",
"view_profile": "View profile",
"add_friend": "Add Friends",
"add_friend": "Send Friend Request",
"disconnect": "Disconnect",
"send_message": "Send Message",
"person_attributes": {

View File

@ -1,4 +1,5 @@
{
"signup": "Sign up",
"signin": "Sign in"
"signup": "Sign Up",
"signin": "Sign In",
"signout": "Sign Out"
}

View File

@ -9,7 +9,7 @@ const AuthBasicLayout = ({location}) => (
<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 width="200"/>
<Logo width={200} />
<Card>
<CardBody className="fs--1 font-weight-normal p-5">
<UserAuth path={location.pathname}>

View File

@ -1,8 +1,7 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
//import DashboardLoadingIndicator from '../components/dashboard/JKDashboardLoadingIndicator';
import Dashboard from '../components/dashboard/JKDashboardMain';
import DashboardMain from '../components/dashboard/JKDashboardMain';
import UserAuth from '../context/UserAuth';
const DashboardLayout = ({ location }) => {
@ -12,7 +11,7 @@ const DashboardLayout = ({ location }) => {
return (
<UserAuth path={location.pathname}>
<Dashboard />
<DashboardMain />
</UserAuth>
);
};

View File

@ -18,7 +18,7 @@ export const helpRoute = {
name: 'Help',
to: '/help',
exact: true,
icon: 'search',
icon: 'question-circle'
}
export const legacyRoute = {

View File

@ -77,6 +77,10 @@ if @search.is_a?(BaseSearch)
[musician.updated_at, musician.last_jam_updated_at].compact.max.to_i
end
end
child :genres => :genres do
attributes :genre_id, :description
end
}
elsif @search.is_a?(BandSearch)

View File

@ -16,7 +16,7 @@ child :performance_samples => :performance_samples do
end
child :genre_players => :genres do
attributes :genre_id, :player_type, :genre_type
attributes :genre_id, :player_type, :genre_type, :description
end
child :band_musicians => :bands do