diff --git a/jam-ui/cypress/e2e/account/change-email-confirm-page.cy.js b/jam-ui/cypress/e2e/account/change-email-confirm-page.cy.js
new file mode 100644
index 000000000..c4751a10c
--- /dev/null
+++ b/jam-ui/cypress/e2e/account/change-email-confirm-page.cy.js
@@ -0,0 +1,24 @@
+///
+import makeFakeUser from '../../factories/user';
+describe('Change Email Confirm Page', () => {
+ beforeEach(() => {
+ // Log in to the application or navigate to the account page
+ // where the change email feature is available
+ const currentUser = makeFakeUser({
+ email: 'sam@example.com'
+ });
+ cy.stubAuthenticate({ ...currentUser });
+ });
+
+ it('should display the confirm page when visiting the confirm URL', () => {
+ // Replace with a realistic token for your app if needed
+ const token = 'dummy-confirm-token';
+
+ // Visit the confirm URL
+ cy.visit(`/public/confirm-email-change?token=${token}`);
+
+ // Assert that the JKChangeEmailConfirm page is rendered
+ // Adjust selectors/texts as per your actual component
+ cy.contains('Change Email Confirmation').should('be.visible');
+ });
+});
\ No newline at end of file
diff --git a/jam-ui/src/components/public/JKConfirmEmailChange.js b/jam-ui/src/components/public/JKConfirmEmailChange.js
new file mode 100644
index 000000000..f9d18730d
--- /dev/null
+++ b/jam-ui/src/components/public/JKConfirmEmailChange.js
@@ -0,0 +1,47 @@
+import React from 'react'
+import { useLocation } from "react-router-dom";
+import { Card, CardBody, CardText, CardTitle } from 'reactstrap';
+import PropTypes from 'prop-types';
+import { useState, useEffect } from 'react';
+import { updateEmail } from '../../helpers/rest';
+
+const JKConfirmEmailChange = () => {
+ const location = useLocation();
+ const params = new URLSearchParams(location.search);
+ const token = params.get('token');
+
+ const [success, setSuccess] = useState(false);
+
+ useEffect(() => {
+ if (token) {
+ updateEmail(token)
+ .then(response => {
+ if (response.status === 200) {
+ setSuccess(true);
+ } else {
+ setSuccess(false);
+ }
+ })
+ .catch(() => {
+ setSuccess(false);
+ });
+ }
+ }, [token]);
+
+ return (
+
+
+ Unsubscribe from JamKazam emails
+
+ {
+ success?
+ 'Your email has been successfully updated.' :
+ 'Loading...'
+ }
+
+
+
+ )
+}
+
+export default JKConfirmEmailChange
\ No newline at end of file
diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js
index a1ec75a58..c6bb87894 100644
--- a/jam-ui/src/helpers/rest.js
+++ b/jam-ui/src/helpers/rest.js
@@ -101,8 +101,8 @@ export const getInstruments = () => {
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
apiFetch('/me')
- .then(response => resolve(response))
- .catch(error => reject(error))
+ .then(response => resolve(response))
+ .catch(error => reject(error))
})
}
@@ -289,7 +289,7 @@ export const getCities = (countryId, regionId) => {
export const postUpdateAccountEmail = (userId, options) => {
const { email, current_password } = options;
return new Promise((resolve, reject) => {
- apiFetch(`/users/${userId}/update_email`, {
+ apiFetch(`/users/${userId}/update_email_alt`, {
method: 'POST',
body: JSON.stringify({ update_email: email, current_password })
})
@@ -339,7 +339,7 @@ export const requstResetForgotPassword = email => {
})
.then(response => resolve(response))
.catch(error => reject(error));
- });
+ });
};
export const resetForgotPassword = (options = {}) => {
@@ -491,9 +491,9 @@ export const getJamTrackPublic = options => {
const { plan_code } = options;
return new Promise((resolve, reject) => {
// This does not make sense; historical reasons here
- apiFetch(`/jamtracks/band/${plan_code}?${new URLSearchParams({plan_code})}`)
- .then(response => resolve(response))
- .catch(error => reject(error));
+ apiFetch(`/jamtracks/band/${plan_code}?${new URLSearchParams({ plan_code })}`)
+ .then(response => resolve(response))
+ .catch(error => reject(error));
});
};
@@ -683,7 +683,7 @@ export const createAlert = (subject, data) => {
return new Promise((resolve, reject) => {
apiFetch(`/alerts`, {
method: 'POST',
- body: JSON.stringify({subject, data})
+ body: JSON.stringify({ subject, data })
})
.then(response => resolve(response))
.catch(error => reject(error));
@@ -700,8 +700,8 @@ export const getClientDownloads = () => {
export const getObsPluginDownloads = () => {
return new Promise((resolve, reject) => {
apiFetch(`/artifacts/OBSPlugin`)
- .then(response => resolve(response))
- .catch(error => reject(error));
+ .then(response => resolve(response))
+ .catch(error => reject(error));
});
}
@@ -719,7 +719,7 @@ export const paypalPlaceOrder = (options = {}) => {
export const submitStripe = (options = {}) => {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve, reject) => {
apiFetch(`/stripe`, {
method: 'POST',
body: JSON.stringify(options)
@@ -749,4 +749,28 @@ export const updatePayment = (options = {}) => {
.then(response => resolve(response))
.catch(error => reject(error));
});
-};
\ No newline at end of file
+};
+
+// function postUpdateEmail(email, current_password) {
+
+// var url = "/api/users/" + context.JK.currentUserId + "/update_email";
+// return $.ajax({
+// type: "POST",
+// dataType: "json",
+// contentType: 'application/json',
+// url: url,
+// data: JSON.stringify({ update_email: email, current_password: current_password }),
+// processData: false
+// });
+// }
+
+export const updateEmail = (userId, email, current_password) => {
+ return new Promise((resolve, reject) => {
+ apiFetch(`/users/${userId}/update_email`, {
+ method: 'POST',
+ body: JSON.stringify({ update_email: email, current_password })
+ })
+ .then(response => resolve(response))
+ .catch(error => reject(error));
+ });
+}
\ No newline at end of file
diff --git a/jam-ui/src/layouts/JKPublicRoutes.js b/jam-ui/src/layouts/JKPublicRoutes.js
index f808dd81d..f4330eb0c 100644
--- a/jam-ui/src/layouts/JKPublicRoutes.js
+++ b/jam-ui/src/layouts/JKPublicRoutes.js
@@ -11,6 +11,7 @@ import JKUnsubscribe from '../components/public/JKUnsubscribe';
import JKDownloads from '../components/public/JKDownloads';
import JKDownloadsLegacy from '../components/public/JKDownloadsLegacy';
import JKObsDownloads from '../components/public/JKObsDownloads';
+import JKConfirmEmailChange from '../components/public/JKConfirmEmailChange';
import JKJamTracksLanding from '../components/jamtracks/JKJamTracksLandingDev';
import JKJamTracksArtistLanding from '../components/jamtracks/JKJamTracksArtistLandingDev';
@@ -23,6 +24,7 @@ const JKPublicRoutes = ({ match: { url } }) => (
+
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 3d6a08c66..2fef976d3 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
@@ -19,11 +19,5 @@
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/20250322000000_affiliate_tracking_totals.rb b/ruby/db/migrate/20250322000000_affiliate_tracking_totals.rb
index 6d3140c6f..5ee85575b 100644
--- a/ruby/db/migrate/20250322000000_affiliate_tracking_totals.rb
+++ b/ruby/db/migrate/20250322000000_affiliate_tracking_totals.rb
@@ -6,7 +6,7 @@
# affiliate_quarterly_payments.subscription_due_amount_in_cents
# affiliate_monthly_payments.jamtrack_due_amount_in_cents
# affiliate_monthly_payments.subscription_due_amount_in_cents
-class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
+class AffiliateTrackingTotals < ActiveRecord::Migration
def self.up
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtrack_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0"
@@ -16,16 +16,16 @@ class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
execute "ALTER TABLE affiliate_partners ADD COLUMN subscriptions_current_quarter_in_cents INTEGER NOT NULL DEFAULT 0"
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtracks_sold INTEGER NOT NULL DEFAULT 0"
execute "ALTER TABLE affiliate_partners ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_monthy_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
- execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
+ # execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
+ # execute "ALTER TABLE affiliate_monthy_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
+ # execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
+ # execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
+ #execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
+ #execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
execute "CREATE INDEX affiliate_partner_user_id_idx ON affiliate_partners USING btree (partner_user_id);"
- execute "CREATE INDEX affiliate_quarterly_payments_closed_index ON affiliate_quarterly_payments USING btree (paid);"
- execute "CREATE INDEX affiliate_quarterly_payments_paid_index ON affiliate_quarterly_payments USING btree (closed);"
- execute "CREATE INDEX affiliate_monthly_payments_paid_index ON affiliate_monthly_payments USING btree (closed);"
+ # execute "CREATE INDEX affiliate_quarterly_payments_closed_index ON affiliate_quarterly_payments USING btree (paid);"
+ # execute "CREATE INDEX affiliate_quarterly_payments_paid_index ON affiliate_quarterly_payments USING btree (closed);"
+ # execute "CREATE INDEX affiliate_monthly_payments_paid_index ON affiliate_monthly_payments USING btree (closed);"
end
=begin
@@ -54,7 +54,7 @@ class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
execute "ALTER TABLE affiliate_partners DROP COLUMN jamtrack_cumulative_earnings_in_cents"
execute "ALTER TABLE affiliate_partners DROP COLUMN subscriptions_cumulative_earnings_in_cents"
execute "ALTER TABLE affiliate_partners DROP COLUMN subscriptions_count"
- execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN jamtrack_due_amount_in_cents"
- execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN subscription_due_amount_in_cents"
+ # execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN jamtrack_due_amount_in_cents"
+ # execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN subscription_due_amount_in_cents"
end
end
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 2af9c834e..06185bbcf 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,10 +11,3 @@
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/lib/jam_ruby/models/user_observer.rb b/ruby/lib/jam_ruby/models/user_observer.rb
index 94da9ebc7..7efb3b1f8 100644
--- a/ruby/lib/jam_ruby/models/user_observer.rb
+++ b/ruby/lib/jam_ruby/models/user_observer.rb
@@ -5,7 +5,7 @@ module JamRuby
def after_save(user)
if user.updating_email && !user.errors.any?
- UserMailer.updating_email(user).deliver_now
+ UserMailer.begin_update_email(user).deliver_now
elsif user.updated_email && !user.errors.any?
UserMailer.updated_email(user).deliver_now
elsif user.setting_password && !user.errors.any?
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 1ef1e8d84..7b64b6863 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -615,6 +615,15 @@ class ApiUsersController < ApiController
# NOTE: if you change confirm_email_link value below, you break outstanding email changes because links in user inboxes are broken
confirm_email_link = confirm_email_url + "?token="
+ do_bigin_complete_email(confirm_email_link)
+ end
+
+ def begin_update_email_alt
+ confirm_email_link = ApplicationHelper.spa_base_uri + '/public/confirm-email-change' + "?token="
+ do_bigin_complete_email(confirm_email_link)
+ end
+
+ def do_bigin_complete_email(confirm_email_link)
current_user.begin_update_email(params[:update_email], params[:current_password], confirm_email_link)
if current_user.errors.any?
diff --git a/web/config/routes.rb b/web/config/routes.rb
index 3b174973c..a152b4561 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -511,6 +511,7 @@ Rails.application.routes.draw do
# user account settings
match '/users/:id/update_email' => 'api_users#begin_update_email', :via => :post, :as => 'begin_update_email'
+ match '/users/:id/update_email_alt' => 'api_users#begin_update_email_alt', :via => :post, :as => 'begin_update_email_alt'
match '/users/update_email/:token' => 'api_users#finalize_update_email', :via => :post, :as => 'finalize_update_email'
# user profile
diff --git a/web/spec/requests/users_api_spec.rb b/web/spec/requests/users_api_spec.rb
index f2630f149..cf2c68c11 100644
--- a/web/spec/requests/users_api_spec.rb
+++ b/web/spec/requests/users_api_spec.rb
@@ -248,6 +248,12 @@ describe "User API", :type => :api do
return last_response
end
+ def begin_update_email_alt(authenticated_user, update_email, validation_password, login = true)
+ login(authenticated_user.email, authenticated_user.password, 200, true) if login
+ post "/api/users/#{authenticated_user.id}/update_email_alt.json", { :update_email => update_email, :current_password => validation_password }.to_json, "CONTENT_TYPE" => 'application/json'
+ return last_response
+ end
+
def finalize_update_email(update_email_token)
post "/api/users/update_email/#{update_email_token}.json", {}.to_json, "CONTENT_TYPE" => 'application/json'
return last_response
@@ -907,7 +913,7 @@ describe "User API", :type => :api do
end
########## UPDATE EMAIL ########
- describe "update email" do
+ describe "update email", focus: true do
describe "begin update email" do
it "success" do
last_response = begin_update_email(user, "not_taken_test@jamkazam.com", user.password)
@@ -933,6 +939,12 @@ describe "User API", :type => :api do
last_response.status.should == 403
UserMailer.deliveries.length.should == 0
end
+
+ it "success alt" do
+ last_response = begin_update_email_alt(user, "not_taken_test_alt@jamkazam.com", user.password)
+ last_response.status.should == 200
+ UserMailer.deliveries.length.should == 1
+ end
end
describe "finalize update email" do