From 71716b2240aa276e3a8ae7b0edab0bf4cc50a564 Mon Sep 17 00:00:00 2001 From: Nuwan Chaturanga Date: Sat, 5 Apr 2025 20:18:38 +0000 Subject: [PATCH 01/23] Merged in 5534-profile-reminder-emails (pull request #54) Profile prompts & reminders * Profile prompts & reminders 3 email reminders to for new users who have not completed their jamkazam profile * PR change requests. moved email sernding job to hourly job tasks. and add database table index Approved-by: Seth Call --- ...1_add_profile_complete_columns_to_users.rb | 18 +++++ ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 36 +++++++++ .../profile_complete_reminder1.html.erb | 73 +++++++++++++++++++ .../profile_complete_reminder1.text.erb | 17 +++++ .../profile_complete_reminder2.html.erb | 34 +++++++++ .../profile_complete_reminder2.text.erb | 11 +++ .../profile_complete_reminder3.html.erb | 29 ++++++++ .../profile_complete_reminder3.text.erb | 9 +++ .../jam_ruby/lib/email_profile_reminder.rb | 43 +++++++++++ .../jam_ruby/resque/scheduled/hourly_job.rb | 1 + .../scheduled/profile_reminder_emailer.rb | 15 ++++ web/config/locales/en.yml | 29 ++++++++ 12 files changed, 315 insertions(+) create mode 100644 ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.text.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.text.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.text.erb create mode 100644 ruby/lib/jam_ruby/lib/email_profile_reminder.rb create mode 100644 ruby/lib/jam_ruby/resque/scheduled/profile_reminder_emailer.rb diff --git a/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb new file mode 100644 index 000000000..cef65f792 --- /dev/null +++ b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb @@ -0,0 +1,18 @@ + class AddProfileCompleteColumnsToUsers < ActiveRecord::Migration + def self.up + execute "ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP" + #add index on profile_completed_at + execute "CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at)" + execute "ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP" + + User.where('users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players)').update_all(profile_completed_at: Time.now) + end + def self.down + execute "ALTER TABLE users DROP COLUMN profile_completed_at" + execute "ALTER TABLE users DROP COLUMN profile_complete_reminder1_sent_at" + execute "ALTER TABLE users DROP COLUMN profile_complete_reminder2_sent_at" + execute "ALTER TABLE users DROP COLUMN profile_complete_reminder3_sent_at" + end + end diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 6cf4e7940..193402404 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -418,6 +418,42 @@ module JamRuby end end + def profile_complete_reminder1(user) + @user = user + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + sendgrid_unique_args :type => "profile_complete_reminder1" + + mail(:to => user.email, :subject => I18n.t('user_mailer.profile_complete_reminder1.subject')) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + + def profile_complete_reminder2(user) + @user = user + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + sendgrid_unique_args :type => "profile_complete_reminder2" + + mail(:to => user.email, :subject => "Take 2 minutes to fill out your JamKazam profile now") do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + + def profile_complete_reminder3(user) + @user = user + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + sendgrid_unique_args :type => "profile_complete_reminder3" + + mail(:to => user.email, :subject => "Last reminder to update your JamKazam profile") do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + #################################### NOTIFICATION EMAILS #################################### def friend_request(user, msg, friend_request_id) return if !user.subscribe_email diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.html.erb new file mode 100644 index 000000000..ec48a3778 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.html.erb @@ -0,0 +1,73 @@ +

<%=I18n.t('user_mailer.profile_complete_reminder1.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.profile_complete_reminder1.paragraph1') -%> +

+ +

+ + <%=I18n.t('user_mailer.profile_complete_reminder1.update_profile') -%> + +

+ +

+<%=I18n.t('user_mailer.profile_complete_reminder1.paragraph2') -%> +

+ +

+ <%=I18n.t('user_mailer.profile_complete_reminder1.paragraph3') -%> +

+ +

+ + <%=I18n.t('user_mailer.profile_complete_reminder1.download_app') -%> + + + <%=I18n.t('user_mailer.profile_complete_reminder1.download_legacy_app') -%> + +

+

+ <%=I18n.t('user_mailer.profile_complete_reminder1.paragraph4') -%> +

+ +
+

+ <%=I18n.t('user_mailer.profile_complete_reminder1.regards') -%> +
+ <%=I18n.t('user_mailer.profile_complete_reminder1.signature') -%> +

+
\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.text.erb new file mode 100644 index 000000000..026906af5 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder1.text.erb @@ -0,0 +1,17 @@ +<%=I18n.t('user_mailer.profile_complete_reminder1.greeting') -%> <%= @user.first_name -%> - + +<%=I18n.t('user_mailer.profile_complete_reminder1.paragraph1') -%> + +<%= APP_CONFIG.spa_origin_url %>/profile + +<%=I18n.t('user_mailer.profile_complete_reminder1.paragraph2') -%> + +<%=I18n.t('user_mailer.profile_complete_reminder1.paragraph3') -%> + +<%= APP_CONFIG.external_root_url %>/downloads + +<%=I18n.t('user_mailer.profile_complete_reminder1.paragraph4') -%> + +<%=I18n.t('user_mailer.profile_complete_reminder1.regards') -%> +<%=I18n.t('user_mailer.profile_complete_reminder1.signature') -%> + diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.html.erb new file mode 100644 index 000000000..95a83fe17 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.html.erb @@ -0,0 +1,34 @@ +

<%=I18n.t('user_mailer.profile_complete_reminder2.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.profile_complete_reminder2.paragraph1') -%> +

+ +

+ + <%=I18n.t('user_mailer.profile_complete_reminder2.update_profile') -%> + +

+ +

+ <%=I18n.t('user_mailer.profile_complete_reminder2.paragraph2') -%> +

+ +
+

+ <%=I18n.t('user_mailer.profile_complete_reminder2.regards') -%> +
+ <%=I18n.t('user_mailer.profile_complete_reminder2.signature') -%> +

+
\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.text.erb new file mode 100644 index 000000000..0cda26fe7 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder2.text.erb @@ -0,0 +1,11 @@ +<%=I18n.t('user_mailer.profile_complete_reminder2.greeting') -%> <%= @user.first_name -%> - + +<%=I18n.t('user_mailer.profile_complete_reminder2.paragraph1') -%> + +<%= APP_CONFIG.spa_origin_url %>/profile + +<%=I18n.t('user_mailer.profile_complete_reminder2.paragraph2') -%> + +<%=I18n.t('user_mailer.profile_complete_reminder2.regards') -%> + +<%=I18n.t('user_mailer.profile_complete_reminder2.signature') -%> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.html.erb new file mode 100644 index 000000000..b469fec07 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.html.erb @@ -0,0 +1,29 @@ +

<%=I18n.t('user_mailer.profile_complete_reminder3.greeting') -%> <%= @user.first_name -%> -

+ +

<%=I18n.t('user_mailer.profile_complete_reminder3.paragraph1') -%> +

+ +

+ + <%=I18n.t('user_mailer.profile_complete_reminder3.update_profile') -%> + +

+ +
+

+ <%=I18n.t('user_mailer.profile_complete_reminder3.regards') -%> +
+ <%=I18n.t('user_mailer.profile_complete_reminder3.signature') -%> +

+
\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.text.erb new file mode 100644 index 000000000..d7632644c --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/profile_complete_reminder3.text.erb @@ -0,0 +1,9 @@ +<%=I18n.t('user_mailer.profile_complete_reminder3.greeting') -%><%= @user.first_name -%> - + +<%=I18n.t('user_mailer.profile_complete_reminder3.paragraph1') -%> + +<%= APP_CONFIG.spa_origin_url %>/profile + +<%=I18n.t('user_mailer.profile_complete_reminder3.regards') -%> + +<%=I18n.t('user_mailer.profile_complete_reminder3.signature') -%> \ No newline at end of file diff --git a/ruby/lib/jam_ruby/lib/email_profile_reminder.rb b/ruby/lib/jam_ruby/lib/email_profile_reminder.rb new file mode 100644 index 000000000..a253f0eb7 --- /dev/null +++ b/ruby/lib/jam_ruby/lib/email_profile_reminder.rb @@ -0,0 +1,43 @@ +module JamRuby + class EmailProfileReminder + + def self.send_reminders + #If the user has not updated their profile 1 day after signup, then send reminder email1 + reminder1_users.each do |user| + UserMailer.profile_complete_reminder1(user).deliver_now + user.update(profile_complete_reminder1_sent_at: Time.now) + end + + #If the user has not updated their profile 3 days after signup, then send reminder email2 + reminder2_users.each do |user| + UserMailer.profile_complete_reminder2(user).deliver_now + user.update(profile_complete_reminder2_sent_at: Time.now) + end + + #If the user has not updated their profile 5 days after signup, then send reminder email3 + reminder3_users.each do |user| + UserMailer.profile_complete_reminder3(user).deliver_now + user.update(profile_complete_reminder3_sent_at: Time.now) + end + + end + + def self.prospect_users + User.where("users.profile_completed_at IS NULL AND users.subscribe_email = ?", true) + end + + def self.reminder1_users + EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NULL", 1.day.ago) + end + + def self.reminder2_users + EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", 3.days.ago) + end + + def self.reminder3_users + EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", 5.days.ago) + end + + + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index e534e98cf..a73b813b9 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -13,6 +13,7 @@ module JamRuby #TeacherPayment.hourly_check User.hourly_check AffiliatePartner.tally_up(Date.today) + EmailProfileReminder.send_reminders ConnectionManager.new.cleanup_dangling @@log.info("done") diff --git a/ruby/lib/jam_ruby/resque/scheduled/profile_reminder_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/profile_reminder_emailer.rb new file mode 100644 index 000000000..9eaba001a --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/profile_reminder_emailer.rb @@ -0,0 +1,15 @@ +module JamRuby + class ProfileReminderEmailer + extend Resque::Plugins::JamLonelyJob + + @queue = :scheduled_profile_reminder_emailer + @@log = Logging.logger[ProfileReminderEmailer] + + def self.perform + @@log.debug("waking up") + EmailProfileReminder.send_reminders + @@log.debug("done") + end + + end +end \ No newline at end of file diff --git a/web/config/locales/en.yml b/web/config/locales/en.yml index 6b0ecd22f..92778cd01 100644 --- a/web/config/locales/en.yml +++ b/web/config/locales/en.yml @@ -71,3 +71,32 @@ en: paragraph2: "Your account was imported for you, so you'll need to set a password. Click the button below to set a password for your JamKazam account." paragraph3: "Thanks for joining JamKazam!" signature: "The JamKazam Team" + profile_complete_reminder1: + subject: "Update your JamKazam profile now & get connected to other musicians" + greeting: "Hello" + paragraph1: "If you may be interested in playing music live online with other musicians, the first thing you should do is fill out your JamKazam profile. The information in your profile lets other musicians with similar interests find, message, and connect with you, and also lets JamKazam’s matchmaking features give you recommendations for great musical connections. Click the button below to update your profile now. It only takes 2 minutes to complete your profile." + update_profile: "Update Profile" + paragraph2: "After you have updated your profile, the next thing you should do is download, install, and run the JamKazam app. You’ll need this app to play online with others, and the app also gives you access to more advanced JamTracks features if you signed up to use our huge catalog of backing tracks. After you download and install the app, we recommend you open the app and then leave the app running for about 10-15 minutes on your computer. The app will use this time to quietly collect Internet data that we use to figure out which other musicians on JamKazam you’ll have the best connections with – i.e. the lowest latency (or lag) when playing together" + paragraph3: "Click a button below to go to the JamKazam app download page. (Most users should click the Download App button, but if you’re on a Mac that can’t run MacOS 10.15 or later, click the Download Legacy App button, as that version of the app supports older MacOS versions back to 10.7.) After you download it, double click the downloaded installer, and follow the on-screen instructions to install the app, then start the app and leave it running for about 15 minutes." + download_app: "Download App" + download_legacy_app: "Download Legacy App" + paragraph4: "For next steps, don’t forget you can always refer back to the Welcome email we sent you when you signed up. And if you run into any problems or get stuck, please send us email at support@jamkazam.com. We’re always happy to help" + regards: "Best Regards," + signature: "JamKazam Team" + + profile_complete_reminder2: + subject: "Complete your JamKazam profile" + greeting: "Hello" + paragraph1: "We share your profile with JamKazam members who may be a good fit to play music with you during your first week on the platform via an automated email feature. Don’t miss this opportunity to connect. Click the button below to update your profile now, before it’s shared with others!" + update_profile: "Update Profile" + paragraph2: "For next steps after your profile, don’t forget you can always refer back to the Welcome email we sent you when you signed up. And if you run into any problems or get stuck, please send us email at support@jamkazam.com. We’re always happy to help!" + regards: "Best Regards," + signature: "JamKazam Team" + + profile_complete_reminder3: + subject: "Complete your JamKazam profile" + greeting: "Hello" + paragraph1: "Your profile is your key to connecting with other musicians on JamKazam. It lets others know what instruments you play at what level of proficiency and what kinds of music you like to play. This lets our existing community find and reach out to you to connect and play, and this information also powers our automated matchmaking features that make recommendations to you. If you think you might want to play music live online on JamKazam, please click the button below now to fill out your profile!" + update_profile: "Update Profile" + regards: "Best Regards," + signature: "JamKazam Team" From 403a830157ac83f7cf48d7ef06827c17976ffb33 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sun, 18 May 2025 19:54:12 +0530 Subject: [PATCH 03/23] list friends, public and inactive sessions in browse session page --- jam-ui/src/components/page/JKMusicSessions.js | 7 +- jam-ui/src/helpers/rest.js | 24 ++++ jam-ui/src/store/features/sessionsSlice.js | 109 +++++++++++++++--- 3 files changed, 119 insertions(+), 21 deletions(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index d2d6772b8..3982e349c 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import FalconCardHeader from '../common/FalconCardHeader'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchSessions } from '../../store/features/sessionsSlice'; +import { fetchSessions, fetchFriendsSessions, fetchPublicSessions, fetchInactiveSessions } from '../../store/features/sessionsSlice'; import { isIterableArray } from '../../helpers/utils'; import { useResponsive } from '@farfetch/react-context-responsive'; import JKModalDialog from '../common/JKModalDialog'; @@ -23,7 +23,10 @@ function JKMusicSessions() { const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp(); useEffect(() => { - dispatch(fetchSessions()); + //dispatch(fetchSessions()); + dispatch(fetchFriendsSessions()); + dispatch(fetchPublicSessions()); + dispatch(fetchInactiveSessions()); }, []); const toggleAppUnavilableModel = () => { diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index 13c1673f4..b5fab9ec7 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -213,6 +213,30 @@ export const getSessions = () => { }); }; +export const getFriendsSessions = () => { + return new Promise((resolve, reject) => { + apiFetch(`/sessions/friends`) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + +export const getPublicSessions = () => { + return new Promise((resolve, reject) => { + apiFetch(`/sessions/public`) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + +export const getInactiveSessions = () => { + return new Promise((resolve, reject) => { + apiFetch(`/sessions/inactive`) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +}; + export const getSessionsHistory = (options = {}) => { return new Promise((resolve, reject) => { apiFetch(`/sessions/history?${new URLSearchParams(options)}`) diff --git a/jam-ui/src/store/features/sessionsSlice.js b/jam-ui/src/store/features/sessionsSlice.js index e6b62209a..3ced285e8 100644 --- a/jam-ui/src/store/features/sessionsSlice.js +++ b/jam-ui/src/store/features/sessionsSlice.js @@ -1,5 +1,5 @@ import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; -import { getSessions, getPersonById } from '../../helpers/rest' +import { getSessions, getFriendsSessions, getPublicSessions, getInactiveSessions, getPersonById } from '../../helpers/rest' const initialState = { sessions: [], @@ -9,27 +9,50 @@ const initialState = { } export const fetchSessions = createAsyncThunk( - "session/fetchSessions", + "session/fetchSessions", async (options, thunkAPI) => { const response = await getSessions(); return response.json(); } +) +export const fetchFriendsSessions = createAsyncThunk( + "session/fetchFriendsSessions", + async (options, thunkAPI) => { + const response = await getFriendsSessions(options); + return response.json(); + } +) + +export const fetchPublicSessions = createAsyncThunk( + "session/fetchPublicSessions", + async (options, thunkAPI) => { + const response = await getPublicSessions(options); + return response.json(); + } +) + +export const fetchInactiveSessions = createAsyncThunk( + "session/fetchInactiveSessions", + async (options, thunkAPI) => { + const response = await getInactiveSessions(options); + return response.json(); + } ) export const fetchPerson = createAsyncThunk( - 'session/fetchPerson', + 'session/fetchPerson', async (options, thunkAPI) => { - const {userId} = options + const { userId } = options const response = await getPersonById(userId) return response.json() } - ,{ - condition: (options, {getState, extra}) => { - const {session} = getState() - const {userId} = options + , { + condition: (options, { getState, extra }) => { + const { session } = getState() + const { userId } = options const person = session.people.find(person => person.id === userId) - if(person && person.website){ + if (person && person.website) { //only proceed if full data set for user has not been fetched. person.website is not included in the initial data fetching (i.e: in friends listing ). return false; } @@ -41,9 +64,9 @@ export const SessionSlice = createSlice({ name: "session", initialState, reducers: { - addSession: (state) => {}, - updateSession: (state) => {}, - deleteSession: (state) => {}, + addSession: (state) => { }, + updateSession: (state) => { }, + deleteSession: (state) => { }, }, extraReducers: (builder) => { builder @@ -51,28 +74,76 @@ export const SessionSlice = createSlice({ state.status = "loading"; }) .addCase(fetchSessions.fulfilled, (state, action) => { - console.log(action.payload); + // add unique sessions to the array + const records = new Set([...state.sessions, ...action.payload]); + const unique = []; + records.forEach((item) => { + unique.push(item); + }); + state.sessions = unique; + state.error = null; state.status = "succeeded"; - state.sessions = action.payload; }) .addCase(fetchSessions.rejected, (state, action) => { state.status = 'failed' - state.error = action.error.message + state.error = action.error.message + }) + .addCase(fetchFriendsSessions.pending, (state, action) => { + state.status = "loading"; + }) + .addCase(fetchFriendsSessions.fulfilled, (state, action) => { + // add unique sessions to the array + const records = new Set([...state.sessions, ...action.payload.sessions]); + const unique = []; + records.forEach((item) => { + unique.push(item); + }); + state.sessions = unique; + state.error = null; + state.status = "succeeded"; + }) + .addCase(fetchPublicSessions.pending, (state, action) => { + state.status = "loading"; + }) + .addCase(fetchPublicSessions.fulfilled, (state, action) => { + // add unique sessions to the array + const records = new Set([...state.sessions, ...action.payload.sessions]); + const unique = []; + records.forEach((item) => { + unique.push(item); + }); + state.sessions = unique; + state.error = null; + state.status = "succeeded"; + }) + .addCase(fetchInactiveSessions.pending, (state, action) => { + state.status = "loading"; + }) + .addCase(fetchInactiveSessions.fulfilled, (state, action) => { + // add unique sessions to the array + const records = new Set([...state.sessions, ...action.payload.sessions]); + const unique = []; + records.forEach((item) => { + unique.push(item); + }); + state.sessions = unique; + state.error = null; + state.status = "succeeded"; }) .addCase(fetchPerson.fulfilled, (state, action) => { const person = state.people.find(person => person.id === action.payload.id) - if(person){ + if (person) { const updated = { - ...person, + ...person, ...action.payload } const objIndex = state.people.findIndex((p => p.id === updated.id)); state.people[objIndex] = updated - }else{ + } else { state.people.push(action.payload) } }) - } + } }) export default SessionSlice.reducer; \ No newline at end of file From 1071dec044dacfa62231f8490c67f031425ab764 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sun, 18 May 2025 20:23:11 +0530 Subject: [PATCH 04/23] revert to list all sesions --- jam-ui/src/components/page/JKMusicSessions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index 3982e349c..d1875dc30 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -23,10 +23,10 @@ function JKMusicSessions() { const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp(); useEffect(() => { - //dispatch(fetchSessions()); - dispatch(fetchFriendsSessions()); - dispatch(fetchPublicSessions()); - dispatch(fetchInactiveSessions()); + dispatch(fetchSessions()); + // dispatch(fetchFriendsSessions()); + // dispatch(fetchPublicSessions()); + // dispatch(fetchInactiveSessions()); }, []); const toggleAppUnavilableModel = () => { From d6dc79b4784cda6dec0f9d1e3a63c31bd099ea70 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Mon, 19 May 2025 00:40:40 +0530 Subject: [PATCH 05/23] fix data reference error --- jam-ui/src/components/sessions/JKSession.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jam-ui/src/components/sessions/JKSession.js b/jam-ui/src/components/sessions/JKSession.js index 8047de5c2..7a4b76206 100644 --- a/jam-ui/src/components/sessions/JKSession.js +++ b/jam-ui/src/components/sessions/JKSession.js @@ -28,7 +28,7 @@ function JKSession({ session }) { useEffect(() => { - const otherUserIds = session.participants.map(p => p.user.id); + const otherUserIds = session.active_music_session.participants.map(p => p.user.id); const options = { currentUserId: currentUser.id, otherUserIds }; dispatch(fetchUserLatencies(options)); }, [session.id]); @@ -40,7 +40,7 @@ function JKSession({ session }) { }; const hasFriendNote = session => { - if (session.participants.find(p => p.user.is_friend)) { + if (session.active_music_session.participants.find(p => p.user.is_friend)) { return t('list.notes.has_friend', { ns: 'sessions' }); } }; @@ -72,7 +72,7 @@ function JKSession({ session }) {
{sessionDescription}
- {session.participants.map(participant => ( + {session.active_music_session.participants.map(participant => ( @@ -81,7 +81,7 @@ function JKSession({ session }) { ))} - {session.participants.map(participant => ( + {session.active_music_session.participants.map(participant => ( @@ -90,7 +90,7 @@ function JKSession({ session }) { ))} - {session.participants.map(participant => ( + {session.active_music_session.participants.map(participant => ( {participant.tracks.map(track => ( @@ -143,7 +143,7 @@ function JKSession({ session }) {
- {session.participants.map(participant => ( + {session.active_music_session.participants.map(participant => ( ))}
From 10e6fedb5ce911faf7e7442d1a355372a346b63b Mon Sep 17 00:00:00 2001 From: Nuwan Date: Sun, 18 May 2025 20:23:11 +0530 Subject: [PATCH 06/23] revert to list all sesions --- jam-ui/src/components/page/JKMusicSessions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index 3982e349c..d1875dc30 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -23,10 +23,10 @@ function JKMusicSessions() { const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp(); useEffect(() => { - //dispatch(fetchSessions()); - dispatch(fetchFriendsSessions()); - dispatch(fetchPublicSessions()); - dispatch(fetchInactiveSessions()); + dispatch(fetchSessions()); + // dispatch(fetchFriendsSessions()); + // dispatch(fetchPublicSessions()); + // dispatch(fetchInactiveSessions()); }, []); const toggleAppUnavilableModel = () => { From 0d25814e6eccf16089010e614e26036733f8e489 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 20 May 2025 14:24:28 +0530 Subject: [PATCH 07/23] list current sessions --- jam-ui/src/components/page/JKMusicSessions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index d1875dc30..914679f69 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -23,10 +23,10 @@ function JKMusicSessions() { const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp(); useEffect(() => { - dispatch(fetchSessions()); - // dispatch(fetchFriendsSessions()); - // dispatch(fetchPublicSessions()); - // dispatch(fetchInactiveSessions()); + // dispatch(fetchSessions()); + dispatch(fetchFriendsSessions()); + dispatch(fetchPublicSessions()); + dispatch(fetchInactiveSessions()); }, []); const toggleAppUnavilableModel = () => { From 0f7b9b2884206acdba559efdf7ac191c59dabbff Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 20 May 2025 18:27:43 +0530 Subject: [PATCH 08/23] fix null value error in session list --- jam-ui/src/components/sessions/JKSession.js | 140 +++++++++++--------- 1 file changed, 78 insertions(+), 62 deletions(-) diff --git a/jam-ui/src/components/sessions/JKSession.js b/jam-ui/src/components/sessions/JKSession.js index 7a4b76206..7fb01e064 100644 --- a/jam-ui/src/components/sessions/JKSession.js +++ b/jam-ui/src/components/sessions/JKSession.js @@ -13,7 +13,7 @@ import useNativeAppCheck from '../../hooks/useNativeAppCheck'; import { useNativeApp } from '../../context/NativeAppContext'; import EnterIcon from '../../icons/enter.svg'; import JKInstrumentIcon from '../profile/JKInstrumentIcon'; -import {useHistory} from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import useSessionHelper from './JKUseSessionHelper'; import JKModalDialog from '../common/JKModalDialog'; import JKAppLaunch from '../page/JKAppLaunch'; @@ -25,10 +25,10 @@ function JKSession({ session }) { const { greaterThan } = useResponsive(); const { setNativeAppUnavailable } = useNativeApp(); const { sessionDescription } = useSessionHelper(session); - + useEffect(() => { - const otherUserIds = session.active_music_session.participants.map(p => p.user.id); + const otherUserIds = session.active_music_session ? session.active_music_session.participants.map(p => p.user.id) : []; const options = { currentUserId: currentUser.id, otherUserIds }; dispatch(fetchUserLatencies(options)); }, [session.id]); @@ -40,7 +40,7 @@ function JKSession({ session }) { }; const hasFriendNote = session => { - if (session.active_music_session.participants.find(p => p.user.is_friend)) { + if (session.active_music_session && session.active_music_session.participants.find(p => p.user.is_friend)) { return t('list.notes.has_friend', { ns: 'sessions' }); } }; @@ -71,48 +71,62 @@ function JKSession({ session }) {
{sessionDescription}
+ - {session.active_music_session.participants.map(participant => ( - - - - - - ))} + {session.active_music_session && session.active_music_session.participants.length > 0 && ( + <> + {session.active_music_session.participants.map(participant => ( + + + + + + ))} + + )} + - {session.active_music_session.participants.map(participant => ( - - - - - - ))} + {session.active_music_session && session.active_music_session.participants.length > 0 && ( + <> + {session.active_music_session.participants.map(participant => ( + + + + + + ))} + + )} - {session.active_music_session.participants.map(participant => ( - - - {participant.tracks.map(track => ( - - - {/* */} - - - - {track.instrument} - - - ))} - - - ))} + {session.active_music_session && session.active_music_session.participants.length > 0 && ( + <> + {session.active_music_session.participants.map(participant => ( + + + {participant.tracks.map(track => ( + + + {/* */} + + + + {track.instrument} + + + ))} + + + ))} + + )} @@ -142,11 +156,13 @@ function JKSession({ session }) { {t('list.header.latency', { ns: 'sessions' })} -
- {session.active_music_session.participants.map(participant => ( - - ))} -
+ {session.active_music_session && session.active_music_session.participants.length > 0 && ( +
+ {session.active_music_session.participants.map(participant => ( + + ))} +
+ )}
@@ -187,23 +203,23 @@ function JoinSessionButton({ session }) { return ( <> -
- - - Date: Wed, 21 May 2025 12:07:16 +0530 Subject: [PATCH 09/23] do not show upcoming events in event listing --- jam-ui/src/components/page/JKMusicSessions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index 914679f69..8da064caa 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -26,7 +26,7 @@ function JKMusicSessions() { // dispatch(fetchSessions()); dispatch(fetchFriendsSessions()); dispatch(fetchPublicSessions()); - dispatch(fetchInactiveSessions()); + //dispatch(fetchInactiveSessions()); }, []); const toggleAppUnavilableModel = () => { From 49d3e2a4ac70824c0977625e401db557e3309d6a Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 21 May 2025 13:56:49 +0530 Subject: [PATCH 10/23] list sessions according to the fetch status --- jam-ui/src/components/page/JKMusicSessions.js | 12 +++++++++--- jam-ui/src/i18n/locales/en/sessions.json | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/jam-ui/src/components/page/JKMusicSessions.js b/jam-ui/src/components/page/JKMusicSessions.js index 8da064caa..a1c70a1a8 100644 --- a/jam-ui/src/components/page/JKMusicSessions.js +++ b/jam-ui/src/components/page/JKMusicSessions.js @@ -40,7 +40,7 @@ function JKMusicSessions() { {loadingStatus === 'loading' && sessions.length === 0 ? ( - ) : isIterableArray(sessions) ? ( + ) : loadingStatus === "succeeded" && sessions.length > 0 ? ( <> {greaterThan.sm ? ( @@ -54,7 +54,7 @@ function JKMusicSessions() { )} - ) : ( + ) : loadingStatus === "succeeded" && sessions.length <= 0 ? ( {t('list.no_records_1', { ns: 'sessions' })} @@ -62,7 +62,13 @@ function JKMusicSessions() { {t('list.no_records_2', { ns: 'sessions' })} - )} + ) : loadingStatus === "failed" ? ( + + + {t('list.error', { ns: 'sessions' })} + + + ) : null } Date: Thu, 22 May 2025 19:17:21 +0530 Subject: [PATCH 11/23] fix duplicate sessions in browse sessions page --- jam-ui/src/store/features/sessionsSlice.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/jam-ui/src/store/features/sessionsSlice.js b/jam-ui/src/store/features/sessionsSlice.js index 3ced285e8..d238528cf 100644 --- a/jam-ui/src/store/features/sessionsSlice.js +++ b/jam-ui/src/store/features/sessionsSlice.js @@ -77,9 +77,7 @@ export const SessionSlice = createSlice({ // add unique sessions to the array const records = new Set([...state.sessions, ...action.payload]); const unique = []; - records.forEach((item) => { - unique.push(item); - }); + records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x)) state.sessions = unique; state.error = null; state.status = "succeeded"; @@ -95,9 +93,7 @@ export const SessionSlice = createSlice({ // add unique sessions to the array const records = new Set([...state.sessions, ...action.payload.sessions]); const unique = []; - records.forEach((item) => { - unique.push(item); - }); + records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x)) state.sessions = unique; state.error = null; state.status = "succeeded"; @@ -109,9 +105,7 @@ export const SessionSlice = createSlice({ // add unique sessions to the array const records = new Set([...state.sessions, ...action.payload.sessions]); const unique = []; - records.forEach((item) => { - unique.push(item); - }); + records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x)) state.sessions = unique; state.error = null; state.status = "succeeded"; @@ -123,9 +117,7 @@ export const SessionSlice = createSlice({ // add unique sessions to the array const records = new Set([...state.sessions, ...action.payload.sessions]); const unique = []; - records.forEach((item) => { - unique.push(item); - }); + records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x)) state.sessions = unique; state.error = null; state.status = "succeeded"; From 676cbaa656b8a27b6576e620887a43a11ca37c0f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Fri, 23 May 2025 16:19:18 +0530 Subject: [PATCH 12/23] include subscription_utils script --- web/app/assets/javascripts/modern/scripts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web/app/assets/javascripts/modern/scripts.js b/web/app/assets/javascripts/modern/scripts.js index 392a0d30d..78120ec25 100644 --- a/web/app/assets/javascripts/modern/scripts.js +++ b/web/app/assets/javascripts/modern/scripts.js @@ -14,6 +14,7 @@ //= require react-components/actions/VideoActions //= require client_init //= require utils +//= require subscription_utils //= require jamkazam //= require modern/JamServer_copy From 2d5d93787f532a6399fb7b622c2ee34ea0fd9f22 Mon Sep 17 00:00:00 2001 From: Nuwan Chaturanga Date: Fri, 23 May 2025 12:53:49 +0000 Subject: [PATCH 13/23] Merged in 5539-gear_setup_reminders (pull request #59) gear setup reminder emails * gear setup reminder emails email reminders to setup audio gear Approved-by: Seth Call --- ...dd_gear_setup_reminder_columns_to_users.rb | 13 ++++++ ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 26 +++++++++++ .../user_mailer/gear_setup_reminder1.html.erb | 22 ++++++++++ .../user_mailer/gear_setup_reminder1.text.erb | 12 ++++++ .../user_mailer/gear_setup_reminder2.html.erb | 26 +++++++++++ .../user_mailer/gear_setup_reminder2.text.erb | 14 ++++++ .../user_mailer/gear_setup_reminder3.html.erb | 26 +++++++++++ .../user_mailer/gear_setup_reminder3.text.erb | 14 ++++++ ruby/lib/jam_ruby/lib/gear_setup_reminder.rb | 21 +++++++++ web/config/locales/en.yml | 43 +++++++++++++++++++ 10 files changed, 217 insertions(+) create mode 100644 ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.text.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.text.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.text.erb create mode 100644 ruby/lib/jam_ruby/lib/gear_setup_reminder.rb diff --git a/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb b/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb new file mode 100644 index 000000000..06185bbcf --- /dev/null +++ b/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb @@ -0,0 +1,13 @@ + class AddGearSetupReminderColumnsToUsers < ActiveRecord::Migration + def self.up + execute "ALTER TABLE users ADD COLUMN gear_setup_reminder1_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN gear_setup_reminder2_sent_at TIMESTAMP" + execute "ALTER TABLE users ADD COLUMN gear_setup_reminder3_sent_at TIMESTAMP" + end + + def self.down + execute "ALTER TABLE users DROP COLUMN gear_setup_reminder1_sent_at" + execute "ALTER TABLE users DROP COLUMN gear_setup_reminder2_sent_at" + execute "ALTER TABLE users DROP COLUMN gear_setup_reminder3_sent_at" + end + end diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 193402404..9dec8cfe6 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -454,6 +454,32 @@ module JamRuby end end + + #################################### GEAR SETUP REMINDER EMAILS #################################### + def gear_setup_reminder1(user) + @user = user + mail(:to => user.email, :subject => I18n.t('user_mailer.gear_setup_reminder1.subject')) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + + def gear_setup_reminder2(user) + @user = user + mail(:to => user.email, :subject => I18n.t('user_mailer.gear_setup_reminder2.subject')) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + + def gear_setup_reminder3(user) + @user = user + mail(:to => user.email, :subject => I18n.t('user_mailer.gear_setup_reminder3.subject')) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + #################################### NOTIFICATION EMAILS #################################### def friend_request(user, msg, friend_request_id) return if !user.subscribe_email diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.html.erb new file mode 100644 index 000000000..4507228a2 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.html.erb @@ -0,0 +1,22 @@ +

<%=I18n.t('user_mailer.gear_setup_reminder1.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder1.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder1.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder1.paragraph3').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder1.paragraph4').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder1.regards') -%>,
+ <%=I18n.t('user_mailer.gear_setup_reminder1.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.text.erb new file mode 100644 index 000000000..be0d164b9 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder1.text.erb @@ -0,0 +1,12 @@ +<%=I18n.t('user_mailer.gear_setup_reminder1.greeting') -%> <%= @user.first_name -%> + +<%=I18n.t('user_mailer.gear_setup_reminder1.paragraph1') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder1.paragraph2') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder1.paragraph3') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder1.paragraph4') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder1.regards') -%>, +<%=I18n.t('user_mailer.gear_setup_reminder1.signature') -%> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.html.erb new file mode 100644 index 000000000..f2382a262 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.html.erb @@ -0,0 +1,26 @@ +

<%=I18n.t('user_mailer.gear_setup_reminder2.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.paragraph3').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.paragraph4').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.paragraph5').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder2.regards') -%>,
+ <%=I18n.t('user_mailer.gear_setup_reminder2.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.text.erb new file mode 100644 index 000000000..322681494 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder2.text.erb @@ -0,0 +1,14 @@ +<%=I18n.t('user_mailer.gear_setup_reminder2.greeting') -%> <%= @user.first_name -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.paragraph1') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.paragraph2') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.paragraph3') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.paragraph4') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.paragraph5') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder2.regards') -%>, +<%=I18n.t('user_mailer.gear_setup_reminder2.signature') -%> diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.html.erb new file mode 100644 index 000000000..d76f7ce43 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.html.erb @@ -0,0 +1,26 @@ +

<%=I18n.t('user_mailer.gear_setup_reminder3.greeting') -%> <%= @user.first_name -%> -

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.paragraph1').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.paragraph2').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.paragraph3').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.paragraph4').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.paragraph5').html_safe -%> +

+ +

+ <%=I18n.t('user_mailer.gear_setup_reminder3.regards') -%>,
+ <%=I18n.t('user_mailer.gear_setup_reminder3.signature') -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.text.erb new file mode 100644 index 000000000..68a6ee788 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/gear_setup_reminder3.text.erb @@ -0,0 +1,14 @@ +<%=I18n.t('user_mailer.gear_setup_reminder3.greeting') -%> <%= @user.first_name -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.paragraph1') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.paragraph2') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.paragraph3') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.paragraph4') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.paragraph5') -%> + +<%=I18n.t('user_mailer.gear_setup_reminder3.regards') -%>, +<%=I18n.t('user_mailer.gear_setup_reminder3.signature') -%> diff --git a/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb new file mode 100644 index 000000000..bb1f06ff5 --- /dev/null +++ b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb @@ -0,0 +1,21 @@ +module JamRuby + class GearSetupReminder + + def self.prospect_users + User.where("users.first_certified_gear_at IS NULL") + end + + def self.reminder1_users + GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago) + end + + def self.reminder2_users + GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago) + end + + def self.reminder3_users + GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago) + end + + end +end \ No newline at end of file diff --git a/web/config/locales/en.yml b/web/config/locales/en.yml index 92778cd01..5f841d9b4 100644 --- a/web/config/locales/en.yml +++ b/web/config/locales/en.yml @@ -100,3 +100,46 @@ en: update_profile: "Update Profile" regards: "Best Regards," signature: "JamKazam Team" + + gear_setup_reminder1: + subject: "Set up audio gear in JamKazam now so you can get in your first session" + greeting: "Hello" + paragraph1: "The next thing you should do with JamKazam is set up your audio gear and test it in a solo session by yourself." + paragraph2: | + If you already have an audio interface and the ability to connect your computer to your home router using an Ethernet cable, you are ready to set up your gear. You can find all of our gear setup articles here. We recommend you use our articles on setting up your audio interface for Mac or for Windows to make sure you get this critical step done properly, and that you use our article on connecting your computer via Ethernet, which is the other critical setup step. + paragraph3: | + If you’re not sure what gear you need, we recommend you start by reading this article that explains this topic in general terms. Next, check this list of articles to find the one that best describes you. You need to use an audio interface rather than relying on the built-in mic on your computer, and you need to connect your computer to your Internet router using an Ethernet cable rather than using WiFi. See this list of articles for recommendations on gear. If you’re worried about spending money on gear without knowing how well JamKazam will work for you, you can buy gear on Amazon, try it for a week, and return it for a refund if you’re not happy for a risk-free trial experience. + paragraph4: | + If you have any trouble or feel confused about gear setup, you can email us for help at support@jamkazam.com. You can also visit with a JamKazam support team member in our weekly Zoom office hours, which is offered every Wednesday from 11am to 12pm US Central Time. + regards: "Best Regards," + signature: "JamKazam Team" + + gear_setup_reminder2: + subject: "Set up your gear in JamKazam now to connect with other musicians" + greeting: "Hello" + paragraph1: "We share your profile with JamKazam members who may be a good fit to play music with you during your first week on the platform via an automated email feature. Don’t miss this opportunity to get connected!" + paragraph2: "One of the critical pieces of information in getting you connected is your latency to other musicians on JamKazam (this is the amount of time it takes to get your audio to them). To get this information, you need to set up your gear in the JamKazam app." + paragraph3: | + If you already have an audio interface and the ability to connect your computer to your home router using an Ethernet cable, you are ready to set up your gear. You can find all of our gear setup articles here. We recommend you use our articles on setting up your audio interface for Mac or for Windows to make sure you get this critical step done properly, and that you use our article on connecting your computer via Ethernet, which is the other critical setup step. + paragraph4: | + If you’re not sure what gear you need, we recommend you start by reading this article that explains this topic in general terms. Next, check this list of articles to find the one that best describes you. You need to use an audio interface rather than relying on the built-in mic on your computer, and you need to connect your computer to your Internet router using an Ethernet cable rather than using WiFi. See this list of articles for recommendations on gear. If you’re worried about spending money on gear without knowing how well JamKazam will work for you, you can buy gear on Amazon, try it for a week, and return it for a refund if you’re not happy for a risk-free trial experience. + paragraph5: | + If you have any trouble or feel confused about gear setup, you can email us for help at support@jamkazam.com. You can also visit with a JamKazam support team member in our weekly Zoom office hours, which is offered every Wednesday from 11am to 12pm US Central Time. + regards: "Best Regards," + signature: "JamKazam Team" + + gear_setup_reminder3: + subject: Don’t waste your free 30-day premium gold plan - set up your gear now! + greeting: "Hello" + paragraph1: | + When you sign up for JamKazam, we give you a free 30-day premium gold plan so you can fully experience how amazing our online sessions are and how JamKazam can help you play more music with more people to bring more musical joy to your life. + paragraph2: | + Don’t waste your 30-day window! Set up your gear in the JamKazam app now, and get started. + paragraph3: | + If you already have an audio interface and the ability to connect your computer to your home router using an Ethernet cable, you are ready to set up your gear. You can find all of our gear setup articles here. We recommend you use our articles on setting up your audio interface for Mac or for Windows to make sure you get this critical step done properly, and that you use our article on connecting your computer via Ethernet, which is the other critical setup step. + paragraph4: | + If you’re not sure what gear you need, we recommend you start by reading this article that explains this topic in general terms. Next, check this list of articles to find the one that best describes you. You need to use an audio interface rather than relying on the built-in mic on your computer, and you need to connect your computer to your Internet router using an Ethernet cable rather than using WiFi. See this list of articles for recommendations on gear. If you’re worried about spending money on gear without knowing how well JamKazam will work for you, you can buy gear on Amazon, try it for a week, and return it for a refund if you’re not happy for a risk-free trial experience. + paragraph5: | + If you have any trouble or feel confused about gear setup, you can email us for help at support@jamkazam.com. You can also visit with a JamKazam support team member in our weekly Zoom office hours, which is offered every Wednesday from 11am to 12pm US Central Time. + regards: "Best Regards," + signature: "JamKazam Team" \ No newline at end of file From ad1d8ea373787f02b0e6553e000ee96e2c09cb98 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 27 May 2025 06:54:27 +0530 Subject: [PATCH 14/23] client downloads - fix download link for legacy clients --- jam-ui/src/components/public/JKDownloadsLegacy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jam-ui/src/components/public/JKDownloadsLegacy.js b/jam-ui/src/components/public/JKDownloadsLegacy.js index d45105190..55b135c1a 100644 --- a/jam-ui/src/components/public/JKDownloadsLegacy.js +++ b/jam-ui/src/components/public/JKDownloadsLegacy.js @@ -72,7 +72,7 @@ const JKDownloadsLegacy = () => { const downloadLink = React.useMemo(() => { if (!currentOS) return null - return downloads[`JamClient/${currentOS}`] + return downloads[`JamClient/${selectedPlatform}`] }, [currentOS]); const selectPlatform = (platform) => { From cd229a0b426187d079d96d6db6c04e78c5bf83df Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 29 May 2025 15:52:31 +0530 Subject: [PATCH 15/23] fix legacy app client download links --- jam-ui/src/components/public/JKDownloadsLegacy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jam-ui/src/components/public/JKDownloadsLegacy.js b/jam-ui/src/components/public/JKDownloadsLegacy.js index 55b135c1a..b76a3c933 100644 --- a/jam-ui/src/components/public/JKDownloadsLegacy.js +++ b/jam-ui/src/components/public/JKDownloadsLegacy.js @@ -72,8 +72,9 @@ const JKDownloadsLegacy = () => { const downloadLink = React.useMemo(() => { if (!currentOS) return null + if (!selectedPlatform) return null return downloads[`JamClient/${selectedPlatform}`] - }, [currentOS]); + }, [currentOS, selectedPlatform]); const selectPlatform = (platform) => { setSelectedPlatform(platform) From 817719d5398c2d53ae2fe1b0e380c642368cbce9 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 29 May 2025 16:15:43 +0530 Subject: [PATCH 16/23] show pointer cursor on download app links --- jam-ui/src/components/public/JKDownloads.js | 2 +- jam-ui/src/components/public/JKDownloadsLegacy.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jam-ui/src/components/public/JKDownloads.js b/jam-ui/src/components/public/JKDownloads.js index 47a5b1f06..ac26254cd 100644 --- a/jam-ui/src/components/public/JKDownloads.js +++ b/jam-ui/src/components/public/JKDownloads.js @@ -122,7 +122,7 @@ const JKDownloads = () => {

diff --git a/jam-ui/src/components/public/JKDownloadsLegacy.js b/jam-ui/src/components/public/JKDownloadsLegacy.js index b76a3c933..7e991aabb 100644 --- a/jam-ui/src/components/public/JKDownloadsLegacy.js +++ b/jam-ui/src/components/public/JKDownloadsLegacy.js @@ -127,7 +127,7 @@ const JKDownloadsLegacy = () => {

From 0f556bfad45acc083d1625861be37e7527dd49f7 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 11 Jun 2025 22:00:42 -0500 Subject: [PATCH 17/23] Only allow stopping a recording if you are the owner --- ruby/lib/jam_ruby/models/active_music_session.rb | 4 ++++ ruby/lib/jam_ruby/models/recording.rb | 7 ++++++- web/app/controllers/api_recordings_controller.rb | 5 +++++ web/config/application.rb | 1 + web/config/initializers/gon.rb | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index c909156fe..16260f839 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -863,6 +863,10 @@ module JamRuby self.save!(:validate => false) end + def in_session?(user) + self.users.exists?(user.id) + end + def connected_participant_count Connection.where(:music_session_id => self.id, :aasm_state => Connection::CONNECT_STATE.to_s, diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 926144983..7a29f29a0 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -75,6 +75,11 @@ module JamRuby has_stream_mix end + def can_stop?(user) + # only allow the starting-user to create (ideally, perhaps, only the client that did it) + user == owner + end + # this should be a has-one relationship. until this, this is easiest way to get from recording > mix def mix self.mixes[0] if self.mixes.length > 0 @@ -214,7 +219,7 @@ module JamRuby def has_access?(user) return false if user.nil? - users.exists?(user.id) || attached_with_lesson(user) #|| plays.where("player_id=?", user).count != 0 + users.exists?(user.id) || attached_with_lesson(user) || (music_session && music_session.in_session?(user)) end def attached_with_lesson(user) diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 3564e514e..74b8dc424 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -187,6 +187,11 @@ class ApiRecordingsController < ApiController def stop + # only allow the creator to stop the recording + if @recording.can_stop?(current_user) == false + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + @recording.stop if @recording.errors.any? diff --git a/web/config/application.rb b/web/config/application.rb index 033060c28..6de2590b1 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -386,6 +386,7 @@ if defined?(Bundler) config.video_available = "full" config.alerts_api_enabled = true + config.show_recording_debug_status = false config.gear_check_ignore_high_latency = false config.remove_whitespace_credit_card = false config.estimate_taxes = true diff --git a/web/config/initializers/gon.rb b/web/config/initializers/gon.rb index cbf6ec62e..6f675776f 100644 --- a/web/config/initializers/gon.rb +++ b/web/config/initializers/gon.rb @@ -31,5 +31,6 @@ Gon.global.braintree_token = Rails.application.config.braintree_token Gon.global.paypal_admin_only = Rails.application.config.paypal_admin_only Gon.global.use_video_conferencing_server = Rails.application.config.use_video_conferencing_server Gon.global.manual_override_installer_ends_with = Rails.application.config.manual_override_installer_ends_with +Gon.global.show_recording_debug_status = Rails.application.config.show_recording_debug_status Gon.global.env = Rails.env Gon.global.version = ::JamWeb::VERSION From 55372bf83d0853857bfceca9f9735db7d9ef43c4 Mon Sep 17 00:00:00 2001 From: Nuwan Chaturanga Date: Fri, 13 Jun 2025 12:02:21 +0000 Subject: [PATCH 18/23] Merged in 5631-signup-survey (pull request #61) signup survey email sending * signup survey email sending Send new user survey email 24 hours after signup to all new users * add config parameters add config.signup_survey_url, config.signup_survey_cutoff_date --- ...92511_add_signup_survey_sent_at_to_users.rb | 8 ++++++++ ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 10 ++++++++++ .../user_mailer/signup_survey.html.erb | 18 ++++++++++++++++++ .../user_mailer/signup_survey.text.erb | 11 +++++++++++ ruby/lib/jam_ruby/lib/email_signup_survey.rb | 16 ++++++++++++++++ .../jam_ruby/resque/scheduled/hourly_job.rb | 1 + web/config/application.rb | 2 ++ web/config/environments/development.rb | 2 ++ web/config/locales/en.yml | 8 ++++++++ 9 files changed, 76 insertions(+) create mode 100644 ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.text.erb create mode 100644 ruby/lib/jam_ruby/lib/email_signup_survey.rb diff --git a/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb new file mode 100644 index 000000000..879bda4a2 --- /dev/null +++ b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb @@ -0,0 +1,8 @@ + class AddSignupSurveySentAtToUsers < ActiveRecord::Migration + def self.up + execute "ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP" + end + def self.down + execute "ALTER TABLE users DROP COLUMN signup_survey_sent_at" + end + end diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 9dec8cfe6..0f2020854 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -480,6 +480,16 @@ module JamRuby end end + def signup_survey(user) + @user = user + @subject = I18n.t('user_mailer.signup_survey.subject') + @survey_url = Rails.application.config.signup_survey_url + mail(:to => user.email, :subject => @subject) do |format| + format.text + format.html { render layout: "user_mailer_beta" } + end + end + #################################### NOTIFICATION EMAILS #################################### def friend_request(user, msg, friend_request_id) return if !user.subscribe_email diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.html.erb new file mode 100644 index 000000000..b3e216fa9 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.html.erb @@ -0,0 +1,18 @@ +

<%= I18n.t 'user_mailer.signup_survey.greeting' -%> <%= @user.first_name -%> -

+ +

+ <%= I18n.t 'user_mailer.signup_survey.paragraph1' -%> +

+ +

+ <%= @survey_url %> +

+

+ <%= I18n.t 'user_mailer.signup_survey.ps' -%> + <%= I18n.t 'user_mailer.signup_survey.ps_text' -%> +

+ + +

<%= I18n.t 'user_mailer.signup_survey.regards' -%>
+ <%= I18n.t 'user_mailer.signup_survey.signature' -%> +

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.text.erb new file mode 100644 index 000000000..4834ebde6 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/signup_survey.text.erb @@ -0,0 +1,11 @@ +<%= I18n.t 'user_mailer.signup_survey.greeting' -%> <%= @user.first_name -%> - + +<%= I18n.t 'user_mailer.signup_survey.paragraph1' -%> + +<%= @survey_url %> + +<%= I18n.t 'user_mailer.signup_survey.ps' -%> +<%= I18n.t 'user_mailer.signup_survey.ps_text' -%> + +<%= I18n.t 'user_mailer.signup_survey.regards' -%>, +<%= I18n.t 'user_mailer.signup_survey.signature' -%> diff --git a/ruby/lib/jam_ruby/lib/email_signup_survey.rb b/ruby/lib/jam_ruby/lib/email_signup_survey.rb new file mode 100644 index 000000000..400513abe --- /dev/null +++ b/ruby/lib/jam_ruby/lib/email_signup_survey.rb @@ -0,0 +1,16 @@ +module JamRuby + class EmailSignupSurvey + def self.send_survey + # if signup survey email has not been sent to this user, then send it + survey_users.each do |user| + UserMailer.signup_survey(user).deliver_now + user.update(signup_survey_sent_at: Time.now) + end + end + + def self.survey_users + cutoff_date = Date.parse(Rails.application.config.signup_survey_cutoff_date) # Define a cutoff date for the survey + User.where("users.signup_survey_sent_at IS NULL AND users.created_at < ? AND users.created_at > ?", 1.days.ago, cutoff_date) + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index a73b813b9..420909a28 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -14,6 +14,7 @@ module JamRuby User.hourly_check AffiliatePartner.tally_up(Date.today) EmailProfileReminder.send_reminders + EmailSignupSurvey.send_survey ConnectionManager.new.cleanup_dangling @@log.info("done") diff --git a/web/config/application.rb b/web/config/application.rb index 6de2590b1..3ca4f1cf5 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -520,6 +520,8 @@ if defined?(Bundler) config.spa_origin_url = "http://beta.jamkazam.local:4000" config.user_match_monitoring_email = "user_match_monitoring_email@jamkazam.com" config.send_user_match_mail_only_to_jamkazam_team = true + config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL" + config.signup_survey_cutoff_date = "2025-06-10" config.action_mailer.asset_host = config.action_controller.asset_host end end diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb index efd1faf54..858e677b1 100644 --- a/web/config/environments/development.rb +++ b/web/config/environments/development.rb @@ -126,4 +126,6 @@ SampleApp::Application.configure do config.action_controller.asset_host = 'http://www.jamkazam.local:3000' config.send_user_match_mail_only_to_jamkazam_team = false + config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL" + config.signup_survey_cutoff_date = "2025-06-10" end diff --git a/web/config/locales/en.yml b/web/config/locales/en.yml index 5f841d9b4..82453cdb5 100644 --- a/web/config/locales/en.yml +++ b/web/config/locales/en.yml @@ -142,4 +142,12 @@ en: paragraph5: | If you have any trouble or feel confused about gear setup, you can email us for help at support@jamkazam.com. You can also visit with a JamKazam support team member in our weekly Zoom office hours, which is offered every Wednesday from 11am to 12pm US Central Time. regards: "Best Regards," + signature: "JamKazam Team" + signup_survey: + subject: "Let us help you to be successful on JamKazam" + greeting: "Hi" + paragraph1: "Thanks for signing up to join our community of musicians! Please click the link below and take 2 minutes to let us know a little more about your goals. Our support team will use this information to provide tailored, individual help to you to make it faster and easier for you to be successful." + ps: "p.s." + ps_text: "If we can help in any way, please always feel free to contact us at" + regards: "Best Regards," signature: "JamKazam Team" \ No newline at end of file From e7923dca9b3259ba717532a60dd7d202c4f08dfa Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Jun 2025 00:22:02 -0500 Subject: [PATCH 19/23] Updated both background jobs to not break HourlyJob --- ...1_add_profile_complete_columns_to_users.rb | 8 ++++ ...dd_gear_setup_reminder_columns_to_users.rb | 7 ++++ ...2511_add_signup_survey_sent_at_to_users.rb | 3 ++ ruby/lib/jam_ruby.rb | 2 + .../jam_ruby/lib/email_profile_reminder.rb | 41 +++++++++++-------- ruby/lib/jam_ruby/lib/email_signup_survey.rb | 15 +++++-- .../jam_ruby/resque/scheduled/hourly_job.rb | 5 ++- 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb index cef65f792..ca64edcc7 100644 --- a/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb +++ b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb @@ -16,3 +16,11 @@ execute "ALTER TABLE users DROP COLUMN profile_complete_reminder3_sent_at" end end +=begin + ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP + CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at) + ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP + ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP + ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP + UPDATE users set profile_completed_at=NOW() WHERE users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players); + end \ No newline at end of file diff --git a/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb b/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb index 06185bbcf..2af9c834e 100644 --- a/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb +++ b/ruby/db/migrate/20250511151844_add_gear_setup_reminder_columns_to_users.rb @@ -11,3 +11,10 @@ execute "ALTER TABLE users DROP COLUMN gear_setup_reminder3_sent_at" end end + + +=begin + ALTER TABLE users ADD COLUMN gear_setup_reminder1_sent_at TIMESTAMP; +ALTER TABLE users ADD COLUMN gear_setup_reminder2_sent_at TIMESTAMP; +ALTER TABLE users ADD COLUMN gear_setup_reminder3_sent_at TIMESTAMP; + end \ No newline at end of file diff --git a/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb index 879bda4a2..5adb973fd 100644 --- a/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb +++ b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb @@ -6,3 +6,6 @@ execute "ALTER TABLE users DROP COLUMN signup_survey_sent_at" end end +=begin + ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP +=end \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 789a5e6cb..112c35648 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -120,6 +120,8 @@ require "jam_ruby/lib/subscription_message" require "jam_ruby/lib/stats.rb" require "jam_ruby/lib/email_new_musician_match" require "jam_ruby/lib/musician_filter" +require "jam_ruby/lib/email_profile_reminder" +require "jam_ruby/lib/email_signup_survey" require "jam_ruby/amqp/amqp_connection_manager" require "jam_ruby/database" require "jam_ruby/message_factory" diff --git a/ruby/lib/jam_ruby/lib/email_profile_reminder.rb b/ruby/lib/jam_ruby/lib/email_profile_reminder.rb index a253f0eb7..c5de827fc 100644 --- a/ruby/lib/jam_ruby/lib/email_profile_reminder.rb +++ b/ruby/lib/jam_ruby/lib/email_profile_reminder.rb @@ -1,25 +1,30 @@ module JamRuby class EmailProfileReminder + @@log = Logging.logger[EmailProfileReminder] def self.send_reminders - #If the user has not updated their profile 1 day after signup, then send reminder email1 - reminder1_users.each do |user| - UserMailer.profile_complete_reminder1(user).deliver_now - user.update(profile_complete_reminder1_sent_at: Time.now) - end + begin + #If the user has not updated their profile 1 day after signup, then send reminder email1 + reminder1_users.find_each do |user| + UserMailer.profile_complete_reminder1(user).deliver_now + User.where(id: user.id).update_all(profile_complete_reminder1_sent_at: Time.now) + end - #If the user has not updated their profile 3 days after signup, then send reminder email2 - reminder2_users.each do |user| - UserMailer.profile_complete_reminder2(user).deliver_now - user.update(profile_complete_reminder2_sent_at: Time.now) - end + #If the user has not updated their profile 3 days after signup, then send reminder email2 + reminder2_users.find_each do |user| + UserMailer.profile_complete_reminder2(user).deliver_now + User.where(id: user.id).update_all(profile_complete_reminder2_sent_at: Time.now) + end - #If the user has not updated their profile 5 days after signup, then send reminder email3 - reminder3_users.each do |user| - UserMailer.profile_complete_reminder3(user).deliver_now - user.update(profile_complete_reminder3_sent_at: Time.now) + #If the user has not updated their profile 5 days after signup, then send reminder email3 + reminder3_users.find_each do |user| + UserMailer.profile_complete_reminder3(user).deliver_now + User.where(id: user.id).update_all(profile_complete_reminder3_sent_at: Time.now) + end + rescue Exception => e + @@log.error("unable to send profile reminder email=#{e}") + puts "unable to send profile reminder email=#{e}" end - end def self.prospect_users @@ -27,15 +32,15 @@ module JamRuby end def self.reminder1_users - EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NULL", 1.day.ago) + EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NULL", 1.day.ago) end def self.reminder2_users - EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", 3.days.ago) + EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", 3.days.ago) end def self.reminder3_users - EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", 5.days.ago) + EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", 5.days.ago) end diff --git a/ruby/lib/jam_ruby/lib/email_signup_survey.rb b/ruby/lib/jam_ruby/lib/email_signup_survey.rb index 400513abe..3d296790b 100644 --- a/ruby/lib/jam_ruby/lib/email_signup_survey.rb +++ b/ruby/lib/jam_ruby/lib/email_signup_survey.rb @@ -1,10 +1,17 @@ module JamRuby class EmailSignupSurvey + @@log = Logging.logger[EmailSignupSurvey] + def self.send_survey - # if signup survey email has not been sent to this user, then send it - survey_users.each do |user| - UserMailer.signup_survey(user).deliver_now - user.update(signup_survey_sent_at: Time.now) + begin + # if signup survey email has not been sent to this user, then send it + survey_users.find_each do |user| + UserMailer.signup_survey(user).deliver_now + User.where(id: user.id).update_all(signup_survey_sent_at: Time.now) + end + rescue Exception => e + @@log.error("unable to send surevy email e=#{e}") + puts "unable to send surevy email e=#{e}" end end diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index 420909a28..c94ec9eab 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -13,7 +13,10 @@ module JamRuby #TeacherPayment.hourly_check User.hourly_check AffiliatePartner.tally_up(Date.today) - EmailProfileReminder.send_reminders + + # bring me back in once tested profile_completed_at is set somewhere + #EmailProfileReminder.send_reminders + EmailSignupSurvey.send_survey ConnectionManager.new.cleanup_dangling From 7c9e449c4bdf49bd91d92c9c7cffe01a1cf5988d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Jun 2025 00:33:03 -0500 Subject: [PATCH 20/23] Hookup gear reminder emails --- ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/lib/email_signup_survey.rb | 2 +- ruby/lib/jam_ruby/lib/gear_setup_reminder.rb | 40 +++++++++++++++---- .../jam_ruby/resque/scheduled/hourly_job.rb | 1 + 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 112c35648..ea2654040 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -122,6 +122,7 @@ require "jam_ruby/lib/email_new_musician_match" require "jam_ruby/lib/musician_filter" require "jam_ruby/lib/email_profile_reminder" require "jam_ruby/lib/email_signup_survey" +require "jam_ruby/lib/gear_setup_reminder" require "jam_ruby/amqp/amqp_connection_manager" require "jam_ruby/database" require "jam_ruby/message_factory" diff --git a/ruby/lib/jam_ruby/lib/email_signup_survey.rb b/ruby/lib/jam_ruby/lib/email_signup_survey.rb index 3d296790b..1fe847013 100644 --- a/ruby/lib/jam_ruby/lib/email_signup_survey.rb +++ b/ruby/lib/jam_ruby/lib/email_signup_survey.rb @@ -16,7 +16,7 @@ module JamRuby end def self.survey_users - cutoff_date = Date.parse(Rails.application.config.signup_survey_cutoff_date) # Define a cutoff date for the survey + cutoff_date = Date.parse(Rails.application.config.signup_survey_cutoff_date) # Define a cutoff date for the survey/gear setup emails User.where("users.signup_survey_sent_at IS NULL AND users.created_at < ? AND users.created_at > ?", 1.days.ago, cutoff_date) end end diff --git a/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb index bb1f06ff5..14e5913ca 100644 --- a/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb +++ b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb @@ -1,20 +1,46 @@ module JamRuby class GearSetupReminder - + + @@log = Logging.logger[GearSetupReminder] + + def self.send_reminders + begin + cutoff_date = Date.parse(Rails.application.config.signup_survey_cutoff_date) # Define a cutoff date for the survey/gear setup emails + + reminder1_users(cutoff_date).find_each do |user| + UserMailer.gear_setup_reminder1(user).deliver_now + User.where(id: user.id).update_all(gear_setup_reminder1_sent_at: Time.now) + end + + reminder2_users(cutoff_date).find_each do |user| + UserMailer.gear_setup_reminder2(user).deliver_now + User.where(id: user.id).update_all(gear_setup_reminder2_sent_at: Time.now) + end + + reminder3_users(cutoff_date).find_each do |user| + UserMailer.gear_setup_reminder3(user).deliver_now + User.where(id: user.id).update_all(gear_setup_reminder3_sent_at: Time.now) + end + rescue Exception => e + @@log.error("unable to send gear setup reminder email #{e}") + puts "unable to send gear setup reminder email #{e}" + end + end + def self.prospect_users User.where("users.first_certified_gear_at IS NULL") end - def self.reminder1_users - GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago) + def self.reminder1_users(cutoff) + GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago, cutoff_date) end - def self.reminder2_users - GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago) + def self.reminder2_users(cutoff) + GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago, cutoff_date) end - def self.reminder3_users - GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago) + def self.reminder3_users(cutoff) + GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago, cutoff_date) end end diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index c94ec9eab..95294b037 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -18,6 +18,7 @@ module JamRuby #EmailProfileReminder.send_reminders EmailSignupSurvey.send_survey + GearSetupReminder.send_reminders ConnectionManager.new.cleanup_dangling @@log.info("done") From dcdf9e55a3effcaf0d99bbf67dc71ff6a7c915bb Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Jun 2025 01:14:35 -0500 Subject: [PATCH 21/23] Fix develop --- ruby/lib/jam_ruby/lib/gear_setup_reminder.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb index 14e5913ca..cc5eb608a 100644 --- a/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb +++ b/ruby/lib/jam_ruby/lib/gear_setup_reminder.rb @@ -31,15 +31,15 @@ module JamRuby User.where("users.first_certified_gear_at IS NULL") end - def self.reminder1_users(cutoff) + def self.reminder1_users(cutoff_date) GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago, cutoff_date) end - def self.reminder2_users(cutoff) + def self.reminder2_users(cutoff_date) GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago, cutoff_date) end - def self.reminder3_users(cutoff) + def self.reminder3_users(cutoff_date) GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago, cutoff_date) end From 51ed748013de2111320ff93a8c11036750e828bd Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Jun 2025 10:02:18 -0500 Subject: [PATCH 22/23] DB updates after actually updating prod --- ...25441_add_profile_complete_columns_to_users.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb index ca64edcc7..3d6a08c66 100644 --- a/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb +++ b/ruby/db/migrate/20250227125441_add_profile_complete_columns_to_users.rb @@ -7,7 +7,10 @@ execute "ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP" execute "ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP" - User.where('users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players)').update_all(profile_completed_at: Time.now) + User.find_each(batch_size:100) do |user| + User.where(id:user.id).update_all(profile_completed_at: Time.now) + end + #User.where('users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players)').update_all(profile_completed_at: Time.now) end def self.down execute "ALTER TABLE users DROP COLUMN profile_completed_at" @@ -17,10 +20,10 @@ end end =begin - ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP - CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at) - ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP - ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP - ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP + ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP; + CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at); + ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP; + ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP; + ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP; UPDATE users set profile_completed_at=NOW() WHERE users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players); end \ No newline at end of file From 5f3a327d3594b16fe2d3d73112f3d38b01f1c188 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Jun 2025 17:37:34 -0500 Subject: [PATCH 23/23] Enable send reminders code, because saving profile_completed_at now --- ...0250605092511_add_signup_survey_sent_at_to_users.rb | 10 +++++++++- ruby/lib/jam_ruby/models/user.rb | 6 ++++++ ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb | 5 +---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb index 5adb973fd..f8f271c99 100644 --- a/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb +++ b/ruby/db/migrate/20250605092511_add_signup_survey_sent_at_to_users.rb @@ -1,11 +1,19 @@ class AddSignupSurveySentAtToUsers < ActiveRecord::Migration def self.up execute "ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP" + User.find_each(batch_size:100) do |user| + User.where(id:user.id).update_all(signup_survey_sent_at: Time.now) + end end def self.down execute "ALTER TABLE users DROP COLUMN signup_survey_sent_at" end end =begin - ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP + ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP; +CREATE INDEX index_users_on_signup_survey_sent_at ON users USING btree (signup_survey_sent_at); + + User.find_each(batch_size:100) do |user| + User.where(id:user.id).update_all(signup_survey_sent_at: Time.now) + end =end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index eab4d54a3..3bd1da941 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -420,6 +420,12 @@ module JamRuby User.where(id: self.id).update_all(updates) end + + # check if the profile looks complete + if musician_instruments.length > 0 || genre_players.length > 0 + User.where(id: self.id).update_all(profile_completed_at: Time.now) + end + end def self.hourly_check diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index 95294b037..45606b896 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -13,10 +13,7 @@ module JamRuby #TeacherPayment.hourly_check User.hourly_check AffiliatePartner.tally_up(Date.today) - - # bring me back in once tested profile_completed_at is set somewhere - #EmailProfileReminder.send_reminders - + EmailProfileReminder.send_reminders EmailSignupSurvey.send_survey GearSetupReminder.send_reminders ConnectionManager.new.cleanup_dangling