From b0672a9cb33f7338c52a25a0cc04596ff60328bc Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 6 Dec 2023 04:51:17 +0530 Subject: [PATCH 01/13] fix query string construction when fetching user latencies --- jam-ui/src/helpers/rest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index d56349631..f3cf93464 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -148,7 +148,8 @@ export const getSessions = () => { export const getLatencyToUsers = (currentUserId, participantIds) => { return new Promise((resolve, reject) => { - apiFetch(`/users/${currentUserId}/latencies?user_ids[]=${participantIds}`) + const query = participantIds.map(id => `user_ids[]=${id}`).join('&') + apiFetch(`/users/${currentUserId}/latencies?${query}`) .then(response => resolve(response)) .catch(error => reject(error)) }) From 84481a4fec38081f9ab6e496c04fa297771d323a Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 14 Dec 2023 11:34:42 +0530 Subject: [PATCH 02/13] fix /filter api - prevent error on exception conditions --- ruby/lib/jam_ruby/lib/musician_filter.rb | 31 +++++++++++++------- web/app/controllers/api_search_controller.rb | 5 ++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ruby/lib/jam_ruby/lib/musician_filter.rb b/ruby/lib/jam_ruby/lib/musician_filter.rb index db5a8d092..84b1f4193 100644 --- a/ruby/lib/jam_ruby/lib/musician_filter.rb +++ b/ruby/lib/jam_ruby/lib/musician_filter.rb @@ -8,7 +8,7 @@ module JamRuby me: { label: 'ME', min: -1, max: -1 }, unknown: { label: 'UNKNOWN', min: -2, max: -2 } }; - + def self.filter(user, remote_ip, params) #debugger latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) @@ -17,8 +17,12 @@ module JamRuby offset = [params[:offset].to_i, 0].max limit = [params[:limit].to_i, 20].max filter_params = {} - - filter_params.merge!(from_location: params[:from_location] ? '1' : '0') + + if params[:from_location].present? && params[:from_location].to_s == 'true' + filter_params.merge!(from_location: "1") + else + filter_params.merge!(from_location: "0") + end genres = params[:genres] filter_params.merge!(genres: genres) if genres @@ -68,14 +72,14 @@ module JamRuby [search, latency_data, nextOffset] rescue => exception - logger.debug("Latency exception: #{exception.message}") + #logger.debug("Latency exception: #{exception.message}") Bugsnag.notify(exception) do |report| report.severity = "error" report.add_tab(:latency, { params: params, user_id: user.id, name: user.name, - url: filter_latency_url, + url: latency_url, }) end raise exception @@ -83,9 +87,7 @@ module JamRuby end def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit) - filter_latency_url = "#{APP_CONFIG.latency_data_host}/search_users" - - uri = URI(filter_latency_url) + uri = URI(latency_url) begin http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if APP_CONFIG.latency_data_host.start_with?("https://") @@ -138,22 +140,29 @@ module JamRuby return { data: latency_data, next: nextOffset } else - logger.debug("Latency response failed: #{response}") Bugsnag.notify("LatencyResponseFailed") do |report| report.severity = "faliure" report.add_tab(:latency, { user_id: user_obj.id, name: user_obj.name, - params: params, - url: filter_latency_url, + params: req_params, + url: latency_url, code: response.code, body: response.body, }) end + Rails.logger.debug("Latency response failed: #{response.code} #{response.body}") + raise Exception.new("#{response.code}: #{response.body}") end rescue => exception raise exception end end + + private + + def self.latency_url + "#{APP_CONFIG.latency_data_host}/client#" + end end end \ No newline at end of file diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index a09e21076..39fc026db 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -181,10 +181,9 @@ class ApiSearchController < ApiController def filter begin @search, @latency_data, @nextOffset = JamRuby::MusicianFilter.filter(current_user, request.remote_ip, params) - Rails.logger.debug("=====SEARCH : #{@search.results.inspect}") - Rails.logger.debug("=====LATENCY : #{@latency_data}") respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter' - rescue + rescue => ex + Rails.logger.debug("=====LATENCY EXCEPTION : #{ex.message}") render json: {}, status: 500 end end From 8e792d16584d0bdf57d9bf27bfcabc5e290a0c70 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 14 Dec 2023 20:02:39 +0530 Subject: [PATCH 03/13] fix: validate latency badge props --- jam-ui/src/components/profile/JKLatencyBadge.js | 9 ++++++--- jam-ui/src/components/profile/JKUserLatencyBadge.js | 8 +++++++- jam-ui/src/components/sessions/JKSessionUser.js | 8 +++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/jam-ui/src/components/profile/JKLatencyBadge.js b/jam-ui/src/components/profile/JKLatencyBadge.js index 2d31034ad..c7d185b0b 100644 --- a/jam-ui/src/components/profile/JKLatencyBadge.js +++ b/jam-ui/src/components/profile/JKLatencyBadge.js @@ -31,12 +31,12 @@ const JKLatencyBadge = ({ latencyData, showAll, showBadgeOnly }) => { useEffect(() => { if (latencyData) { - const tot = Math.round(latencyData.ars.internet_latency) + Math.round(latencyData.audio_latency); + const tot = Math.round(latencyData.ars_internet_latency) + Math.round(latencyData.audio_latency); setLatencyLabel(tot); if (showAll) { setLatencyInfo( - `${Math.round(latencyData.ars.internet_latency)}ms (Internet) + ${Math.round( + `${Math.round(latencyData.ars_internet_latency)}ms (Internet) + ${Math.round( latencyData.audio_latency )}ms (audio) = ${tot}ms (total) ` ); @@ -57,7 +57,10 @@ const JKLatencyBadge = ({ latencyData, showAll, showBadgeOnly }) => { }; JKLatencyBadge.propTypes = { - latencyData: PropTypes.object, + latencyData: PropTypes.shape({ + ars_internet_latency: PropTypes.number.isRequired, + audio_latency: PropTypes.number.isRequired + }), showAll: PropTypes.bool, showBadgeOnly: PropTypes.bool }; diff --git a/jam-ui/src/components/profile/JKUserLatencyBadge.js b/jam-ui/src/components/profile/JKUserLatencyBadge.js index f843b8ab8..4b64d883c 100644 --- a/jam-ui/src/components/profile/JKUserLatencyBadge.js +++ b/jam-ui/src/components/profile/JKUserLatencyBadge.js @@ -5,7 +5,13 @@ import PropTypes from 'prop-types'; import JKLatencyBadge from './JKLatencyBadge'; const JKUserLatency = ({user, showAll, showBadgeOnly}) => { - const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id)); + const latencyData = useSelector(state => { + const userLatency = state.latency.latencies.find(l => l.user_id === user.id); + return { + ars_internet_latency: userLatency?.ars?.internet_latency, + audio_latency: userLatency?.audio_latency + } + }); return ( ); diff --git a/jam-ui/src/components/sessions/JKSessionUser.js b/jam-ui/src/components/sessions/JKSessionUser.js index fff23d771..80a98e9ba 100644 --- a/jam-ui/src/components/sessions/JKSessionUser.js +++ b/jam-ui/src/components/sessions/JKSessionUser.js @@ -10,7 +10,13 @@ import PropTypes from 'prop-types'; function JKSessionUser({ user }) { const dispatch = useDispatch(); - const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id)); + const latencyData = useSelector(state => { + const userLatency = state.latency.latencies.find(l => l.user_id === user.id); + return { + ars_internet_latency: userLatency?.ars?.internet_latency, + audio_latency: userLatency?.audio_latency + } + }); const userData = useSelector(state => state.session.people.find(p => p.id === user.id)); const [showSidePanel, setShowSidePanel] = useState(false); const { greaterThan } = useResponsive(); From 06e085fd13af2a7943573f1612e68a399990f3e9 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 27 Dec 2023 18:15:50 +0530 Subject: [PATCH 04/13] fix instrument icon size in mobile view --- jam-ui/cypress/e2e/friends/friends-page.cy.js | 2 +- jam-ui/src/components/profile/JKInstrumentIcon.js | 2 +- .../profile/JKProfileInstrumentsList.js | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/jam-ui/cypress/e2e/friends/friends-page.cy.js b/jam-ui/cypress/e2e/friends/friends-page.cy.js index a80657f00..c322f70d7 100644 --- a/jam-ui/cypress/e2e/friends/friends-page.cy.js +++ b/jam-ui/cypress/e2e/friends/friends-page.cy.js @@ -127,7 +127,7 @@ describe('Friends page with data', () => { cy.get('[data-testid=paginate-next-page]').should('not.exist'); }); - it('show profiles', () => { + it.only('show profiles', () => { cy.contains('Find New Friends').should('exist'); cy.contains('Update Search').should('exist'); cy.contains('Reset Filters').should('exist'); diff --git a/jam-ui/src/components/profile/JKInstrumentIcon.js b/jam-ui/src/components/profile/JKInstrumentIcon.js index e38427187..3703105a1 100644 --- a/jam-ui/src/components/profile/JKInstrumentIcon.js +++ b/jam-ui/src/components/profile/JKInstrumentIcon.js @@ -6,7 +6,7 @@ function JKInstrumentIcon({instrumentId, instrumentName}) { () => { try { const file = require(`../../icons/instruments/icon_instrument_${instrumentId.replace(/\s+/g, '_')}.svg`); - return {instrumentName} + return {instrumentName} } catch (error) { return } diff --git a/jam-ui/src/components/profile/JKProfileInstrumentsList.js b/jam-ui/src/components/profile/JKProfileInstrumentsList.js index 4b8771806..53826955c 100644 --- a/jam-ui/src/components/profile/JKProfileInstrumentsList.js +++ b/jam-ui/src/components/profile/JKProfileInstrumentsList.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { useResponsive, useIsMobile } from '@farfetch/react-context-responsive'; +import { useResponsive } from '@farfetch/react-context-responsive'; import JKInstrumentIcon from './JKInstrumentIcon'; const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => { @@ -15,7 +15,6 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => const [instrumentsToShow, setInstrumentsToShow] = useState([]); const { greaterThan } = useResponsive(); - const { isMobile } = useIsMobile(); useEffect(() => { showAll || !greaterThan.xs ? setInstrumentsToShow(instruments) : setInstrumentsToShow(instruments.slice(0, LIMIT)); @@ -25,11 +24,13 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) =>
{instrumentsToShow && instrumentsToShow.map(instrument => ( -
- - - - {instrument.description}: {proficiencies[instrument.proficiency_level]} +
+
+ +
+
+ {instrument.description}: {proficiencies[instrument.proficiency_level]} +
))} {((!showAll && greaterThan.xs) || (showAll && !greaterThan.xs)) && instruments.length > LIMIT && ( From c07bf115e6b645f82ba310c08ae98e67d217092f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 27 Dec 2023 20:44:15 +0530 Subject: [PATCH 05/13] change session enter button icon --- .../src/assets/img/session/session_entrance.png | Bin 301 -> 0 bytes jam-ui/src/components/sessions/JKSession.js | 4 ++-- jam-ui/src/icons/enter.svg | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 jam-ui/src/assets/img/session/session_entrance.png create mode 100644 jam-ui/src/icons/enter.svg diff --git a/jam-ui/src/assets/img/session/session_entrance.png b/jam-ui/src/assets/img/session/session_entrance.png deleted file mode 100644 index b63575afa50284a2216331723d6c9ff0689fb65e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0tt~ z#W5tK@$Jlwd`yZ0Ea|XH z*S)`3zXjE%Fz5((jPO0hW8GP73`N7e}yp>e!0xJ=AjXX z_CJOh{d^xh+0p{8t(?bxtd;S_^lMSIe+y$g8U#BG7`EkHGvZ@pceuZU=g}dS8HJCl zQrIhu7;o9lFXPM&ozY*h`cnSlm;9UdxpUq1+EUE)b&?yq$9=!WNcM3hL+xAr)@t&{pck8`vFaPnfZvUtU^eTg=tDnm{r-UW|Yo&Ds diff --git a/jam-ui/src/components/sessions/JKSession.js b/jam-ui/src/components/sessions/JKSession.js index dccf2f049..0b98abda4 100644 --- a/jam-ui/src/components/sessions/JKSession.js +++ b/jam-ui/src/components/sessions/JKSession.js @@ -14,7 +14,7 @@ import JKUserLatencyBadge from '../profile/JKUserLatencyBadge'; import JKSessionUser from './JKSessionUser'; import useNativeAppCheck from '../../hooks/useNativeAppCheck'; import { useNativeApp } from '../../context/NativeAppContext'; -import EnterIcon from '../../assets/img/session/session_entrance.png'; +import EnterIcon from '../../icons/enter.svg'; import JKInstrumentIcon from '../profile/JKInstrumentIcon'; function JKSession({ session }) { @@ -189,7 +189,7 @@ function JoinSessionButton({ session }) { className="btn-join btn-sm mr-1 mb-1 pt-1 pb-1 pl-1 pr-1" style={{ cursor: 'pointer' }} > - enter + enter ); } diff --git a/jam-ui/src/icons/enter.svg b/jam-ui/src/icons/enter.svg new file mode 100644 index 000000000..53f30dda9 --- /dev/null +++ b/jam-ui/src/icons/enter.svg @@ -0,0 +1 @@ +login-3 \ No newline at end of file From 9b59ef61210d1471b24deefa915d598153fe1f0f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 6 Dec 2023 04:51:17 +0530 Subject: [PATCH 06/13] fix query string construction when fetching user latencies --- jam-ui/src/helpers/rest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index 409be9f27..586ea2a30 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -167,7 +167,8 @@ export const getSessions = () => { export const getLatencyToUsers = (currentUserId, participantIds) => { return new Promise((resolve, reject) => { - apiFetch(`/users/${currentUserId}/latencies?user_ids[]=${participantIds}`) + const query = participantIds.map(id => `user_ids[]=${id}`).join('&') + apiFetch(`/users/${currentUserId}/latencies?${query}`) .then(response => resolve(response)) .catch(error => reject(error)) }) From 286841c3d0e40a24c4d748d1cfa235dad89a8ef4 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 14 Dec 2023 11:34:42 +0530 Subject: [PATCH 07/13] fix /filter api - prevent error on exception conditions --- ruby/lib/jam_ruby/lib/musician_filter.rb | 31 +++++++++++++------- web/app/controllers/api_search_controller.rb | 5 ++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ruby/lib/jam_ruby/lib/musician_filter.rb b/ruby/lib/jam_ruby/lib/musician_filter.rb index db5a8d092..84b1f4193 100644 --- a/ruby/lib/jam_ruby/lib/musician_filter.rb +++ b/ruby/lib/jam_ruby/lib/musician_filter.rb @@ -8,7 +8,7 @@ module JamRuby me: { label: 'ME', min: -1, max: -1 }, unknown: { label: 'UNKNOWN', min: -2, max: -2 } }; - + def self.filter(user, remote_ip, params) #debugger latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) @@ -17,8 +17,12 @@ module JamRuby offset = [params[:offset].to_i, 0].max limit = [params[:limit].to_i, 20].max filter_params = {} - - filter_params.merge!(from_location: params[:from_location] ? '1' : '0') + + if params[:from_location].present? && params[:from_location].to_s == 'true' + filter_params.merge!(from_location: "1") + else + filter_params.merge!(from_location: "0") + end genres = params[:genres] filter_params.merge!(genres: genres) if genres @@ -68,14 +72,14 @@ module JamRuby [search, latency_data, nextOffset] rescue => exception - logger.debug("Latency exception: #{exception.message}") + #logger.debug("Latency exception: #{exception.message}") Bugsnag.notify(exception) do |report| report.severity = "error" report.add_tab(:latency, { params: params, user_id: user.id, name: user.name, - url: filter_latency_url, + url: latency_url, }) end raise exception @@ -83,9 +87,7 @@ module JamRuby end def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit) - filter_latency_url = "#{APP_CONFIG.latency_data_host}/search_users" - - uri = URI(filter_latency_url) + uri = URI(latency_url) begin http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if APP_CONFIG.latency_data_host.start_with?("https://") @@ -138,22 +140,29 @@ module JamRuby return { data: latency_data, next: nextOffset } else - logger.debug("Latency response failed: #{response}") Bugsnag.notify("LatencyResponseFailed") do |report| report.severity = "faliure" report.add_tab(:latency, { user_id: user_obj.id, name: user_obj.name, - params: params, - url: filter_latency_url, + params: req_params, + url: latency_url, code: response.code, body: response.body, }) end + Rails.logger.debug("Latency response failed: #{response.code} #{response.body}") + raise Exception.new("#{response.code}: #{response.body}") end rescue => exception raise exception end end + + private + + def self.latency_url + "#{APP_CONFIG.latency_data_host}/client#" + end end end \ No newline at end of file diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index a09e21076..39fc026db 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -181,10 +181,9 @@ class ApiSearchController < ApiController def filter begin @search, @latency_data, @nextOffset = JamRuby::MusicianFilter.filter(current_user, request.remote_ip, params) - Rails.logger.debug("=====SEARCH : #{@search.results.inspect}") - Rails.logger.debug("=====LATENCY : #{@latency_data}") respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter' - rescue + rescue => ex + Rails.logger.debug("=====LATENCY EXCEPTION : #{ex.message}") render json: {}, status: 500 end end From b4200a1b87087a59c193bda595c903ec202c9f02 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 14 Dec 2023 20:02:39 +0530 Subject: [PATCH 08/13] fix: validate latency badge props --- jam-ui/src/components/profile/JKLatencyBadge.js | 9 ++++++--- jam-ui/src/components/profile/JKUserLatencyBadge.js | 8 +++++++- jam-ui/src/components/sessions/JKSessionUser.js | 8 +++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/jam-ui/src/components/profile/JKLatencyBadge.js b/jam-ui/src/components/profile/JKLatencyBadge.js index 2d31034ad..c7d185b0b 100644 --- a/jam-ui/src/components/profile/JKLatencyBadge.js +++ b/jam-ui/src/components/profile/JKLatencyBadge.js @@ -31,12 +31,12 @@ const JKLatencyBadge = ({ latencyData, showAll, showBadgeOnly }) => { useEffect(() => { if (latencyData) { - const tot = Math.round(latencyData.ars.internet_latency) + Math.round(latencyData.audio_latency); + const tot = Math.round(latencyData.ars_internet_latency) + Math.round(latencyData.audio_latency); setLatencyLabel(tot); if (showAll) { setLatencyInfo( - `${Math.round(latencyData.ars.internet_latency)}ms (Internet) + ${Math.round( + `${Math.round(latencyData.ars_internet_latency)}ms (Internet) + ${Math.round( latencyData.audio_latency )}ms (audio) = ${tot}ms (total) ` ); @@ -57,7 +57,10 @@ const JKLatencyBadge = ({ latencyData, showAll, showBadgeOnly }) => { }; JKLatencyBadge.propTypes = { - latencyData: PropTypes.object, + latencyData: PropTypes.shape({ + ars_internet_latency: PropTypes.number.isRequired, + audio_latency: PropTypes.number.isRequired + }), showAll: PropTypes.bool, showBadgeOnly: PropTypes.bool }; diff --git a/jam-ui/src/components/profile/JKUserLatencyBadge.js b/jam-ui/src/components/profile/JKUserLatencyBadge.js index f843b8ab8..4b64d883c 100644 --- a/jam-ui/src/components/profile/JKUserLatencyBadge.js +++ b/jam-ui/src/components/profile/JKUserLatencyBadge.js @@ -5,7 +5,13 @@ import PropTypes from 'prop-types'; import JKLatencyBadge from './JKLatencyBadge'; const JKUserLatency = ({user, showAll, showBadgeOnly}) => { - const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id)); + const latencyData = useSelector(state => { + const userLatency = state.latency.latencies.find(l => l.user_id === user.id); + return { + ars_internet_latency: userLatency?.ars?.internet_latency, + audio_latency: userLatency?.audio_latency + } + }); return ( ); diff --git a/jam-ui/src/components/sessions/JKSessionUser.js b/jam-ui/src/components/sessions/JKSessionUser.js index 596fc5422..f9644d3ae 100644 --- a/jam-ui/src/components/sessions/JKSessionUser.js +++ b/jam-ui/src/components/sessions/JKSessionUser.js @@ -10,7 +10,13 @@ import PropTypes from 'prop-types'; function JKSessionUser({ user }) { const dispatch = useDispatch(); - const latencyData = useSelector(state => state.latency.latencies.find(l => l.user_id === user.id)); + const latencyData = useSelector(state => { + const userLatency = state.latency.latencies.find(l => l.user_id === user.id); + return { + ars_internet_latency: userLatency?.ars?.internet_latency, + audio_latency: userLatency?.audio_latency + } + }); const userData = useSelector(state => state.session.people.find(p => p.id === user.id)); const [showSidePanel, setShowSidePanel] = useState(false); const { greaterThan } = useResponsive(); From 3bf694d816a54ffb4b8b39cabf6efa9c905a94e2 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 27 Dec 2023 18:15:50 +0530 Subject: [PATCH 09/13] fix instrument icon size in mobile view --- jam-ui/cypress/e2e/friends/friends-page.cy.js | 2 +- jam-ui/src/components/profile/JKInstrumentIcon.js | 2 +- .../profile/JKProfileInstrumentsList.js | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/jam-ui/cypress/e2e/friends/friends-page.cy.js b/jam-ui/cypress/e2e/friends/friends-page.cy.js index a80657f00..c322f70d7 100644 --- a/jam-ui/cypress/e2e/friends/friends-page.cy.js +++ b/jam-ui/cypress/e2e/friends/friends-page.cy.js @@ -127,7 +127,7 @@ describe('Friends page with data', () => { cy.get('[data-testid=paginate-next-page]').should('not.exist'); }); - it('show profiles', () => { + it.only('show profiles', () => { cy.contains('Find New Friends').should('exist'); cy.contains('Update Search').should('exist'); cy.contains('Reset Filters').should('exist'); diff --git a/jam-ui/src/components/profile/JKInstrumentIcon.js b/jam-ui/src/components/profile/JKInstrumentIcon.js index e38427187..3703105a1 100644 --- a/jam-ui/src/components/profile/JKInstrumentIcon.js +++ b/jam-ui/src/components/profile/JKInstrumentIcon.js @@ -6,7 +6,7 @@ function JKInstrumentIcon({instrumentId, instrumentName}) { () => { try { const file = require(`../../icons/instruments/icon_instrument_${instrumentId.replace(/\s+/g, '_')}.svg`); - return {instrumentName} + return {instrumentName} } catch (error) { return } diff --git a/jam-ui/src/components/profile/JKProfileInstrumentsList.js b/jam-ui/src/components/profile/JKProfileInstrumentsList.js index 4b8771806..53826955c 100644 --- a/jam-ui/src/components/profile/JKProfileInstrumentsList.js +++ b/jam-ui/src/components/profile/JKProfileInstrumentsList.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { useResponsive, useIsMobile } from '@farfetch/react-context-responsive'; +import { useResponsive } from '@farfetch/react-context-responsive'; import JKInstrumentIcon from './JKInstrumentIcon'; const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => { @@ -15,7 +15,6 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => const [instrumentsToShow, setInstrumentsToShow] = useState([]); const { greaterThan } = useResponsive(); - const { isMobile } = useIsMobile(); useEffect(() => { showAll || !greaterThan.xs ? setInstrumentsToShow(instruments) : setInstrumentsToShow(instruments.slice(0, LIMIT)); @@ -25,11 +24,13 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) =>
{instrumentsToShow && instrumentsToShow.map(instrument => ( -
- - - - {instrument.description}: {proficiencies[instrument.proficiency_level]} +
+
+ +
+
+ {instrument.description}: {proficiencies[instrument.proficiency_level]} +
))} {((!showAll && greaterThan.xs) || (showAll && !greaterThan.xs)) && instruments.length > LIMIT && ( From 23e0a7096c9263bf2c40bd011eb8f1a02823337f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 27 Dec 2023 20:44:15 +0530 Subject: [PATCH 10/13] change session enter button icon --- .../src/assets/img/session/session_entrance.png | Bin 301 -> 0 bytes jam-ui/src/components/sessions/JKSession.js | 4 ++-- jam-ui/src/icons/enter.svg | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 jam-ui/src/assets/img/session/session_entrance.png create mode 100644 jam-ui/src/icons/enter.svg diff --git a/jam-ui/src/assets/img/session/session_entrance.png b/jam-ui/src/assets/img/session/session_entrance.png deleted file mode 100644 index b63575afa50284a2216331723d6c9ff0689fb65e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0tt~ z#W5tK@$Jlwd`yZ0Ea|XH z*S)`3zXjE%Fz5((jPO0hW8GP73`N7e}yp>e!0xJ=AjXX z_CJOh{d^xh+0p{8t(?bxtd;S_^lMSIe+y$g8U#BG7`EkHGvZ@pceuZU=g}dS8HJCl zQrIhu7;o9lFXPM&ozY*h`cnSlm;9UdxpUq1+EUE)b&?yq$9=!WNcM3hL+xAr)@t&{pck8`vFaPnfZvUtU^eTg=tDnm{r-UW|Yo&Ds diff --git a/jam-ui/src/components/sessions/JKSession.js b/jam-ui/src/components/sessions/JKSession.js index dccf2f049..0b98abda4 100644 --- a/jam-ui/src/components/sessions/JKSession.js +++ b/jam-ui/src/components/sessions/JKSession.js @@ -14,7 +14,7 @@ import JKUserLatencyBadge from '../profile/JKUserLatencyBadge'; import JKSessionUser from './JKSessionUser'; import useNativeAppCheck from '../../hooks/useNativeAppCheck'; import { useNativeApp } from '../../context/NativeAppContext'; -import EnterIcon from '../../assets/img/session/session_entrance.png'; +import EnterIcon from '../../icons/enter.svg'; import JKInstrumentIcon from '../profile/JKInstrumentIcon'; function JKSession({ session }) { @@ -189,7 +189,7 @@ function JoinSessionButton({ session }) { className="btn-join btn-sm mr-1 mb-1 pt-1 pb-1 pl-1 pr-1" style={{ cursor: 'pointer' }} > - enter + enter ); } diff --git a/jam-ui/src/icons/enter.svg b/jam-ui/src/icons/enter.svg new file mode 100644 index 000000000..53f30dda9 --- /dev/null +++ b/jam-ui/src/icons/enter.svg @@ -0,0 +1 @@ +login-3 \ No newline at end of file From b563f22a32d69ca2bed58ac22d5c28008cdf1038 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sun, 14 Jan 2024 13:32:15 +0530 Subject: [PATCH 11/13] wip: profile edit --- .../components/dashboard/JKDashboardMain.js | 3 + jam-ui/src/components/page/JKEditProfile.js | 654 ++++++++++++++++++ jam-ui/src/helpers/rest.js | 37 + jam-ui/src/i18n/config.js | 4 + jam-ui/src/i18n/locales/en/profile.json | 3 + jam-ui/src/i18n/locales/es/profile.json | 3 + 6 files changed, 704 insertions(+) create mode 100644 jam-ui/src/components/page/JKEditProfile.js create mode 100644 jam-ui/src/i18n/locales/en/profile.json create mode 100644 jam-ui/src/i18n/locales/es/profile.json diff --git a/jam-ui/src/components/dashboard/JKDashboardMain.js b/jam-ui/src/components/dashboard/JKDashboardMain.js index dcd7b1c50..ba780ee43 100644 --- a/jam-ui/src/components/dashboard/JKDashboardMain.js +++ b/jam-ui/src/components/dashboard/JKDashboardMain.js @@ -32,6 +32,8 @@ import JKMusicSessions from '../page/JKMusicSessions'; import JKNewMusicSession from '../page/JKNewMusicSession'; import JKMusicSessionsLobby from '../page/JKMusicSessionsLobby'; +import JKEditProfile from '../page/JKEditProfile'; + //import loadable from '@loadable/component'; //const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes')); //const PublicRoutes = loadable(() => import('../../layouts/JKPublicRoutes')) @@ -195,6 +197,7 @@ function JKDashboardMain() { + {/*Redirect*/} diff --git a/jam-ui/src/components/page/JKEditProfile.js b/jam-ui/src/components/page/JKEditProfile.js new file mode 100644 index 000000000..a1b606507 --- /dev/null +++ b/jam-ui/src/components/page/JKEditProfile.js @@ -0,0 +1,654 @@ +import React, { useRef, useEffect, useState, useReducer } from 'react'; +import { Card, CardBody, Col, Row, CardHeader, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import Select from 'react-select'; +import FalconCardHeader from '../common/FalconCardHeader'; +import { useTranslation } from 'react-i18next'; +import JKCurrentUserAvatar from '../navbar/JKCurrentUserAvatar'; +import { useAuth } from '../../context/UserAuth'; +import { useForm, Controller } from 'react-hook-form'; +import { + getPersonById, + getInstruments, + getGenres, + updateUser, + getCountries, + getRegions, + getCities +} from '../../helpers/rest'; +import { set } from 'lodash'; + +function JKEditProfile() { + const { t } = useTranslation(); + const { currentUser } = useAuth(); + const [musicInstruments, setMusicInstruments] = useState([]); + const [genres, setGenres] = useState([]); + const [instrumentsInitialLoadingDone, setInstrumentsInitialLoadingDone] = useState(false); + const [genreInitialLoadingDone, setGenreInitialLoadingDone] = useState(false); + const [countries, setCountries] = useState([]); + const [regions, setRegions] = useState([]); + const [cities, setCities] = useState([]); + + const [_, forceUpdate] = useReducer(x => x + 1, 0); + + const saveTimeoutRef = useRef(null); + const cityRef = useRef(null); + const regionRef = useRef(null); + + const PROFICIENCIES = [ + { value: '1', label: 'Beginner' }, + { value: '2', label: 'Intermediate' }, + { value: '3', label: 'Advanced' } + ]; + + const { register, control, handleSubmit, setValue, getValues } = useForm({ + defaultValues: { + firstName: '', + lastName: '', + country: '', + state: '', + city: '', + biography: '', + subscribeEmail: false, + virtualBand: false, + traditionalBand: false, + cowriting: false, + instruments: [], + genres: [] + } + }); + + useEffect(() => { + if (currentUser) { + fetchCurentUser().then(userData => { + console.log('_DEBUG userData', userData); + fetchInstruments(); + fetchGenres(); + fetchCountries(); + }); + } + }, [currentUser]); + + const fetchCurentUser = () => { + return new Promise((resolve, reject) => { + getPersonById(currentUser.id) + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setValue('firstName', data.first_name); + setValue('lastName', data.last_name); + setValue('country', data.country); + setValue('state', data.state); + setValue('city', data.city); + setValue('biography', data.biography); + setValue('subscribeEmail', data.subscribe_email); + setValue('virtualBand', data.virtual_band); + setValue('traditionalBand', data.traditional_band); + setValue('cowriting', data.cowriting); + setValue('instruments', data.instruments); + setValue('genres', data.genres); + if (data.country) { + fetchRegions(data.country); + } + if (data.country && data.state) { + fetchCities(data.country, data.state); + } + resolve(getValues()); + }) + .catch(error => reject(error)); + }); + }; + + const fetchInstruments = () => { + getInstruments() + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setMusicInstruments(data); + }) + .catch(error => console.log(error)); + }; + + useEffect(() => { + if (!musicInstruments.length || !getValues('instruments') || instrumentsInitialLoadingDone) return; + const updatedMusicInstruments = musicInstruments.map(musicInstrument => { + const instrument = getValues('instruments').find(instrument => instrument.instrument_id === musicInstrument.id); + if (instrument) { + musicInstrument.proficiency_level = instrument.proficiency_level; + musicInstrument.checked = true; + musicInstrument.instrument_id = instrument.instrument_id; + } else { + musicInstrument.proficiency_level = null; + musicInstrument.checked = false; + musicInstrument.instrument_id = null; + } + return musicInstrument; + }); + setMusicInstruments(updatedMusicInstruments); + setInstrumentsInitialLoadingDone(true); + }, [musicInstruments, getValues('instruments')]); + + const fetchGenres = () => { + getGenres() + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setGenres(data); + }) + .catch(error => { + console.log(error); + }); + }; + + useEffect(() => { + if (!genres.length || !getValues('genres') || genreInitialLoadingDone) return; + const updatedGenres = genres.map(genre => { + const userGenre = getValues('genres').find(userGenre => userGenre.genre_id === genre.id); + if (userGenre) { + genre.checked = true; + } else { + genre.checked = false; + } + genre.genre_id = genre.id; + return genre; + }); + setGenres(updatedGenres); + setGenreInitialLoadingDone(true); + }, [genres, getValues('genres')]); + + const fetchCountries = () => { + getCountries() + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setCountries(data.countriesx); + }) + .catch(error => console.log(error)); + }; + + const fetchRegions = countryCode => { + getRegions(countryCode) + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setRegions(data.regions); + }) + .catch(error => console.log(error)); + }; + + const fetchCities = (country, region) => { + getCities(country, region) + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + setCities(data.cities); + }) + .catch(error => console.log(error)); + }; + + const onSubmit = data => console.log(data); + + const handleInstrumentSelect = (e, musicInstrument) => { + //alert(e.target.checked) + if (e.target.checked) { + const userInstruments = getValues('instruments'); + const thisInstrument = userInstruments.find( + instrument => instrument.instrument_id === musicInstrument.instrument_id + ); + if (thisInstrument) return; + const { id } = musicInstrument; + const updatedInstruments = [...userInstruments, { ...musicInstrument, instrument_id: id }]; + setValue('instruments', updatedInstruments); + } else { + const updatedInstruments = getValues('instruments').filter( + instrument => instrument.instrument_id !== musicInstrument.instrument_id + ); + setValue('instruments', updatedInstruments); + } + + const updatedMusicInstruments = musicInstruments.map(instrument => { + if (instrument.id === musicInstrument.id) { + instrument.checked = e.target.checked; + } else { + instrument.checked = instrument.checked; + } + return instrument; + }); + + setMusicInstruments(updatedMusicInstruments); + handleChange(); + }; + + const handleInstrumentProficiencyChange = (option, musicInstrument) => { + const userInstrument = getValues('instruments').find(instrument => instrument.instrument_id === musicInstrument.id); + if (!userInstrument) return; + userInstrument.proficiency_level = option.value; + forceUpdate(); + handleChange(); + }; + + const handleGenreChange = (e, genre) => { + if (e.target.checked) { + const userGenres = getValues('genres'); + const thisGenre = userGenres.find(userGenre => userGenre.genre_id === genre.genre_id); + if (thisGenre) return; + const updatedGenres = [...userGenres, { ...genre }]; + setValue('genres', updatedGenres); + } else { + const updatedGenres = getValues('genres').filter(userGenre => userGenre.genre_id !== genre.genre_id); + setValue('genres', updatedGenres); + } + + const updatedGenres = genres.map(genreItem => { + if (genreItem.genre_id === genre.genre_id) { + genreItem.checked = e.target.checked; + } else { + genreItem.checked = genreItem.checked; + } + return genreItem; + }); + + setGenres(updatedGenres); + handleChange(); + }; + + const handleTextInputChage = () => { + clearTimeout(saveTimeoutRef.current); + saveTimeoutRef.current = setTimeout(() => { + handleChange(); + }, 2000); + }; + + const skipRegionChange = useRef(false); + + const handleCountryChange = selectedOpt => { + const country = selectedOpt.value; + setValue('country', country); + setValue('state', null); + setValue('city', null); + setRegions([]); + skipRegionChange.current = true; + regionRef.current.select.clearValue(); + setCities([]); + cityRef.current.select.clearValue(); + fetchRegions(country.value); + forceUpdate(); + handleChange(); + }; + + const handleRegionChange = selectedOpt => { + if (skipRegionChange.current) { + skipRegionChange.current = false; + return; + } + const state = selectedOpt.value; + const country = getValues('country'); + setValue('state', state); + setValue('city', null); + setCities([]); + cityRef.current.select.clearValue(); + fetchCities(country, state); + handleChange(); + }; + + const handleChange = () => { + const params = getValues(); + const data = { + first_name: params.firstName, + last_name: params.lastName, + country: params.country, + state: params.state, + city: params.city, + biography: params.biography, + subscribe_email: params.subscribeEmail, + virtual_band: params.virtualBand, + traditional_band: params.traditionalBand, + cowriting: params.cowriting, + instruments: params.instruments, + genres: params.genres + }; + console.log('_DEBUG data', data); + // updateUser(currentUser.id, data).then(response => { + // if (response.ok) { + // return response.json(); + // } + // }).then(data => { + // console.log('_DEBUG data', data); + // } + // ).catch(error => console.log(error)); + }; + + return ( + + + +
+ + + + +
Basics
+
+ + + + + + ( + { + onChange(e); + handleTextInputChage(); + }} + /> + )} + /> + + + + + + ( + { + onChange(e); + handleTextInputChage(); + }} + /> + )} + /> + + + +
+
+ +
+ +
+ +
+ + + + + {countries.length > 0 && ( + { + const country = countries.find(country => country.countrycode === value); + return ( + { + return { value: region.region, label: region.region }; + })} + /> + ); + }} + /> + )} + + + + + + ( + { + onChange(e); + handleTextInputChage(); + }} + /> + )} + /> + + + ( + { + onChange(e); + handleChange(); + }} + /> + )} + /> + + +
+
+ + + +
Interests
+
+ + + ( + { + onChange(e); + handleChange(); + }} + type="checkbox" + /> + )} + /> + + + + ( + { + onChange(e); + handleChange(); + }} + type="checkbox" + /> + )} + /> + + + + ( + { + onChange(e); + handleChange(); + }} + type="checkbox" + /> + )} + /> + + + +
+ + + + +
Instruments
+
+ + + {instrumentsInitialLoadingDone && + musicInstruments.map((musicInstrument, index) => { + return ( + + + { + handleInstrumentSelect(e, musicInstrument); + }} + type="checkbox" + checked={musicInstrument.checked} + /> + + + + + { + handleGenreChange(e, genre); + }} + type="checkbox" + checked={genre.checked} + /> + + + + ); + })} + + +
+ +
+
+
+
+ ); +} + +export default JKEditProfile; diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index 586ea2a30..f6ec11d3d 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -182,3 +182,40 @@ export const getLobbyChatMessages = (options = {}) => { .catch(error => reject(error)) }) } + + +export const updateUser = (userId, options) => { + return new Promise((resolve, reject) => { + apiFetch(`/users/${userId}`, { + method: 'PATCH', + body: JSON.stringify(options) + }) + .then(response => resolve(response)) + .catch(error => reject(error)) + }) +} + + +export const getCountries = () => { + return new Promise((resolve, reject) => { + apiFetch(`/countries`) + .then(response => resolve(response)) + .catch(error => reject(error)) + }) +} + +export const getRegions = (countryId) => { + return new Promise((resolve, reject) => { + apiFetch(`/regions?country=${countryId}`) + .then(response => resolve(response)) + .catch(error => reject(error)) + }) +} + +export const getCities = (countryId, regionId) => { + return new Promise((resolve, reject) => { + apiFetch(`/cities?country=${countryId}®ion=${regionId}`) + .then(response => resolve(response)) + .catch(error => reject(error)) + }) +} \ No newline at end of file diff --git a/jam-ui/src/i18n/config.js b/jam-ui/src/i18n/config.js index 8d6c08072..56c791242 100644 --- a/jam-ui/src/i18n/config.js +++ b/jam-ui/src/i18n/config.js @@ -7,6 +7,7 @@ import peopleTranslationsEN from './locales/en/people.json' import authTranslationsEN from './locales/en/auth.json' import sessTranslationsEN from './locales/en/sessions.json' import unsubscribeTranslationsEN from './locales/en/unsubscribe.json' +import profileEN from './locales/en/profile.json' import commonTranslationsES from './locales/es/common.json' import homeTranslationsES from './locales/es/home.json' @@ -14,6 +15,7 @@ import peopleTranslationsES from './locales/es/people.json' import authTranslationsES from './locales/es/auth.json' import sessTranslationsES from './locales/es/sessions.json' import unsubscribeTranslationsES from './locales/es/unsubscribe.json' +import profileES from './locales/es/profile.json' i18n.use(initReactI18next).init({ fallbackLng: 'en', @@ -27,6 +29,7 @@ i18n.use(initReactI18next).init({ auth: authTranslationsEN, sessions: sessTranslationsEN, unsubscribe: unsubscribeTranslationsEN, + profile: profileEN }, es: { //translations: require('./locales/es/translations.json') @@ -36,6 +39,7 @@ i18n.use(initReactI18next).init({ auth: authTranslationsES, sessions: sessTranslationsES, unsubscribe: unsubscribeTranslationsES, + profile: profileES } }, //ns: ['translations'], diff --git a/jam-ui/src/i18n/locales/en/profile.json b/jam-ui/src/i18n/locales/en/profile.json new file mode 100644 index 000000000..2eade7bb7 --- /dev/null +++ b/jam-ui/src/i18n/locales/en/profile.json @@ -0,0 +1,3 @@ +{ + "page_title": "Profile" +} diff --git a/jam-ui/src/i18n/locales/es/profile.json b/jam-ui/src/i18n/locales/es/profile.json new file mode 100644 index 000000000..2eade7bb7 --- /dev/null +++ b/jam-ui/src/i18n/locales/es/profile.json @@ -0,0 +1,3 @@ +{ + "page_title": "Profile" +} From aeb2e506922471a8dde9e23bbc854ff0c491a5e1 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sat, 3 Feb 2024 18:29:57 +0530 Subject: [PATCH 12/13] account identity update allow users to update their JamKazam email and password securly. --- jam-ui/src/components/common/JKModalDialog.js | 5 +- .../components/dashboard/JKDashboardMain.js | 2 + jam-ui/src/components/page/JKEditAccount.js | 46 ++++ jam-ui/src/components/profile/JKEditEmail.js | 165 ++++++++++++ .../src/components/profile/JKEditPassword.js | 191 ++++++++++++++ jam-ui/src/helpers/apiFetch.js | 1 - jam-ui/src/helpers/initFA.js | 4 +- jam-ui/src/helpers/rest.js | 241 ++++++++++-------- jam-ui/src/i18n/config.js | 8 +- jam-ui/src/i18n/locales/en/account.json | 11 + jam-ui/src/i18n/locales/es/account.json | 4 + web/app/controllers/api_users_controller.rb | 7 +- web/config/routes.rb | 1 + 13 files changed, 574 insertions(+), 112 deletions(-) create mode 100644 jam-ui/src/components/page/JKEditAccount.js create mode 100644 jam-ui/src/components/profile/JKEditEmail.js create mode 100644 jam-ui/src/components/profile/JKEditPassword.js create mode 100644 jam-ui/src/i18n/locales/en/account.json create mode 100644 jam-ui/src/i18n/locales/es/account.json diff --git a/jam-ui/src/components/common/JKModalDialog.js b/jam-ui/src/components/common/JKModalDialog.js index 16c0377ca..15821ba45 100644 --- a/jam-ui/src/components/common/JKModalDialog.js +++ b/jam-ui/src/components/common/JKModalDialog.js @@ -3,7 +3,8 @@ import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; -const JKModalDialog = ({ title, children, show, onToggle, showFooter }) => { +const JKModalDialog = (args) => { + const { show, title, children, onToggle, showFooter, ...rest } = args; const [modal, setModal] = useState(show); const toggle = () => { @@ -18,7 +19,7 @@ const JKModalDialog = ({ title, children, show, onToggle, showFooter }) => { }, [show]); return ( - + {title} {children} {showFooter && ( diff --git a/jam-ui/src/components/dashboard/JKDashboardMain.js b/jam-ui/src/components/dashboard/JKDashboardMain.js index ba780ee43..b736b6b52 100644 --- a/jam-ui/src/components/dashboard/JKDashboardMain.js +++ b/jam-ui/src/components/dashboard/JKDashboardMain.js @@ -33,6 +33,7 @@ import JKNewMusicSession from '../page/JKNewMusicSession'; import JKMusicSessionsLobby from '../page/JKMusicSessionsLobby'; import JKEditProfile from '../page/JKEditProfile'; +import JKEditAccount from '../page/JKEditAccount'; //import loadable from '@loadable/component'; //const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes')); @@ -198,6 +199,7 @@ function JKDashboardMain() { + {/*Redirect*/} diff --git a/jam-ui/src/components/page/JKEditAccount.js b/jam-ui/src/components/page/JKEditAccount.js new file mode 100644 index 000000000..82d2ac005 --- /dev/null +++ b/jam-ui/src/components/page/JKEditAccount.js @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { Row, Col, Card, CardBody } from 'reactstrap'; +import FalconCardHeader from '../common/FalconCardHeader'; +import { useTranslation } from 'react-i18next'; + +import JKEditEmail from '../profile/JKEditEmail'; +import JKEditPassword from '../profile/JKEditPassword'; +import JKModalDialog from '../common/JKModalDialog'; + +const JKEditAccount = () => { + const { t } = useTranslation('account'); + const [alertText, setAlertText] = useState("") + + const [showAlert, setShowAlert] = useState(false); + const toggleAlert = () => setShowAlert(!showAlert); + + return ( + <> + + + + + + + + + + + + + + + + {alertText} + + + ); +}; + +export default JKEditAccount; diff --git a/jam-ui/src/components/profile/JKEditEmail.js b/jam-ui/src/components/profile/JKEditEmail.js new file mode 100644 index 000000000..296ead950 --- /dev/null +++ b/jam-ui/src/components/profile/JKEditEmail.js @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from 'react'; +import { + + Card, + CardHeader, + CardBody, + Form, + FormGroup, + Label, + Input, + InputGroup, + InputGroupAddon +} from 'reactstrap'; + +import { useTranslation } from 'react-i18next'; +import { useAuth } from '../../context/UserAuth'; +import { useForm, Controller } from 'react-hook-form'; +import { postUpdateAccountEmail } from '../../helpers/rest'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const JKEditEmail = ({setAlert, toggleAlert}) => { + const { t } = useTranslation('account'); + const { currentUser } = useAuth(); + const [user, setUser] = useState(null); + const [showEmailPassword, setShowEmailPassword] = useState(false); + const [submitting, setSubmitting] = useState(false) + + const { + handleSubmit, + control, + formState: { errors }, + setError, + setValue, + } = useForm({ + defaultValues: { + current_password: '', + new_email: '' + } + }); + + useEffect(() => { + if (currentUser) { + setUser(currentUser); + } + }, [currentUser]); + + const onSubmitEmail = (data) => { + setSubmitting(true) + //post + const { new_email, current_password } = data; + postUpdateAccountEmail(currentUser.id, { email: new_email, current_password }) + .then(response => { + setAlert('A confirmation email has been sent to your email. Please check your email and click the link to confirm your new email address.'); + setValue('current_password', ''); + setValue('new_email', ''); + toggleAlert() + }) + .catch(async error => { + const errorResp = await error.json() + console.log(errorResp) + if(errorResp.errors){ + const errors = errorResp.errors; + if(errors.current_password && errors.current_password.length){ + errors.current_password.forEach(error => { + setError('current_password', { + type: 'manual', + message: `Current password ${error}` + }) + }) + } + if(errors.update_email && errors.update_email.length){ + errors.update_email.forEach(error => { + setError('new_email', { + type: 'manual', + message: `New email ${error}` + }) + }) + } + } + + }).finally(() => { + setSubmitting(false) + }) + }; + + + return ( + + +
Email
+
+ + + To update the email associated with your account, enter your current password (for security reasons) and the + new email, and click the "Save Email" button. + + +
+ + + ( + + + { + setShowEmailPassword(!showEmailPassword); + }} + > + + + + + + )} + /> + {errors.current_password &&
{errors.current_password.message}
} +
+ + + ( + + )} + /> + {errors.new_email &&
{errors.new_email.message}
} +
+
+ + + { submitting && } + +
+
+
+
+ ); +}; + +export default JKEditEmail; diff --git a/jam-ui/src/components/profile/JKEditPassword.js b/jam-ui/src/components/profile/JKEditPassword.js new file mode 100644 index 000000000..55f199d81 --- /dev/null +++ b/jam-ui/src/components/profile/JKEditPassword.js @@ -0,0 +1,191 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardHeader, CardBody, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap'; + +import { useTranslation } from 'react-i18next'; +import { useAuth } from '../../context/UserAuth'; +import { useForm, Controller } from 'react-hook-form'; +import { postUpdateAccountPassword, requestPasswordReset } from '../../helpers/rest'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const JKEditPassword = ({ setAlert, toggleAlert }) => { + const { t } = useTranslation('account'); + const { currentUser } = useAuth(); + const [user, setUser] = useState(null); + + const [showPassword, setShowPassword] = useState(false); + const [showNewPassword, setShowNewPassword] = useState(false); + const [submitting, setSubmitting] = useState(false); + + const { + handleSubmit: handleSubmit, + control: control, + formState: { errors }, + setError, + setValue + } = useForm({ + defaultValues: { + current_password: '', + new_password: '' + } + }); + + useEffect(() => { + if (currentUser) { + setUser(currentUser); + } + }, [currentUser]); + + const onSubmitPassword = data => { + setSubmitting(true); + const { new_password, current_password } = data; + postUpdateAccountPassword(currentUser.id, { current_password, new_password }) + .then(response => { + setAlert('Your password has been successfully updated.'); + setValue('current_password', ''); + setValue('new_password', ''); + toggleAlert(); + }) + .catch(async error => { + console.log(error); + const errorResp = await error.json(); + console.log(errorResp); + if (errorResp.errors) { + const errors = errorResp.errors; + if (errors.current_password && errors.current_password.length) { + errors.current_password.forEach(error => { + setError('current_password', { + type: 'manual', + message: `Current password ${error}` + }); + }); + } + if (errors.password && errors.password.length) { + errors.password.forEach(error => { + setError('new_password', { + type: 'manual', + message: `New password ${error}` + }); + }); + } + } + }) + .finally(() => { + setSubmitting(false); + }); + }; + + const requestResetPassword = async (e) => { + e.preventDefault() + if (!currentUser) { + return; + } + try { + await requestPasswordReset(currentUser.id); + setAlert( + 'A password reset email has been sent to your email. Please check your email and click the link to reset your password.' + ); + toggleAlert(); + } catch (error) { + console.log(error); + } + }; + + return ( + + +
Password
+
+ + + To update the password associated with your account, enter your current password (for security reasons) and + the new password, and click the "Save Password" button. + + +
+ + + ( + + + { + setShowPassword(!showPassword); + }} + > + + + + + + )} + /> + {errors.current_password && ( +
+ {errors.current_password.message} +
+ )} +
+ + + + ( + + + { + setShowNewPassword(!showNewPassword); + }} + > + + + + + + )} + /> + {errors.new_password && ( +
+ {errors.new_password.message} +
+ )} +
+
+ + {submitting && } +
+
+
+ + If you can not remember your current password, Click here to reset + your password, and you will receive an email with instructions on how to reset your password to the email + address associated with your JamKazam account. + +
+
+
+ ); +}; + +export default JKEditPassword; diff --git a/jam-ui/src/helpers/apiFetch.js b/jam-ui/src/helpers/apiFetch.js index 611501524..3070e0a66 100644 --- a/jam-ui/src/helpers/apiFetch.js +++ b/jam-ui/src/helpers/apiFetch.js @@ -41,7 +41,6 @@ function secureFetch(path, options) { break; default: console.log('apiFetch Some error occured'); - break; } //here you also can thorow custom error too reject(response); diff --git a/jam-ui/src/helpers/initFA.js b/jam-ui/src/helpers/initFA.js index 9f5c0bdc3..3c3f94fa5 100644 --- a/jam-ui/src/helpers/initFA.js +++ b/jam-ui/src/helpers/initFA.js @@ -87,6 +87,7 @@ import { faExclamationTriangle, faExternalLinkAlt, faEye, + faEyeSlash, faFileAlt, faFileArchive, faFilePdf, @@ -158,7 +159,7 @@ import { faRecordVinyl, faAddressCard, faVolumeUp, - faSpinner + faSpinner, } from '@fortawesome/free-solid-svg-icons'; //import { faAcousticGuitar } from "../icons"; @@ -245,6 +246,7 @@ library.add( faFilePdf, faFileAlt, faEye, + faEyeSlash, faCaretUp, faCodeBranch, faExclamationTriangle, diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index f6ec11d3d..a6940dfdb 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -1,12 +1,12 @@ -import apiFetch from "./apiFetch"; +import apiFetch from './apiFetch'; -export const getMusicians = (page) => { +export const getMusicians = page => { return new Promise((resolve, reject) => { apiFetch(`/search/musicians?results=true`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; // export const getPeople = (page) => { // return new Promise((resolve, reject) => { @@ -16,48 +16,48 @@ export const getMusicians = (page) => { // }) // } -export const getPersonById = (id) => { - return new Promise((resolve, reject) => ( +export const getPersonById = id => { + return new Promise((resolve, reject) => apiFetch(`/users/${id}/profile?show_teacher=true`) - .then(response => resolve(response)) - .catch(error => reject(error)) - )) -} + .then(response => resolve(response)) + .catch(error => reject(error)) + ); +}; export const getPeople = ({ data, offset, limit } = {}) => { return new Promise((resolve, reject) => { apiFetch(`/filter?offset=${offset}&limit=${limit}`, { method: 'POST', - body: JSON.stringify(data) + body: JSON.stringify(data) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getPeopleIndex = () => { return new Promise((resolve, reject) => { apiFetch(`/users`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getGenres = () => { return new Promise((resolve, reject) => { apiFetch('/genres') - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getInstruments = () => { return new Promise((resolve, reject) => { apiFetch('/instruments') - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; // export const getCurrentUser = () => { // return new Promise((resolve, reject) => { @@ -67,13 +67,13 @@ export const getInstruments = () => { // }) // } -export const getFriends = (userId) => { +export const getFriends = userId => { return new Promise((resolve, reject) => { apiFetch(`/users/${userId}/friends`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const addFriend = (userId, friendId) => { return new Promise((resolve, reject) => { @@ -81,108 +81,106 @@ export const addFriend = (userId, friendId) => { method: 'POST', body: JSON.stringify({ friend_id: friendId }) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .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)) - }) -} + .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)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; -export const createTextMessage = (options) => { +export const createTextMessage = options => { return new Promise((resolve, reject) => { apiFetch(`/text_messages`, { - method: "POST", + method: 'POST', body: JSON.stringify(options) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; -export const createLobbyChatMessage = (options) => { +export const createLobbyChatMessage = options => { return new Promise((resolve, reject) => { apiFetch(`/chat`, { - method: "POST", + method: 'POST', body: JSON.stringify(options) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getNotifications = (userId, options = {}) => { return new Promise((resolve, reject) => { apiFetch(`/users/${userId}/notifications?${new URLSearchParams(options)}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) - -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const acceptFriendRequest = (userId, options = {}) => { return new Promise((resolve, reject) => { - const { status, friend_request_id } = options + const { status, friend_request_id } = options; apiFetch(`/users/${userId}/friend_requests/${friend_request_id}`, { method: 'POST', body: JSON.stringify({ status }) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const deleteNotification = (userId, notificationId) => { return new Promise((resolve, reject) => { apiFetch(`/users/${userId}/notifications/${notificationId}`, { - method: 'DELETE', + method: 'DELETE' }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getSessions = () => { return new Promise((resolve, reject) => { apiFetch(`/sessions`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getLatencyToUsers = (currentUserId, participantIds) => { return new Promise((resolve, reject) => { - const query = participantIds.map(id => `user_ids[]=${id}`).join('&') + const query = participantIds.map(id => `user_ids[]=${id}`).join('&'); apiFetch(`/users/${currentUserId}/latencies?${query}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getLobbyChatMessages = (options = {}) => { return new Promise((resolve, reject) => { - console.log('getLobbyChatMessages', options) + console.log('getLobbyChatMessages', options); apiFetch(`/chat?${new URLSearchParams(options)}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} - + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const updateUser = (userId, options) => { return new Promise((resolve, reject) => { @@ -190,32 +188,65 @@ export const updateUser = (userId, options) => { method: 'PATCH', body: JSON.stringify(options) }) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} - + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getCountries = () => { return new Promise((resolve, reject) => { apiFetch(`/countries`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; -export const getRegions = (countryId) => { +export const getRegions = countryId => { return new Promise((resolve, reject) => { apiFetch(`/regions?country=${countryId}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) -} + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; export const getCities = (countryId, regionId) => { return new Promise((resolve, reject) => { apiFetch(`/cities?country=${countryId}®ion=${regionId}`) - .then(response => resolve(response)) - .catch(error => reject(error)) - }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + +export const postUpdateAccountEmail = (userId, options) => { + const { email, current_password } = options; + return new Promise((resolve, reject) => { + apiFetch(`/users/${userId}/update_email`, { + method: 'POST', + body: JSON.stringify({ update_email: email, current_password }) + }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + +export const postUpdateAccountPassword = (userId, options) => { + const { new_password, current_password } = options; + return new Promise((resolve, reject) => { + apiFetch(`/users/${userId}/set_password`, { + method: 'POST', + body: JSON.stringify({ old_password: current_password, new_password, new_password_confirm: new_password }) + }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + +export const requestPasswordReset = (userId) => { + return new Promise((resolve, reject) => { + apiFetch(`/users/${userId}/request_reset_password`, { + method: 'POST', + }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); } \ No newline at end of file diff --git a/jam-ui/src/i18n/config.js b/jam-ui/src/i18n/config.js index 56c791242..35dfabd95 100644 --- a/jam-ui/src/i18n/config.js +++ b/jam-ui/src/i18n/config.js @@ -8,6 +8,7 @@ import authTranslationsEN from './locales/en/auth.json' import sessTranslationsEN from './locales/en/sessions.json' import unsubscribeTranslationsEN from './locales/en/unsubscribe.json' import profileEN from './locales/en/profile.json' +import accountEN from './locales/en/account.json' import commonTranslationsES from './locales/es/common.json' import homeTranslationsES from './locales/es/home.json' @@ -16,6 +17,7 @@ import authTranslationsES from './locales/es/auth.json' import sessTranslationsES from './locales/es/sessions.json' import unsubscribeTranslationsES from './locales/es/unsubscribe.json' import profileES from './locales/es/profile.json' +import accountES from './locales/es/account.json' i18n.use(initReactI18next).init({ fallbackLng: 'en', @@ -29,7 +31,8 @@ i18n.use(initReactI18next).init({ auth: authTranslationsEN, sessions: sessTranslationsEN, unsubscribe: unsubscribeTranslationsEN, - profile: profileEN + profile: profileEN, + account: accountEN }, es: { //translations: require('./locales/es/translations.json') @@ -39,7 +42,8 @@ i18n.use(initReactI18next).init({ auth: authTranslationsES, sessions: sessTranslationsES, unsubscribe: unsubscribeTranslationsES, - profile: profileES + profile: profileES, + account: accountES } }, //ns: ['translations'], diff --git a/jam-ui/src/i18n/locales/en/account.json b/jam-ui/src/i18n/locales/en/account.json new file mode 100644 index 000000000..a78f96aae --- /dev/null +++ b/jam-ui/src/i18n/locales/en/account.json @@ -0,0 +1,11 @@ +{ + "identity": { + "page_title": "Identity", + "modals": { + "update_notification": { + "title": "Account Identity Updated" + } + } + + } +} \ No newline at end of file diff --git a/jam-ui/src/i18n/locales/es/account.json b/jam-ui/src/i18n/locales/es/account.json new file mode 100644 index 000000000..2ddff10c2 --- /dev/null +++ b/jam-ui/src/i18n/locales/es/account.json @@ -0,0 +1,4 @@ + +{ + +} \ No newline at end of file diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 4e2eb02b2..6ef339b26 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -12,7 +12,7 @@ class ApiUsersController < ApiController :friend_show, :friend_destroy, # friends :notification_index, :notification_destroy, # notifications :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations - :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, + :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :request_reset_password, :share_session, :share_recording, :affiliate_report, :audio_latency, :get_latencies, :broadcast_notification, :redeem_giftcard] @@ -333,6 +333,11 @@ class ApiUsersController < ApiController respond_with responder: ApiResponder, :status => 204 end + def request_reset_password + User.reset_password(current_user.email, ApplicationHelper.base_uri(request)) + respond_with current_user, responder: ApiResponder, :status => 200 + end + ###################### AUTHENTICATION ################### def auth_session_create @user = User.authenticate(params[:email], params[:password]) diff --git a/web/config/routes.rb b/web/config/routes.rb index aa85799ef..61d17ce14 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -403,6 +403,7 @@ Rails.application.routes.draw do match '/users/complete/:signup_token' => 'api_users#complete', as: 'complete', via: 'post' match '/users/authorizations/google' => 'api_users#google_auth', :via => :get match '/users/:id/set_password' => 'api_users#set_password', :via => :post + match '/users/:id/request_reset_password' => 'api_users#request_reset_password', :via => :post match '/reviews' => 'api_reviews#index', :via => :get match '/reviews' => 'api_reviews#create', :via => :post From c16be142076c0228762477ae926a56507df93da1 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sun, 4 Feb 2024 23:39:54 +0530 Subject: [PATCH 13/13] fix errors due to null values --- jam-ui/src/components/page/JKEditProfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jam-ui/src/components/page/JKEditProfile.js b/jam-ui/src/components/page/JKEditProfile.js index a1b606507..d179e50a7 100644 --- a/jam-ui/src/components/page/JKEditProfile.js +++ b/jam-ui/src/components/page/JKEditProfile.js @@ -79,9 +79,9 @@ function JKEditProfile() { .then(data => { setValue('firstName', data.first_name); setValue('lastName', data.last_name); - setValue('country', data.country); - setValue('state', data.state); - setValue('city', data.city); + setValue('country', data.country ? data.country : ''); + setValue('state', data.state ? data.state : ''); + setValue('city', data.city? data.city : ''); setValue('biography', data.biography); setValue('subscribeEmail', data.subscribe_email); setValue('virtualBand', data.virtual_band);