includes fixes in profile edit form and lobby page

This commit is contained in:
Nuwan 2024-02-12 19:11:11 +05:30
parent 30aa9eeed2
commit 861722e5d4
13 changed files with 251 additions and 57 deletions

View File

@ -37,6 +37,7 @@ 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'));
//const PublicRoutes = loadable(() => import('../../layouts/JKPublicRoutes'))
@ -48,6 +49,7 @@ const Msg = ({ closeToast, toastProps, title }) => (
);
function JKDashboardMain() {
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
const isKanban = getPageName('kanban');
@ -76,10 +78,34 @@ function JKDashboardMain() {
useEffect(() => {
setVisibilityState(document.visibilityState);
// if(currentUser){
// postInteraction(document.visibilityState);
// }
}, [document.visibilityState]);
const dispatch = useDispatch();
// const postInteraction = visibilityState => {
// const options = {
// client: INTERACTION_CLIENT,
// screen: INTERACTION_SCREEN,
// user_id: currentUser.id,
// };
// if (visibilityState === 'visible') {
// options.action = INTERACTION_PAGE_ENTER;
// } else {
// options.action = INTERACTION_PAGE_EXIT;
// }
// postUserAppInteraction(options)
// .then(response => {
// console.log('User app interactions updated', response);
// })
// .catch(error => {
// console.log('Error updating user app interactions', error);
// });
// };
const initJKScripts = () => {
if (scriptLoaded.current) {
return;
@ -149,11 +175,11 @@ function JKDashboardMain() {
const registerChatMessageCallback = () => {
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.CHAT_MESSAGE, function(header, payload) {
console.log('registerChatMessageCallback ' + JSON.stringify(payload));
if ( payload !== undefined && payload.sender_id !== window.currentUser.id) {
if (payload !== undefined && payload.sender_id !== window.currentUser.id) {
if (visibilityState === 'hidden' && Notification.permission === 'granted') {
try{
const notification = new Notification("JamKazam Lobby Message", {
body: `${payload.sender_name}: ${truncate(payload.msg, 100)}`,
try {
const notification = new Notification('JamKazam Lobby Message', {
body: `${payload.sender_name}: ${truncate(payload.msg, 100)}`
//icon: `${process.env.REACT_APP_CLIENT_BASE_URL}/assets/img/jamkazam-logo.png`
});
notification.onclick = function(event) {
@ -161,7 +187,7 @@ function JKDashboardMain() {
window.focus();
event.target.close();
};
}catch(err){
} catch (err) {
console.log('Error when showing notification', err);
}
}

View File

@ -15,7 +15,7 @@ import {
getRegions,
getCities
} from '../../helpers/rest';
import { set } from 'lodash';
function JKEditProfile() {
const { t } = useTranslation();
@ -23,6 +23,7 @@ function JKEditProfile() {
const [musicInstruments, setMusicInstruments] = useState([]);
const [genres, setGenres] = useState([]);
const [instrumentsInitialLoadingDone, setInstrumentsInitialLoadingDone] = useState(false);
const [currentUserLoaded, setCurrentUserLoaded] = useState(false);
const [genreInitialLoadingDone, setGenreInitialLoadingDone] = useState(false);
const [countries, setCountries] = useState([]);
const [regions, setRegions] = useState([]);
@ -58,9 +59,10 @@ function JKEditProfile() {
});
useEffect(() => {
if (currentUser) {
fetchCurentUser().then(userData => {
console.log('_DEBUG userData', userData);
if (currentUser && !currentUserLoaded) {
setCurrentUserLoaded(true);
fetchCurentUser().then(data => {
updateUserData(data);
fetchInstruments();
fetchGenres();
fetchCountries();
@ -74,33 +76,36 @@ function JKEditProfile() {
.then(response => {
if (response.ok) {
return response.json();
}else{
reject('Error fetching user data');
}
})
.then(data => {
setValue('firstName', data.first_name);
setValue('lastName', data.last_name);
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);
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());
})
.then(data => resolve(data))
.catch(error => reject(error));
});
};
const updateUserData = data => {
setValue('firstName', data.first_name);
setValue('lastName', data.last_name);
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);
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);
}
}
const fetchInstruments = () => {
getInstruments()
.then(response => {
@ -116,14 +121,18 @@ function JKEditProfile() {
useEffect(() => {
if (!musicInstruments.length || !getValues('instruments') || instrumentsInitialLoadingDone) return;
setInstrumentsInitialLoadingDone(false);
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.priority = instrument.priority;
musicInstrument.checked = true;
musicInstrument.instrument_id = instrument.instrument_id;
} else {
musicInstrument.proficiency_level = null;
musicInstrument.priority = '0'; // default priority - to prevent database not null constraint violation
musicInstrument.checked = false;
musicInstrument.instrument_id = null;
}
@ -131,6 +140,7 @@ function JKEditProfile() {
});
setMusicInstruments(updatedMusicInstruments);
setInstrumentsInitialLoadingDone(true);
}, [musicInstruments, getValues('instruments')]);
const fetchGenres = () => {
@ -237,9 +247,19 @@ function JKEditProfile() {
};
const handleInstrumentProficiencyChange = (option, musicInstrument) => {
const userInstrument = getValues('instruments').find(instrument => instrument.instrument_id === musicInstrument.id);
if (!userInstrument) return;
userInstrument.proficiency_level = option.value;
const userInstrument = getValues('instruments').find(instrument => instrument.instrument_id === musicInstrument.id);
if (!userInstrument) {
const updatedInstruments = [...getValues('instruments'), { ...musicInstrument, proficiency_level: option.value}];
setValue('instruments', updatedInstruments);
}else{
const updatedInstruments = getValues('instruments').map(instrument => {
if (instrument.instrument_id === musicInstrument.id) {
instrument.proficiency_level = option.value;
}
return instrument;
});
setValue('instruments', updatedInstruments);
}
forceUpdate();
handleChange();
};
@ -301,15 +321,22 @@ function JKEditProfile() {
const state = selectedOpt.value;
const country = getValues('country');
setValue('state', state);
setValue('city', null);
setValue('city', '');
setCities([]);
cityRef.current.select.clearValue();
fetchCities(country, state);
handleChange();
};
const handleCityChange = selectedOpt => {
const city = selectedOpt.value;
setValue('city', city);
handleChange();
};
const handleChange = () => {
const params = getValues();
const data = {
first_name: params.firstName,
last_name: params.lastName,
@ -322,17 +349,23 @@ function JKEditProfile() {
traditional_band: params.traditionalBand,
cowriting: params.cowriting,
instruments: params.instruments,
genres: params.genres
genres: params.genres.map(genre => genre.genre_id)
};
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));
const instrments = params.instruments.filter(instrument =>
instrument.instrument_id !== null &&
instrument.proficiency_level !== null
);
data.instruments = instrments;
updateUser(currentUser.id, data).then(response => {
if (response.ok) {
console.log('User data updated');
}else{
console.log('Error updating user data');
}
}).catch(error => console.log(error));
};
return (
@ -453,10 +486,7 @@ function JKEditProfile() {
<Select
value={{ value: value, label: value }}
ref={cityRef}
onChange={e => {
onChange(e);
handleChange();
}}
onChange={handleCityChange}
options={cities.map(city => {
return { value: city, label: city };
})}

View File

@ -16,8 +16,15 @@ import { useNativeApp } from '../../context/NativeAppContext';
import JKModalDialog from '../common/JKModalDialog';
import JKTooltip from '../common/JKTooltip';
import { updateUser } from '../../helpers/rest';
import { postUserAppInteraction } from '../../helpers/rest';
import { useVisbilityState } from '../../hooks/useVisibilityState';
function JKMusicSessionsLobby() {
const INTERACTION_CLIENT = 'browser';
const INTERACTION_SCREEN = 'session:lobby';
const INTERACTION_PAGE_ENTER = 'page:enter';
const INTERACTION_PAGE_EXIT = 'page:exit';
const { t } = useTranslation();
const { greaterThan } = useResponsive();
const dispatch = useDispatch();
@ -34,6 +41,8 @@ function JKMusicSessionsLobby() {
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
const NOTIFICATION_INTERVAL = 1000 * 10 //10 seconds
const documentVisibility = useVisbilityState();
useEffect(() => {
dispatch(fetchOnlineMusicians());
//check if browser notifications are enabled
@ -60,6 +69,33 @@ function JKMusicSessionsLobby() {
}
}, [loadingStatus]);
useEffect(() => {
if(currentUser){
postInteraction(documentVisibility);
}
}, [documentVisibility]);
const postInteraction = visibilityState => {
const options = {
client: INTERACTION_CLIENT,
screen: INTERACTION_SCREEN,
};
if (visibilityState === 'visible') {
options.act = INTERACTION_PAGE_ENTER;
} else {
options.act = INTERACTION_PAGE_EXIT;
}
postUserAppInteraction(currentUser.id, options)
.then(response => {
console.log('User app interactions updated', response);
})
.catch(error => {
console.log('Error updating user app interactions', error);
});
};
const handleClick = async () => {
const payload = {
privacy: sessionPrivacyMap.public,

View File

@ -18,7 +18,7 @@ export const getMusicians = page => {
export const getPersonById = id => {
return new Promise((resolve, reject) =>
apiFetch(`/users/${id}/profile?show_teacher=true`)
apiFetch(`/users/${id}/profile`)
.then(response => resolve(response))
.catch(error => reject(error))
);
@ -53,7 +53,10 @@ export const getLobbyUsers = () => {
export const updateUser = (id, data) => {
return new Promise((resolve, reject) => {
apiFetch('/genres')
apiFetch(`/users/${id}`, {
method: 'POST',
body: JSON.stringify(data)
})
.then(response => resolve(response))
.catch(error => reject(error));
});
@ -254,4 +257,15 @@ export const requestPasswordReset = (userId) => {
.then(response => resolve(response))
.catch(error => reject(error));
});
}
export const postUserAppInteraction = (userId, options) => {
return new Promise((resolve, reject) => {
apiFetch(`/users/${userId}/app_interactions`, {
method: 'POST',
body: JSON.stringify(options)
})
.then(response => resolve(response))
.catch(error => reject(error));
});
}

View File

@ -0,0 +1,17 @@
import { useState, useEffect, useCallback } from 'react';
export const useVisbilityState = () => {
const [visibilityState, setVisibilityState] = useState('visible');
const handleVisbilityChange = useCallback(() => {
setVisibilityState(document.visibilityState);
}, [setVisibilityState]);
useEffect(() => {
document.addEventListener('visibilitychange', handleVisbilityChange);
return () =>
document.removeEventListener('visibilitychange', handleVisbilityChange);
}, [handleVisbilityChange]);
return visibilityState;
};

View File

@ -1,6 +1,4 @@
import { getCurrentUser } from '../helpers/rest';
import apiFetch from "../helpers/apiFetch";
import { reject } from 'lodash';
export const checkIsAuthenticated = () => {
return new Promise((resolve, reject) => {

View File

@ -0,0 +1,20 @@
class CreateAppInterations < ActiveRecord::Migration
def self.up
execute(<<-SQL
CREATE UNLOGGED TABLE public.app_interactions (
id character varying(64) DEFAULT public.uuid_generate_v4() PRIMARY KEY NOT NULL,
user_id character varying(64) NOT NULL,
client character varying(64),
screen character varying(64),
action character varying(64),
action_at timestamp without time zone DEFAULT now()
);
SQL
)
execute("CREATE INDEX index_app_interactions_screen_action ON public.temp_tokens USING btree (screen, action);")
end
def self.down
execute("DROP INDEX index_app_interactions_screen_action")
execute("DROP TABLE public.app_interactions")
end
end

View File

@ -344,6 +344,7 @@ require "jam_ruby/models/temp_token"
require "jam_ruby/models/ad_campaign"
require "jam_ruby/models/user_asset"
require "jam_ruby/models/user_match_email_sending"
require "jam_ruby/models/app_interaction"
include Jampb

View File

@ -0,0 +1,16 @@
module JamRuby
class AppInteraction < ActiveRecord::Base
self.primary_key = 'id'
self.table_name = 'app_interactions'
ACTIONS = %w(page:enter page:exit)
CLIENTS = %w(browser client)
validates :action, inclusion: { in: ACTIONS }
validates :client, inclusion: { in: CLIENTS }
attr_accessible :user_id, :client, :screen, :action
belongs_to :user
end
end

View File

@ -252,6 +252,8 @@ module JamRuby
has_many :posa_cards, class_name: 'JamRuby::PosaCard', dependent: :destroy
has_many :temp_tokens, class_name: 'JamRuby::TempToken', dependent: :destroy
has_many :app_interactions, class_name: 'JamRuby::AppInteraction', dependent: :destroy
before_save :default_anonymous_names
before_save :create_remember_token, :if => :should_validate_password?
before_save :stringify_avatar_info, :if => :updating_avatar
@ -282,7 +284,7 @@ module JamRuby
validates :is_onboarder, :inclusion => {:in => [true, false, nil]}
#validates :mods, json: true
validates_numericality_of :last_jam_audio_latency, greater_than: MINIMUM_AUDIO_LATENCY, less_than: MAXIMUM_AUDIO_LATENCY, :allow_nil => true
validates :last_jam_updated_reason, :inclusion => {:in => [nil, JAM_REASON_REGISTRATION, JAM_REASON_NETWORK_TEST, JAM_REASON_FTUE, JAM_REASON_JOIN, JAM_REASON_IMPORT, JAM_REASON_LOGIN]}
validates :last_jam_updated_reason, :inclusion => {:in => [nil, JAM_REASON_REGISTRATION, JAM_REASON_NETWORK_TEST, JAM_REASON_FTUE, JAM_REASON_JOIN, JAM_REASON_IMPORT, JAM_REASON_LOGIN, JAM_REASON_PRESENT]}
# stored in cents
validates_numericality_of :paid_sessions_hourly_rate, greater_than: 0, less_than: 200000, :if => :paid_sessions
@ -310,12 +312,31 @@ module JamRuby
scope :not_deleted, ->{ where(deleted: false) }
def self.lobby(current_user, options = {})
query = User.where("users.id <> ? AND last_jam_updated_at IS NOT NULL AND users.last_jam_updated_at > ?", current_user.id, 15.minutes.ago)
#the query below return the users that are either
#the users that are currently viewing the lobby page or
#the users that have opened the lobby page but now in a different tab or window or
#the users that have been active in the last 15 minutes
query = User.joins(:app_interactions).where("
(app_interactions.client = 'browser' AND app_interactions.action_at > ? AND app_interactions.screen = 'lobby' AND app_interactions.action = 'page:enter' AND app_interactions.user_id <> ?)
OR (app_interactions.client = 'browser' AND app_interactions.action_at > ? AND app_interactions.screen = 'lobby' AND app_interactions.action = 'page:exit' AND app_interactions.user_id = ? AND users.accept_desktop_notifications = ?)
OR (users.last_jam_updated_at IS NOT NULL AND users.last_jam_updated_at > ? AND users.id <> ?)",
10.minutes.ago,
current_user.id,
10.minutes.ago,
current_user.id,
true,
10.minutes.ago,
current_user.id
)
#the users that are currently not in a music session
live_music_sessions = ActiveMusicSession
if live_music_sessions.count > 0
query = query.where("users.id NOT IN (?)", live_music_sessions.pluck(:user_id))
end
query
user_ids = query.select("DISTINCT ON(users.id) users.*").map(&:id)
User.where(id: user_ids).order("users.id, users.last_jam_updated_at DESC")
end
def after_save

View File

@ -14,7 +14,7 @@ class ApiUsersController < ApiController
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
: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]
:affiliate_report, :audio_latency, :get_latencies, :broadcast_notification, :redeem_giftcard, :post_app_interactions]
before_filter :ip_blacklist, :only => [:create, :redeem_giftcard]
@ -1264,6 +1264,20 @@ class ApiUsersController < ApiController
@onboarding = User.find(params[:id])
end
def post_app_interactions
user = User.find(params[:id])
interaction = user.app_interactions.new
interaction.client = params[:client]
interaction.screen = params[:screen]
interaction.action = params[:act]
if interaction.save
render json: {}, status: 200
else
render json: interaction.errors.full_messages, state: :unprocessable_entity
end
end
###################### RECORDINGS #######################
# def recording_index
# @recordings = User.recording_index(current_user, params[:id])

View File

@ -405,6 +405,7 @@ Rails.application.routes.draw do
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 '/users/:id/app_interactions' => 'api_users#post_app_interactions', :via => :post
match '/reviews' => 'api_reviews#index', :via => :get
match '/reviews' => 'api_reviews#create', :via => :post

View File

@ -1526,7 +1526,7 @@ module JamWebsockets
if active_users_ids.any?
sql = %{
update users set last_jam_updated_at = now(), last_jam_updated_reason=#{User::JAM_REASON_PRESENT} where users.id in (#{active_users_ids.join(',')});
update users set last_jam_updated_at = now(), last_jam_updated_reason='#{User::JAM_REASON_PRESENT}' where users.id in (#{active_users_ids.map{|id| "'#{id}'"}.join(',')});
}
@log.info("SQL #{sql}")