Merged in feature/affiliate-testing (pull request #56)

Update signups aggressively as they occur (realtime), and fix some affiliate and jamtrack flows
This commit is contained in:
Seth Call 2025-03-10 20:38:02 +00:00
commit e6046f74b6
17 changed files with 5342 additions and 31 deletions

View File

@ -37,3 +37,4 @@ cd jam-ui
npm run start
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.

View File

@ -3,3 +3,4 @@ output
node_modules
public
jam_track_tracks_for_jam_ui*

View File

@ -4,7 +4,7 @@
if [ -n "$1" ]; then
SAVE_TO="$1"
else
SAVE_TO=`pwd`
SAVE_TO=/tmp
fi
echo "Saving to $SAVE_TO"
@ -16,9 +16,10 @@ psql jam -c "COPY( select id, original_artist, name , original_artist_slug, name
# dump all artists
psql jam -c "COPY( select original_artist, original_artist_slug, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug ) as \"URL\" FROM jam_tracks group by original_artist, original_artist_slug) TO '$SAVE_TO/jam_tracks_for_jam_ui_artists.$USER.csv' with CSV HEADER;"
psql jam -c "COPY( select id, part, instrument_id, (select description from instruments where id = instrument_id) as instrument_description, track_type, position, preview_mp3_url, preview_url as preview_ogg_url, preview_acc_url from jam_track_tracks) TO '$SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv' with CSV HEADER;"
psql jam -c "COPY( select id, part, instrument_id, (select description from instruments where id = instrument_id) as instrument_description, track_type, position, preview_mp3_url, preview_url as preview_ogg_url, preview_aac_url from jam_track_tracks) TO '$SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv' with CSV HEADER;"
echo "Moving personal files to $SAVE_TO"
sudo mv $SAVE_TO/jam_tracks_for_jam_ui.$USER.csv .
sudo mv $SAVE_TO/jam_tracks_for_jam_ui_artists.$USER.csv .
sudo mv $SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv .
echo "Moving personal files to jamtracks-for-env"
mkdir -p jamtracks-for-env
sudo mv $SAVE_TO/jam_tracks_for_jam_ui.$USER.csv jamtracks-for-env
sudo mv $SAVE_TO/jam_tracks_for_jam_ui_artists.$USER.csv jamtracks-for-env
sudo mv $SAVE_TO/jam_track_tracks_for_jam_ui.$USER.csv jamtracks-for-env

View File

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ const JKAffiliateSignup = ({signup}) => {
{greaterThan.sm ? (
<tr>
<td>{signup.month}</td>
<td className="text-center">{signup.visits}</td>
{/*<td className="text-center">{signup.visits}</td>*/}
<td className="text-center">{signup.signups}</td>
</tr>
) : (
@ -17,11 +17,11 @@ const JKAffiliateSignup = ({signup}) => {
<Row className="mb-1">
<Col>{signup.month}</Col>
</Row>
<Row className="mb-1">
{/*<Row className="mb-1">
<Col>
<small>Visits: {signup.visits}</small>
</Col>
</Row>
</Row>*/}
<Row className="mb-1">
<Col>
<small>Signups: {signup.signups}</small>

View File

@ -6,15 +6,16 @@ import JKAffiliateSignup from './JKAffiliateSignup';
const JKAffiliateSignupsList = ({ signups, nextPage, loading, stepForward }) => {
const { t } = useTranslation('affiliate');
return (
<div>
<Table striped bordered className="fs--1" data-testid="affiliateSignupList">
<thead className="bg-200 text-900">
<tr>
<th width="35%" scope="col">
{t('signups.list.header.date')}
</th>
<th width="35%" scope="col" className="text-center">
{/* <th width="35%" scope="col" className="text-center">
{t('signups.list.header.visits')}
</th>
</th> */}
<th scope="col" className="text-center">
{t('signups.list.header.signups')}
</th>
@ -25,10 +26,11 @@ const JKAffiliateSignupsList = ({ signups, nextPage, loading, stepForward }) =>
<JKAffiliateSignup key={index} signup={signup} />
))}
</tbody>
<div>
</Table>
<div>
{nextPage && nextPage > 0 && !loading && <button onClick={stepForward}>{t('signups.load_more')}</button>}
</div>
</Table>
</div>
);
};

View File

@ -169,7 +169,7 @@ const ArtistBodyComponent = ({
window.removeEventListener("resize", updateHeight);
window.removeEventListener("resize", checkOverflow);
}
});
}, [currentUser, jamTrack, jamTrackLoading]);
// This was a different approach to allow you to listen to different songs inline
/**

View File

@ -35,6 +35,8 @@ const BodyComponent = ({
const {addToCart} = useJamTrackShopping()
const { currentUser } = useAuth();
console.log(`loading jamtrack with slug ${slug}`)
const isLoggedIn = () => {
return currentUser != null
}
@ -46,7 +48,7 @@ const BodyComponent = ({
const ctaClick = () => {
const addJamTrackToCart = async () => {
try {
await addToCart(jamTrack);
await addToCart(jamTrack, true);
} catch (error) {
console.error(error);
alert("Unable to add JamTrack to your cart.");
@ -60,7 +62,7 @@ const BodyComponent = ({
} else {
console.log("no user")
// redirect to the signup screen with the jamtrack & artist params set
window.location.href = "/auth/signup?artist=" + encodeURIComponent(artist) + '&jamtrack=' + encodeURIComponent(slug)
window.location.href = "/auth/signup?jamtrack=" + encodeURIComponent(slug)
}
} else {
@ -105,6 +107,7 @@ const BodyComponent = ({
//coming to signup page after clicking on jamtrack or artist
useEffect(() => {
console.log("useEffect, currentUser", currentUser)
updateHeight(); // Run after initial render
window.addEventListener("resize", updateHeight); // Listen to resize
@ -172,7 +175,7 @@ const BodyComponent = ({
return () => window.removeEventListener("resize", updateHeight);
});
}, [currentUser, jamTrack, jamTrackLoading]);
return (
<div className="jamtrack-landing-body">

View File

@ -1,6 +1,7 @@
import React, {useContext, useEffect} from "react";
import { BrowserRouter as Router, ReactDOM } from "react-router-dom";
import UserAuth from '../../context/UserAuth';
import JKJamTracksLandingBody from "./JKJamTracksLandingBody";
import AppContext from "../../context/Context";
import {useResponsive} from "@farfetch/react-context-responsive";
@ -47,10 +48,12 @@ const TemplatePage = ({ id, plan_code, slug, artist, song, location}) => {
<Logo at="navbar-top" id="topLogo" width={240}/>
</Navbar>
<div className={`pt-3 row ${paddingClass}`}>
<BrowserQueryProvider>
<JKJamTracksLandingBody id={id} plan_code={plan_code} slug={slug} song={song} artist={artist}
<UserAuth path={location}>
<BrowserQueryProvider>
<JKJamTracksLandingBody id={id} plan_code={plan_code} slug={slug} song={song} artist={artist}
provided_jam_track={null}/>
</BrowserQueryProvider>
</BrowserQueryProvider>
</UserAuth>
</div>
{/* <div className='px-6 row'> */}
<Footer/>

View File

@ -28,7 +28,7 @@ const ProfileDropdown = () => {
});
setCurrentUser(null);
await logout();
window.location.href = "/";
window.location.href = "/auth/login";
};
return (

View File

@ -12,7 +12,9 @@ const useJamTrackShopping = () => {
const { t } = useTranslation('jamtracks');
const history = useHistory();
const addToCart = async (jamTrack) => {
// forceReload means to navigate 'forcefully' using window.location.href, because
// most likely coming from a landing page, which is not a part of the SPA
const addToCart = async (jamTrack, forceReload = false) => {
if (!currentUser) {
return;
}
@ -32,13 +34,25 @@ const useJamTrackShopping = () => {
if (purchadeResp.ok) {
const userResp = await updateUser(currentUser.id);
if (userResp.ok) {
history.push('/checkout/success?free=yes&jamtrackId=' + jamTrack.id);
const checkoutPath = '/checkout/success?free=yes&jamtrackId=' + jamTrack.id;
if (forceReload) {
window.location.href = checkoutPath;
}
else {
history.push(checkoutPath);
}
}
}
}
} else { //if this is a paid jamtrack
toast.success(t('search.list.add_success_alert'));
history.push('/shopping-cart');
const shoppingCartPath = '/shopping-cart';
if (forceReload) {
window.location.href = shoppingCartPath;
}
else {
history.push(shoppingCartPath);
}
}
} catch (error) {
console.error(error);

View File

@ -19,8 +19,10 @@ const Layout = () => {
//see if there is affiliate in query string and save it as cookie
const urlParams = new URLSearchParams(window.location.search);
const affiliate = urlParams.get('affiliate');
const maxAge = 24 * 3600; // 1 day
if (affiliate) {
console.log(`affiliate found ${affiliate} ${process.env.REACT_APP_COOKIE_DOMAIN}`);
document.cookie = `affiliate_visitor=${affiliate}; path=/; max-age=${maxAge}; domain=${process.env.REACT_APP_COOKIE_DOMAIN}`;
}
}, []);

View File

@ -1,5 +1,6 @@
import React, { useContext } from 'react'
import React, { useContext, useEffect } from 'react'
import PublicRoutes from './JKPublicRoutes';
import UserAuth from '../context/UserAuth';
import { BrowserQueryProvider } from '../context/BrowserQuery';
import Logo from '../components/navbar/Logo';
import AppContext from "../context/Context";
@ -8,7 +9,7 @@ import { Navbar } from 'reactstrap';
import Footer from '../components/footer/JKFooter';
import {useResponsive} from "@farfetch/react-context-responsive";
const JKPublicLayout = () => {
const JKPublicLayout = ({ location }) => {
const {
isFluid,
@ -31,9 +32,12 @@ const JKPublicLayout = () => {
<Logo at="navbar-top" id="topLogo" width={240} />
</Navbar>
<div className={`pt-3 row ${paddingClass}`}>
<UserAuth path={location.pathname}>
<BrowserQueryProvider>
<PublicRoutes />
</BrowserQueryProvider>
</UserAuth>
</div>
{/* <div className='px-6 row'> */}
<Footer />

View File

@ -154,6 +154,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
# subtract the total quantity from the freebie quantity, to see how much we should attribute to them
real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i
{fee_in_cents: (rate * product_info[:real_price]).round}
# if shopping_cart.is_lesson?
# applicable_rate = lesson_rate
# else
@ -161,7 +162,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
# end
#{fee_in_cents: (product_info[:price] * 100 * real_quantity * applicable_rate.to_f).round}
{ fee_in_cents: (real_quantity * jamtrack_share_in_cents.to_f).round}
# { fee_in_cents: (real_quantity * jamtrack_share_in_cents.to_f).round}
else
false
end
@ -227,7 +228,8 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
tally_monthly_payments(quarter_info[:year], quarter_info[:quarter])
tally_quarterly_payments(quarter_info[:year], quarter_info[:quarter])
tally_traffic_totals(GenericState.affiliate_tallied_at, day)
# we aren't tracking visits anymore, so we don't need to tally_traffic_totals
#tally_traffic_totals(GenericState.affiliate_tallied_at, day)
tally_partner_totals
@ -437,6 +439,34 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
close_months(year, quarter)
end
# This was added because the tally_traffic_totals runs once a day, which is not often enough to get a fresh count of signups
# so as users sign up, we increment the signups count for the day
# jam=# \d affiliate_traffic_totals
# Table "public.affiliate_traffic_totals"
# Column | Type | Modifiers
#----------------------+-----------------------------+------------------------
# day | date | not null
# signups | integer | not null default 0
# visits | integer | not null default 0
# affiliate_partner_id | integer | not null
# created_at | timestamp without time zone | not null default now()
def self.increment_signups(user)
sql = "SELECT count(day) as count FROM affiliate_traffic_totals WHERE day = '#{user.created_at.to_date}' AND affiliate_partner_id = #{user.affiliate_referral_id}"
count = ActiveRecord::Base.connection.execute(sql)
if count.count > 0 && count[0]['count'].to_i && count[0]['count'].to_i > 0
sql = %{
UPDATE affiliate_traffic_totals SET signups = signups + 1 WHERE day = '#{user.created_at.to_date}' AND affiliate_partner_id = #{user.affiliate_referral_id}
}
else
sql = %{
INSERT INTO affiliate_traffic_totals (day, signups, visits, affiliate_partner_id) VALUES ('#{user.created_at.to_date}', 1, 0, #{user.affiliate_referral_id})
}
end
ActiveRecord::Base.connection.execute(sql)
end
def self.tally_partner_totals
sql = %{
UPDATE affiliate_partners SET
@ -460,6 +490,8 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
return
end
# Because we now increment_signups, as users sign up, it's possible this row already exists. however, if there were no signups and only visits, there may still not be a row here.
# So we need to insert the rows, and if they already exist, the INSERT will be a no-op, as long as we also update this statement to not fail if the row already exists.
sql = %{
INSERT INTO affiliate_traffic_totals(SELECT day, 0, 0, ap.id FROM affiliate_partners AS ap CROSS JOIN (select (generate_series('#{start_date}', '#{end_date - 1}', '1 day'::interval))::date as day) AS lurp)
}

View File

@ -1865,6 +1865,9 @@ module JamRuby
if user.affiliate_referral = AffiliatePartner.find_by_id(affiliate_referral_id)
user.save
if !user.errors.any?
AffiliatePartner.increment_signups(user)
end
end if affiliate_referral_id.present?