Compare commits

..

1 Commits

Author SHA1 Message Date
Nuwan 7721a9fa39 jamClient proxy
jamClient proxy acts as a wrapper which intercept methods
been passed to JK.jamClient and logs them
2022-06-04 19:14:44 +05:30
817 changed files with 40765 additions and 126753 deletions

View File

@ -1,25 +0,0 @@
name: Build Admin
on:
push:
branches:
- develop
jobs:
build:
runs-on: dagger
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Install Dagger
run: |
curl -L https://dl.dagger.io/dagger/install.sh | sh
sudo mv bin/dagger /usr/local/bin/
- name: Login to Gitea Registry
run: echo "${{ gitea.token }}" | docker login git.staging.jamkazam.com -u ${{ gitea.actor }} --password-stdin
- name: Build and Publish with Dagger
working-directory: ./admin
run: |
dagger call build-local --source=. --repo-root=../ publish --address=git.staging.jamkazam.com/seth/jam-cloud-admin:latest

View File

@ -1,41 +0,0 @@
name: Environment Orchestrator
on: [push]
jobs:
orchestrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Component Deployment Gatekeeper
run: |
# JAM_CLUSTER_ENV should be set to 'staging' or 'production' in the Gitea Runner
ENV="${JAM_CLUSTER_ENV:-staging}"
echo "🌐 Cluster Environment: $ENV"
# 1. Extract modes for this environment
ADMIN_MODE=$(jq -r ".environments.$ENV.admin" .jk-deploy.json)
WEB_MODE=$(jq -r ".environments.$ENV.web" .jk-deploy.json)
WS_MODE=$(jq -r ".environments.$ENV.[\"websocket-gateway\"]" .jk-deploy.json)
# 2. Conditional Execution
if [ "$ADMIN_MODE" == "short-circuit" ]; then
echo "⚡ ADMIN: Short-circuit detected. Deploying immediately..."
cd admin && dagger call ship --source=.
else
echo "⏸️ ADMIN: Mode is $ADMIN_MODE. Skipping short-circuit deploy."
fi
if [ "$WEB_MODE" == "short-circuit" ]; then
echo "⚡ WEB: Short-circuit detected. Deploying immediately..."
cd web && dagger call ship --source=.
else
echo "⏸️ WEB: Mode is $WEB_MODE. Skipping short-circuit deploy."
fi
if [ "$WS_MODE" == "short-circuit" ]; then
echo "⚡ WS-GATEWAY: Short-circuit detected. Deploying immediately..."
cd websocket-gateway && just ship
else
echo "⏸️ WS-GATEWAY: Mode is $WS_MODE. Skipping short-circuit deploy."
fi

View File

@ -1,8 +0,0 @@
name: Test Runner
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "Runner is working!"

1
.gitignore vendored
View File

@ -11,4 +11,3 @@ working.png
ruby/.rails5-gems ruby/.rails5-gems
web/.rails5-gems web/.rails5-gems
websocket-gateway/.rails5-gems websocket-gateway/.rails5-gems
.pg_data/

View File

@ -1 +0,0 @@
2.4.1

View File

@ -2,7 +2,7 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
menu :label => 'Quarterly Reports', :parent => 'Affiliates' menu :label => 'Quarterly Reports', :parent => 'Affiliates'
config.sort_order = 'year desc, quarter desc, due_amount_in_cents desc' config.sort_order = 'due_amount_in_cents DESC'
config.batch_actions = false config.batch_actions = false
config.clear_action_items! config.clear_action_items!
config.filters = true config.filters = true
@ -14,32 +14,18 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
filter :quarter filter :quarter
filter :closed filter :closed
filter :paid filter :paid
filter :jamtracks_sold
filter :subscriptions_count
filter :due_amount_in_cents
form :partial => 'form' form :partial => 'form'
scope("Sorted By Due Amount", default: true) { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.order('year desc, quarter desc, jamtracks_sold desc') }
scope("Sorted By Subs", default: false) { |scope| scope.order('year desc, quarter desc, subscriptions_count desc') }
scope("Sorted By Newest First") { |scope| scope.order('year desc, quarter desc, id desc') }
scope("Any") { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
index do index do
# default_actions # use this for all view/edit/delete links # default_actions # use this for all view/edit/delete links
column 'Year' do |oo| oo.year end column 'Year' do |oo| oo.year end
column 'Quarter' do |oo| oo.quarter end column 'Quarter' do |oo| oo.quarter end
column 'Partner Id' do |oo| oo.affiliate_partner.id end
column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
column "Tot ($)" do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end column "Due (\u00A2)" do |oo| oo.due_amount_in_cents end
column "Sub ($)" do |oo| sprintf("$%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f) end column 'JamTracks Sold' do |oo| oo.jamtracks_sold end
column "Jam ($)" do |oo| sprintf("$%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f) end
column 'JamTracks' do |oo| oo.jamtracks_sold end
column 'Subscriptions' do |oo| oo.subscriptions_count end
column 'Paid' do |oo| oo.paid end column 'Paid' do |oo| oo.paid end
column 'Closed' do |oo| oo.paid end column 'Closed' do |oo| oo.paid end

View File

@ -23,9 +23,7 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
# default_actions # use this for all view/edit/delete links # default_actions # use this for all view/edit/delete links
column 'Day' do |oo| oo.day end column 'Day' do |oo| oo.day end
column 'Partner ID' do |oo| oo.affiliate_partner.id end column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
column 'Partner Name' do |oo| oo.affiliate_partner.display_name end
column 'Partner User' do |oo| link_to(oo.affiliate_partner.partner_user.name, admin_user_path(oo.affiliate_partner.partner_user.id), { :title => oo.affiliate_partner.partner_user.name }) end
column 'Signups' do |oo| oo.signups end column 'Signups' do |oo| oo.signups end
column 'Visits' do |oo| oo.visits end column 'Visits' do |oo| oo.visits end
@ -33,16 +31,6 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
controller do controller do
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_partner])
.order('day DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_partner_id = ?', ref_id]
else
qq = ['affiliate_partner_id IS NOT NULL']
end
@users ||= rel.where(qq)
end
end end
end end

View File

@ -8,17 +8,12 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
config.filters = true config.filters = true
filter :affiliate_referral filter :affiliate_referral
filter :email
## scope("Has Signups", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') }
index do index do
column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end
column 'User Email' do |oo| oo.email end column 'Email' do |oo| oo.email end
column 'Created' do |oo| oo.created_at end column 'Created' do |oo| oo.created_at end
column 'Partner ID' do |oo| oo.affiliate_referral.id end column 'Partner' do |oo| oo.affiliate_referral.display_name end
column 'Partner Name' do |oo| oo.affiliate_referral.display_name end
column 'Partner User' do |oo| link_to(oo.affiliate_referral.partner_user.name, admin_user_path(oo.affiliate_referral.partner_user.id), { :title => oo.affiliate_referral.partner_user.name }) end
end end
controller do controller do

View File

@ -5,35 +5,19 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
config.sort_order = 'referral_user_count DESC' config.sort_order = 'referral_user_count DESC'
config.batch_actions = false config.batch_actions = false
# config.clear_action_items! # config.clear_action_items!
config.filters = true config.filters = false
config.per_page = 100 config.per_page = 50
config.paginate = true config.paginate = true
#form :partial => 'form' #form :partial => 'form'
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
#filter :partner_user
filter :partner_name
filter :id
filter :current_quarter_in_cents
filter :cumulative_earnings_in_cents
filter :jamtracks_sold
filter :subscriptions_count
filter :referral_user_count
scope("Sorted By Current Quarter", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('current_quarter_in_cents desc') }
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('jamtracks_sold desc') }
scope("Sorted By Subs", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('subscriptions_count desc') }
scope("Sorted By Signups", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Sorted By Newest First") { |scope| scope.where('partner_user_id IS NOT NULL').order('id desc') }
scope("Any") { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Unpaid") { |partner| partner.unpaid } scope("Unpaid") { |partner| partner.unpaid }
controller do controller do
helper 'active_admin/subscription' helper 'active_admin/subscription'
end end
form do |f| form do |f|
f.inputs 'Fields' do f.inputs 'Fields' do
f.input(:partner_name, :input_html => { :maxlength => 128 }) f.input(:partner_name, :input_html => { :maxlength => 128 })
@ -61,47 +45,14 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
column 'Code' do |oo| column 'Code' do |oo|
oo.id oo.id
end end
column 'Signups' do |oo| column 'Referral Count' do |oo|
oo.referral_user_count oo.referral_user_count
end end
column 'JamTracks' do |oo| column 'Earnings' do |oo|
oo.jamtracks_sold sprintf("$%.2f", oo.cumulative_earnings_in_dollars)
end
column 'Subs' do |oo|
oo.subscriptions_count
end
column 'Cum Earnings' do |oo|
div do
sprintf("Tot $%.2f", oo.cumulative_earnings_in_dollars)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_cumulative_earnings_in_dollars)
end
div do
sprintf("Sub $%.2f", oo.subscriptions_cumulative_earnings_in_dollars)
end
end
column 'Current Quarter' do |oo|
div do
sprintf("Tot $%.2f", oo.current_quarter_in_dollars)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_current_quarter_in_dollars)
end
div do
sprintf("Sub $%.2f", oo.subscriptions_current_quarter_in_dollars)
end
end end
column 'Amount Owed' do |oo| column 'Amount Owed' do |oo|
div do sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
sprintf("Tot $%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
end
div do
sprintf("Jam $%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f)
end
div do
sprintf("Sub $%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f)
end
end end
column 'Pay Actions' do |oo| column 'Pay Actions' do |oo|
link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid
@ -120,15 +71,6 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
row :address row :address
row :tax_identifier row :tax_identifier
row :paypal_id row :paypal_id
row :venmo_user_id
row :jamtracks_sold
row :subscriptions_count
row :cumulative_earnings_in_dollars
row :jamtrack_cumulative_earnings_in_dollars
row :subscriptions_cumulative_earnings_in_dollars
row :current_quarter_in_dollars
row :jamtrack_current_quarter_in_dollars
row :subscriptions_current_quarter_in_dollars
end end

View File

@ -1,21 +0,0 @@
ActiveAdmin.register JamRuby::AppFeature, as: 'App Features' do
menu parent: 'Misc', label: 'App Features'
config.sort_order = 'created_at ASC'
config.batch_actions = false
config.filters = false
config.per_page = 50
config.paginate = true
form do |f|
f.inputs 'Fields' do
f.input(:feature_type, as: :select, collection: JamRuby::AppFeature::FEATURE_TYPES)
f.input(:handle, :input_html => { :maxlength => 1025 })
f.input(:is_enabled, as: :boolean)
f.input(:env, as: :select, collection: %w(production staging development))
end
f.actions
end
end

View File

@ -640,12 +640,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name, extra_data: [:last_jam_addr] autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name, extra_data: [:last_jam_addr]
def get_autocomplete_items(parameters) def get_autocomplete_items(parameters)
term = parameters[:term] User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
if term.include?('@')
User.select("email, first_name, last_name, id, last_jam_addr").where(["email = ?", term]).limit(5)
else
User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{term}%", "%#{term}%", "%#{term}%"]).limit(40)
end
end end

View File

@ -17,14 +17,6 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
filter :jam_track filter :jam_track
controller do
def create
jt_params = params[:jam_ruby_jam_track_right]
jt_params[:jam_track] =JamRuby::JamTrack.where("id=?", jt_params[:jam_track_id_val]).first # jt_params[:jam_track_id_val]
jt_params[:user] = JamRuby::User.where("id=?", jt_params[:user_id_val]).first # jt_params[:user_id_val]
create!
end
end
index do index do
actions actions
@ -55,14 +47,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
form do |f| form do |f|
f.inputs 'New Jam Track Right' do f.inputs 'New Jam Track Right' do
#f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
f.input :jam_track_id_val, :required=>true, :as => :hidden f.input :user, :required=>true, collection: User.all, include_blank: false
f.input :can_download, :required => true, as: :boolean
f.input :jam_track, :required=>true, :as => :autocomplete, :url => autocomplete_jam_track_name_admin_jam_tracks_path, hint: 'Select a jamtrack to give to this user'
#f.input :user, :required=>true, collection: User.all, include_blank: false
f.input :user_id_val, :required=>true, :as => :hidden
f.input :user, :required=>true, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, hint: 'Give a free jamtrack to this user'
f.input :can_download, :required => true, as: :boolean, :input_html => { :checked => 'checked' }
end end
f.actions f.actions
end end

View File

@ -1,7 +1,5 @@
ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
collection_action :autocomplete_jam_track_name, :method => :get
menu :label => 'JamTracks', :parent => 'JamTracks' menu :label => 'JamTracks', :parent => 'JamTracks'
config.sort_order = 'name_asc' config.sort_order = 'name_asc'
@ -21,19 +19,6 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
form :partial => 'form' form :partial => 'form'
controller do
# this actually searches on first name, last name, and email, because of get_autocomplete_items defined below
autocomplete :jam_track, :name, :full => true, :display_value => :autocomplete_display_name
def get_autocomplete_items(parameters)
JamTrack.select("name, original_artist, id").where(["name ILIKE ? OR original_artist ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
end
end
index do index do
# actions # use this for all view/edit/delete links # actions # use this for all view/edit/delete links

View File

@ -1,185 +0,0 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', ' ') + '% - ' + val.to_s
end
end
=begin
select
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
select first_name, last_name, email
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
AND first_music_session_at is NULL;
=end
ActiveAdmin.register_page "Jammers Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "Jammers Subscription Cohorts" do
filter_type = params[:filter_type] || 'All'
filter_campaign = params[:filter_campaign]
filter_campaign_id = params[:filter_campaign_id]
filter_ad_set = params[:filter_ad_set]
filter_ad_name = params[:filter_ad_name]
campaigns = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_utm_campaign).compact.sort
campaign_ids = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_id).compact.sort
ad_sets = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_term).compact.sort
ad_names = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_content).compact.sort
div style: "margin-bottom: 20px; padding: 10px; background-color: #f4f4f4; border-radius: 4px;" do
form action: admin_jammers_subscription_cohorts_path, method: :get do
span "Source: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_type', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: 'All', selected: filter_type == 'All'
option "Organic", value: 'Organic', selected: filter_type == 'Organic'
option "Advertising", value: 'Advertising', selected: filter_type == 'Advertising'
end
if filter_type == 'Advertising'
div style: "margin-top: 10px;" do
span "Campaign Name: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_campaign == 'NULL'
campaigns.each do |c|
option c, value: c, selected: filter_campaign == c
end
end
end
div style: "margin-top: 10px;" do
span "Campaign ID: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_campaign_id', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_campaign_id == 'NULL'
campaign_ids.each do |c|
option c, value: c, selected: filter_campaign_id == c
end
end
end
div style: "margin-top: 10px;" do
span "Ad Set: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_ad_set', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_ad_set == 'NULL'
ad_sets.each do |c|
option c, value: c, selected: filter_ad_set == c
end
end
div style: "margin-top: 10px;" do
span "Ad Name: ", style: "font-weight: bold; margin-right: 5px;"
select name: 'filter_ad_name', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
option "All", value: ''
option "Null", value: 'NULL', selected: filter_ad_name == 'NULL'
ad_names.each do |c|
option c, value: c, selected: filter_ad_name == c
end
end
end
end
end
noscript { input type: :submit, value: "Filter" }
end
end
h2 "Users Grouped By Month as Paying Subscribers"
query = User.select(%Q{date_trunc('month', users.created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed,
count(first_played_jamtrack_at) as played_jamtrack
})
.joins(%Q{LEFT JOIN LATERAL (
SELECT
j.created_at
FROM
jam_track_rights as j
WHERE
j.user_id = users.id
ORDER BY
j.created_at
LIMIT 1 -- Select only that single row
) j ON TRUE })
if filter_type == 'Organic'
query = query.where("users.origin_utm_source = 'organic'")
elsif filter_type == 'Advertising'
query = query.where("origin_utm_medium = 'cpc'")
if filter_campaign.present?
if filter_campaign == 'NULL'
query = query.where("users.origin_utm_campaign IS NULL")
elsif filter_campaign != 'All'
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
end
end
if filter_campaign_id.present?
if filter_campaign_id == 'NULL'
query = query.where("users.origin_id IS NULL")
elsif filter_campaign_id != 'All'
query = query.where("users.origin_id = ?", filter_campaign_id)
end
end
if filter_ad_set.present?
if filter_ad_set == 'NULL'
query = query.where("users.origin_term IS NULL")
elsif filter_ad_set != 'All'
query = query.where("users.origin_term = ?", filter_ad_set)
end
end
if filter_ad_name.present?
if filter_ad_name == 'NULL'
query = query.where("users.origin_content IS NULL")
elsif filter_ad_name != 'All'
query = query.where("users.origin_content = ?", filter_ad_name)
end
end
end
table_for query.group("date_trunc('month', users.created_at)")
.where("j.created_at IS NULL OR (j.created_at - users.created_at) >= INTERVAL '2 hours'")
.order("date_trunc('month', users.created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
end
end
end

View File

@ -1,73 +0,0 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', '&nbsp;') + '%&nbsp;-&nbsp;' + val.to_s
end
end
=begin
select
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
select first_name, last_name, email
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
AND first_music_session_at is NULL;
=end
ActiveAdmin.register_page "JamTrack Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "JamTrack Subscription Cohorts" do
h2 "Users Grouped By Month as Paying Subscribers"
table_for User.select(%Q{date_trunc('month', users.created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed,
count(first_played_jamtrack_at) as played_jamtrack
})
.joins(%Q{INNER JOIN LATERAL (
SELECT
j.created_at
FROM
jam_track_rights as j
WHERE
j.user_id = users.id
ORDER BY
j.created_at
LIMIT 1 -- Select only that single row
) j ON (j.created_at - users.created_at) < INTERVAL '2 hours' })
.group("date_trunc('month', users.created_at)").order("date_trunc('month', users.created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
end
end
end

View File

@ -0,0 +1,41 @@
class Spacer
def self.spacer(val, row)
percentage = ((val * 100) / row.total.to_f).round(1).to_s
('%-5.5s' % percentage).gsub(' ', '&nbsp;') + '%&nbsp;-&nbsp;' + val.to_s
end
end
ActiveAdmin.register_page "Subscription Cohorts" do
menu :parent => 'Reports'
content :title => "Subscription Cohorts" do
h2 "Users Grouped By Month as Paying Subscribers"
table_for User.select(%Q{date_trunc('month', created_at) as month,
count(id) as total,
count(first_downloaded_client_at) as downloaded,
count(first_ran_client_at) as ran_client,
count(first_certified_gear_at) as ftue,
count(first_music_session_at) as any_session,
count(first_real_music_session_at) as real_session,
count(first_good_music_session_at) as good_session,
count(first_invited_at) as invited,
count(first_friended_at) as friended,
count(first_subscribed_at) as subscribed
}).group("date_trunc('month', created_at)").order("date_trunc('month', created_at) DESC") do |row|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
column "Total", :total
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
end
end
end

View File

@ -6,18 +6,15 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
config.batch_actions = false config.batch_actions = false
config.clear_action_items! config.clear_action_items!
config.filters = false config.filters = false
config.per_page = 250
scope("Paid", default: true) { |scope| scope.unscoped.where(:origin_utm_medium => 'cpc').order('created_at desc') } scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
scope("Inorganic Source") { |scope| scope.unscoped.where("origin_utm_source != 'organic' OR origin_utm_source IS NULL").order('created_at desc') }
scope("Include Organic") { |scope| scope.unscoped.order('created_at desc') }
index do index do
column "Email" do |user| column "Email" do |user|
user.email user.email
end end
column "Signup (CST)" do |user| column "Bought TestDrive" do |user|
user.created_at.in_time_zone("Central Time (US & Canada)") !user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
end end
column "UTM Source" do |user| column "UTM Source" do |user|
user.origin_utm_source user.origin_utm_source
@ -28,23 +25,8 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
column "UTM Campaign" do |user| column "UTM Campaign" do |user|
user.origin_utm_campaign user.origin_utm_campaign
end end
column "UTM ID" do |user|
user.origin_id
end
column "UTM Term" do |user|
user.origin_term
end
column "UTM Content" do |user|
user.origin_content
end
column "Referrer" do |user| column "Referrer" do |user|
user.origin_referrer user.origin_referrer
end end
column "FB Click ID" do |user|
user.facebook_click_id
end
column "FB Browser ID" do |user|
user.facebook_browser_id
end
end end
end end

View File

@ -7,38 +7,6 @@ function intToIP(int) {
return part4 + "." + part3 + "." + part2 + "." + part1; return part4 + "." + part3 + "." + part2 + "." + part1;
} }
function handleJamTrackRightsForm() {
var $jamTrackRights = $('form#new_jam_ruby_jam_track_right');
var $jamTrack = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id');
var $jamTrackVal = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id_val')
var $user = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id');
var $userVal = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id_val');
$jamTrack.on('change', function(){
console.log("change jam track");
});
/**
$user.on('change', function(){
console.log("change user");
});
$jamTrack.on('focus', function(){
$userVal.val('')
});*/
$jamTrack.bind('railsAutocomplete.select', function(event, data){
$jamTrackVal.val('');
$jamTrackVal.val(data.item.id);
console.log("jam track selected with id " + data.item.id);
});
$user.bind('railsAutocomplete.select', function(event, data){
$userVal.val('');
$userVal.val(data.item.id);
console.log("user selected with id " + data.item.id);
});
}
function handleUserLatencyForm(){ function handleUserLatencyForm(){
var $userLatenciesForm = $('form#user_latencies_form'); var $userLatenciesForm = $('form#user_latencies_form');
var $latenciesMyUser = $userLatenciesForm.find('#latencies_my_user'); var $latenciesMyUser = $userLatenciesForm.find('#latencies_my_user');
@ -106,5 +74,4 @@ function handleLatencyRecommendationForm(){
$(document).ready(function() { $(document).ready(function() {
handleUserLatencyForm(); handleUserLatencyForm();
handleLatencyRecommendationForm(); handleLatencyRecommendationForm();
handleJamTrackRightsForm();
}); });

View File

@ -2,80 +2,29 @@ class ArsesController < ApplicationController
respond_to :json respond_to :json
def index
if params[:code] != Rails.application.config.data_dump_code
render :json => {error: "Unauthorized"}, :status => 401
return
end
@arses = JamRuby::Ars.all
render :json => @arses
end
def update
if params[:code] != Rails.application.config.data_dump_code
render :json => {error: "Unauthorized"}, :status => 401
return
end
begin
# Primary ID lookup
@ars = JamRuby::Ars.find_by_id(params[:id])
# Explicit secondary lookups if primary ID fails
@ars ||= JamRuby::Ars.find_by_id_int(params[:id_int]) if params[:id_int]
@ars ||= JamRuby::Ars.find_by_name(params[:name]) if params[:name]
if @ars.nil?
render :json => {error: "Not Found"}, :status => 404
return
end
allowed = [:password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude]
update_hash = {}
allowed.each do |attr|
update_hash[attr] = params[attr] if params.has_key?(attr)
end
if @ars.update_attributes(update_hash, as: :admin)
render :json => @ars, :status => :ok
else
render :json => @ars.errors, :status => :unprocessable_entity
end
rescue => e
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
end
end
# create or update a client_artifact row # create or update a client_artifact row
def get_or_create def get_or_create
begin
name = params[:name] name = params[:name]
provider = params[:provider] provider = params[:provider]
active = params[:active] active = params[:active]
beta = params.has_key?(:beta) ? params[:beta] : true
ip = params[:ip] ip = params[:ip]
username = params[:username] username = params[:username]
password = params[:password] password = params[:password]
topology = params[:topology] topology = params[:topology]
ars_id = params[:ars_id] ars_id = params[:ars_id]
puts "TOPOLOGY #{topology}"
# Explicit field-based lookups if ars_id
ars = nil ars = Ars.find_by_id_int(ars_id)
ars = JamRuby::Ars.find_by_id_int(ars_id) if ars_id end
ars ||= JamRuby::Ars.find_by_name(name) if name
if ars.nil? if ars.nil?
ars = JamRuby::Ars.new ars = Ars.new
ars.name = name ars.name = name
ars.id_int = ars_id if !ars_id.nil?
end end
ars.id_int = ars_id if !ars_id.nil?
ars.provider = provider ars.provider = provider
ars.active = active ars.active = active
ars.beta = params[:beta]
ars.beta = beta
ars.ip = ip ars.ip = ip
ars.password = password ars.password = password
ars.username = username ars.username = username
@ -87,12 +36,21 @@ class ArsesController < ApplicationController
ars.longitude = topology['longitude'] ars.longitude = topology['longitude']
ars.subdivision = topology['subdivision'] ars.subdivision = topology['subdivision']
end end
ars.save! ars.save
@ars = ars @ars = ars
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, beta: @ars.beta, ip: @ars.ip}, :status => :ok unless @ars.errors.any?
rescue => e if ars_id.nil?
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500 ars.reload
end ars_id = ars.id_int
end
@ars = Ars.find_by_id_int(ars_id)
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, ip: @ars.ip}, :status => :ok
else
response.status = :unprocessable_entity
respond_with @ars
end
end end
end end

View File

@ -112,19 +112,6 @@ class Cohort < ActiveRecord::Base
def self.cohort_users(cohort) def self.cohort_users(cohort)
User.where(created_at: cohort.group_start..cohort.group_end) User.where(created_at: cohort.group_start..cohort.group_end)
end end
=begin
SELECT played.user_id FROM
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
WHERE
msuh1.created_at >= '2024-11-01' AND
msuh1.created_at <= '202' AND
EXTRACT(EPOCH FROM (msuh1.session_removed_at - msuh1.created_at)) >= 900 AND
(SELECT COUNT(*) FROM music_sessions_user_history msuh2
WHERE msuh1.music_session_id = msuh2.music_session_id
) > 1
GROUP BY user_id
) played
=end
def _played_online_subquery(constraint) def _played_online_subquery(constraint)
where = if constraint.is_a?(Range) where = if constraint.is_a?(Range)

View File

@ -1 +0,0 @@
BUNDLE_GEMFILE=Gemfile.alt RAILS_ENV=development LOCAL_DEV=1 MODERN_OS=1 JAM_RUBY_VERSION=2.4.1 bundle _1.17.3_ exec rails server -b 0.0.0.0 -p 3333

View File

@ -131,7 +131,7 @@ module JamAdmin
config.email_smtp_starttls_auto = true config.email_smtp_starttls_auto = true
config.verify_email_enabled = false config.verify_email_enabled = false
config.musician_count = '300,000+' config.musician_count = '200,000+'
config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398' config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398'
config.facebook_app_secret = ENV['FACEBOOK_APP_SECRET'] || '546a5b253972f3e2e8b36d9a3dd5a06e' config.facebook_app_secret = ENV['FACEBOOK_APP_SECRET'] || '546a5b253972f3e2e8b36d9a3dd5a06e'

View File

@ -1,18 +0,0 @@
class JamRuby::JamTrackRight
attr_accessible :jam_track, :user, :jam_track_id_val, :user_id_val, as: :admin
def jam_track_id_val
end
def jam_track_id_val=(val)
end
def user_id_val
end
def user_id_val=(val)
end
end

View File

@ -3,8 +3,4 @@ class JamRuby::JamTrack
# add a custom validation # add a custom validation
def autocomplete_display_name
"#{original_artist} - #{name}"
end
end end

View File

@ -20,10 +20,6 @@ JamAdmin::Application.routes.draw do
post :user_latencies, on: :collection post :user_latencies, on: :collection
post :user_latency_recommendation, on: :collection post :user_latency_recommendation, on: :collection
end end
resources :jam_tracks do
get :autocomplete_jam_track_name, :on => :collection
end
end end
namespace :admin do namespace :admin do
@ -44,8 +40,6 @@ JamAdmin::Application.routes.draw do
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv' match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
match '/api/arses/register' => 'arses#get_or_create', :via => :post match '/api/arses/register' => 'arses#get_or_create', :via => :post
match '/api/arses' => 'arses#index', :via => :get
match '/api/arses/:id' => 'arses#update', :via => :post
mount Resque::Server.new, :at => "/resque" mount Resque::Server.new, :at => "/resque"

View File

@ -1 +0,0 @@
# trigger build

View File

@ -1,4 +1,4 @@
image: node:14.21.3 image: node:14.17.1
pipelines: pipelines:
branches: branches:
@ -6,93 +6,33 @@ pipelines:
- step: - step:
name: Build Staging name: Build Staging
script: script:
- pushd jam-ui
- npm install
- popd
- pushd jam-ui/cicd
- npm install
- NODE_ENV=production PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT ENVIRONMENT=staging ./generate.sh
- popd
- cd jam-ui - cd jam-ui
- NODE_ENV=production CI=false PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT npm run build - npm install
- CI=false REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_LEGACY_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
artifacts: artifacts:
- jam-ui/build/** - jam-ui/build/**
- step: - step:
name: Deploy to staging - SPA name: Deploy to staging
deployment: staging
script: script:
- pipe: atlassian/aws-s3-deploy:1.6.2 - pipe: atlassian/aws-s3-deploy:1.1.0
variables: variables:
S3_BUCKET: "jamkazam-ui/stg" S3_BUCKET: "jamkazam-ui/stg"
LOCAL_PATH: "jam-ui/build" LOCAL_PATH: "jam-ui/build"
EXTRA_ARGS: "--exclude=*backing-tracks/*"
- step:
name: Deploy to staging - backing-tracks
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/stg/backing-tracks"
LOCAL_PATH: "jam-ui/build/backing-tracks"
EXTRA_ARGS: "--exclude=*.js --content-type text/html"
- step:
name: Deploy to staging - backing-tracks js
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/stg/js"
LOCAL_PATH: "jam-ui/build/js"
EXTRA_ARGS: "--content-type text/javascript"
# - step:
# name: Deploy to staging - invalidate cloudfront distribution
# deployment: staging
# script:
# - pipe: atlassian/aws-cloudfront-invalidate:0.10.1
# variables:
# DISTRIBUTION_ID: "E2AQIC9RSON94Q" # ESQDIABYLT0RV
custom:
build-and-deploy-to-production:
- step: - step:
name: Build Production name: Build Production
script: script:
- pushd jam-ui
- npm install
- popd
- pushd jam-ui/cicd
- npm install
- NODE_ENV=production ENVIRONMENT=production PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB ./generate.sh
- popd
- cd jam-ui - cd jam-ui
- NODE_ENV=production CI=false PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB npm run build - npm install
- CI=false REACT_APP_ORIGIN=jamkazam.com REACT_APP_LEGACY_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
artifacts: artifacts:
- jam-ui/build/** - jam-ui/build/**
- step: - step:
name: Deploy to production - SPA name: Deploy to production
deployment: production
script: script:
- pipe: atlassian/aws-s3-deploy:1.6.2 - pipe: atlassian/aws-s3-deploy:1.1.0
variables: variables:
S3_BUCKET: "jamkazam-ui/prd" S3_BUCKET: "jamkazam-ui/prd"
LOCAL_PATH: "jam-ui/build" LOCAL_PATH: "jam-ui/build"
EXTRA_ARGS: "--exclude=*backing-tracks/*"
- step:
name: Deploy to production - backing-tracks
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/backing-tracks"
LOCAL_PATH: "jam-ui/build/backing-tracks"
EXTRA_ARGS: "--exclude=*.js --content-type text/html"
- step:
name: Deploy to production - backing-tracks js
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/js"
LOCAL_PATH: "jam-ui/build/js"
EXTRA_ARGS: "--content-type text/javascript"
#- step:
# name: Deploy to production - invalidate cloudfront distribution
# deployment: production
# script:
# - pipe: atlassian/aws-cloudfront-invalidate:0.10.1
# variables:
# DISTRIBUTION_ID: "ESQDIABYLT0RV"

1
build
View File

@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
# RUN_SLOW_TESTS, RUN_AWS_TESTS, SKIP_KARMA=1 SHOW_JS_ERRORS=1 PACKAGE=1 # RUN_SLOW_TESTS, RUN_AWS_TESTS, SKIP_KARMA=1 SHOW_JS_ERRORS=1 PACKAGE=1
# WORKSPACE=/var/lib/jenkins/jobs/jam-web/workspace # WORKSPACE=/var/lib/jenkins/jobs/jam-web/workspace

View File

@ -1,16 +1,7 @@
HOST=beta.jamkazam.local HOST=beta.jamkazam.local
PORT=4000 PORT=4000
REACT_APP_ORIGIN=jamkazam.local REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000 REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=dev REACT_APP_BITBUCKET_BUILD_NUMBER=dev
REACT_APP_BITBUCKET_COMMIT=dev REACT_APP_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
PUBLIC_URL=
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -1,13 +1,7 @@
HOST=beta.jamkazam.local HOST=beta.jamkazam.local
PORT=4000 PORT=4000
REACT_APP_ORIGIN=jamkazam.local REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000 REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=dev REACT_APP_BITBUCKET_BUILD_NUMBER=dev
REACT_APP_BITBUCKET_COMMIT=dev REACT_APP_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
REACT_APP_RECURLY_PUBLIC_API_KEY=
REACT_APP_BRAINTREE_TOKEN=

View File

@ -1,13 +1,5 @@
HOST=beta.jamkazam.com HOST=beta.jamkazam.com
PORT=4000 PORT=4000
REACT_APP_ORIGIN=jamkazam.com REACT_APP_ORIGIN=jamkazam.com
REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_LEGACY_BASE_URL=https://www.jamkazam.com
REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com
REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_API_BASE_URL=https://www.jamkazam.com/api
REACT_APP_ENV=production
REACT_APP_RECAPTCHA_ENABLED=true
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh

View File

@ -1,13 +1,5 @@
HOST=beta.staging.jamkazam.com HOST=beta.staging.jamkazam.com
PORT=4000 PORT=4000
REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com
REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_LEGACY_BASE_URL=https://staging.jamkazam.com
REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com
REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api
REACT_APP_ENV=staging
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -3,6 +3,6 @@
"plugins": ["prettier"], "plugins": ["prettier"],
"rules": { "rules": {
"prettier/prettier": "error", "prettier/prettier": "error",
"react/no-unescaped-entities": 0 "react/no-unescaped-entities": false
} }
} }

4
jam-ui/.gitignore vendored
View File

@ -31,7 +31,3 @@ yarn-error.log*
/cypress/videos/ /cypress/videos/
/cypress/screenshots/ /cypress/screenshots/
/public/backing-tracks
/public/js

View File

@ -1 +0,0 @@
v14.21.3

View File

@ -1 +0,0 @@
2.7.18

View File

@ -1,14 +1,5 @@
# JamKazam new react frontend UI/UX # JamKazam new react frontend UI/UX
`jam-ui` is a react app created using `create-react-app` utility. to run the app on your development environment you need to `cd jam-ui` and run `npm run start`
The changes to the source files are auto-loaded but sometimes you might need to force close it by crtl+c and then start manually.
The application files goes under `src` and the react components are placed under `src/components` directory. We have a convention of naming the files using JK prefix in the filename. For example `JKMusicSessions`.
The routes are defined in `jam-ui/src/components/dashboard/JKDashboardMain.js`
## Running react app ## Running react app
In production this React app is supposed to run on beta.jamkazam.com subdomain which is same origin domain to the production Rails app (www.jamkazam.com). This way we utilize same session based user authentication of Rails web app for authenticating users. (It looks for remember_token session cookie in headers and if it is not availale redirect the user to Rails web app sign in page) In production this React app is supposed to run on beta.jamkazam.com subdomain which is same origin domain to the production Rails app (www.jamkazam.com). This way we utilize same session based user authentication of Rails web app for authenticating users. (It looks for remember_token session cookie in headers and if it is not availale redirect the user to Rails web app sign in page)
@ -17,7 +8,7 @@ The DOMAIN and PORT running this app is defined in env.production file. This fil
HOST=beta.jamkazam.local HOST=beta.jamkazam.local
PORT=4000 PORT=4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000 REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
## Subdomains setup (development) ## Subdomains setup (development)
@ -38,19 +29,3 @@ 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. 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.
## Working with JamTracks
if you have the latest from develop, you can go:
```
cd cicd
npm install
./export_personal_jamtracks.sh
./generate.js
open http://beta.jamkazam.local:4000/backing-tracks/ac-dc/back-in-black.html
```
You can also do none of the above, and go straight to:
http://beta.jamkazam.local:4000/public/backing-tracks/ac-dc/back-in-black
I tried to make it so the SPA has 'secret routes' to these pages, which is convenient for us dev & testing
but also tried to make it convenient to run the cicd approach of actually generating separate pages for each landing page (which is what those 5 steps cover)

View File

@ -1,6 +0,0 @@
build
output
node_modules
public
jam_track_tracks_for_jam_ui*

View File

@ -1 +0,0 @@
22

View File

@ -1,7 +0,0 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"ignore": [
"../src/components/e-commerce/*.js"
]
}

View File

@ -1,29 +0,0 @@
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/stable';
import React from "react";
import ReactDOM from "react-dom";
import Main from "../src/Main.js"
import TemplatePage from "../src/components/jamtracks/JKJamTracksLandingTemplatePage.js"
import ArtistTemplatePage from "../src/components/jamtracks/JKJamTracksArtistLandingTemplatePage.js"
import '../src/helpers/initFA';
import '../src/i18n/config';
const rootElement = document.getElementById("root");
// Ensure props are passed correctly (or fetch from the server)
const props = window.jamtrack_data;
console.log('init', props, rootElement);
// Hydrate the server-rendered React component
//ReactDOM.hydrate(React.createElement(TemplatePage, props), rootElement);
ReactDOM.render(
<Main>
{props.song ? <TemplatePage {...props} /> : <ArtistTemplatePage {...props} /> }
</Main>, rootElement
);

View File

@ -1,25 +0,0 @@
#!/bin/bash
# check if 1st argument spceified; if it is, then set that to SAVE_TO
if [ -n "$1" ]; then
SAVE_TO="$1"
else
SAVE_TO=/tmp
fi
echo "Saving to $SAVE_TO"
psql jam -c "COPY( select id, original_artist, name , original_artist_slug, name_slug, plan_code, slug, allow_free, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug || '/' || name_slug) as \"URL\", (select name from jam_track_licensors l where l.id = licensor_id) as \"Licensor\", vendor_id as \"Vendor ID\" FROM jam_tracks) TO '$SAVE_TO/jam_tracks_for_jam_ui.$USER.csv' with CSV HEADER;"
//https://jamkazam-public.s3.amazonaws.com
# 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_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 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

@ -1,39 +0,0 @@
#!/bin/bash
# Ensure the correct number of arguments
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <save_to_path> <server_env>"
exit 1
fi
SAVE_TO="$1"
server_env="$2"
# Validate server_env
if [ "$server_env" != "staging" ] && [ "$server_env" != "production" ]; then
echo "Error: server_env must be either 'staging' or 'production'"
exit 1
fi
# Determine SSH target
if [ "$server_env" == "staging" ]; then
SSH_TARGET="jam@int.jamkazam.com"
else
SSH_TARGET="jam@db.jamkazam.com"
fi
echo "Saving to $SAVE_TO on $server_env"
# Run psql commands remotely
ssh $SSH_TARGET "psql jam -c \"COPY( select id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, allow_free, ('https://www.jamkazam.com/backing-tracks/' || original_artist_slug || '/' || name_slug) as \"URL\", (select name from jam_track_licensors l where l.id = licensor_id) as \"Licensor\" FROM jam_tracks order by id::int) TO '$SAVE_TO/jam_tracks_for_jam_ui.$server_env.csv' with CSV HEADER;\""
ssh $SSH_TARGET "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 order by original_artist) TO '$SAVE_TO/jam_tracks_for_jam_ui_artists.$server_env.csv' with CSV HEADER;\""
ssh $SSH_TARGET "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 order by jam_track_id::int) TO '$SAVE_TO/jam_track_tracks_for_jam_ui.$server_env.csv' with CSV HEADER;\""
# Move files locally from the remote server
scp $SSH_TARGET:"$SAVE_TO/jam_tracks_for_jam_ui.$server_env.csv" jamtracks-for-env
scp $SSH_TARGET:"$SAVE_TO/jam_tracks_for_jam_ui_artists.$server_env.csv" jamtracks-for-env
scp $SSH_TARGET:"$SAVE_TO/jam_track_tracks_for_jam_ui.$server_env.csv" jamtracks-for-env
echo "Files moved successfully to local machine"

View File

@ -1,337 +0,0 @@
const fs = require("fs");
const path = require("path");
const csv = require("csv-parser");
const React = require("react");
const dotenv = require("dotenv");
const ReactDOMServer = require("react-dom/server");
const TemplatePageModule = require("./build/components/jamtracks/JKJamTracksLandingTemplatePage");
const ArtistTemplatePageModule = require("./build/components/jamtracks/JKJamTracksArtistLandingTemplatePage");
var csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${process.env.USER}.csv`
var artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${process.env.USER}.csv`
var sitemapPath = path.join(__dirname, "..", "public", "sitemap.xml");
const clear_sitemap = () => {
fs.writeFileSync(sitemapPath, "");
// Add the root element
fs.writeFileSync(sitemapPath, `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`, { flag: 'a' });
// Add the root url
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}</loc></url>\n`, { flag: 'a' });
// Add standard URLs specific to this site, such as:
// All prefix with /public
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/</loc></url>\n`, { flag: 'a' });
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/privacy</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/knowledge-base</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help-desk</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/forum</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/unsubscribe</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads-legacy</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/login</loc></url>\n`, { flag: 'a' });
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/signup</loc></url>\n`, { flag: 'a' } );
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/forget-password</loc></url>\n`, { flag: 'a' } );
// Add the closing root element
}
const add_song_to_sitemap = (artistSlug, songSlug) => {
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}/${songSlug}</loc></url>\n`, { flag: 'a' });
}
const add_artist_to_sitemap = (artistSlug) => {
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}</loc></url>\n`, { flag: 'a' });
}
const close_sitemap = () => {
fs.writeFileSync(sitemapPath, "</urlset>", { flag: 'a' } );
}
/**
* Loads a CSV file into an array of objects.
* @param {string} csvPath - The path to the CSV file.
* @returns {Promise<Array<Object>>} - A promise that resolves with the parsed CSV data.
*/
const load_csv = (csvPath) => {
return new Promise((resolve, reject) => {
const results = [];
fs.createReadStream(csvPath)
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => resolve(results))
.on('error', (error) => reject(error));
});
};
/**
* Finds all songs for a given artist based on the original_artist_slug
* and sorts them alphabetically by the `slug` field.
* @param {string} artistSlug - The original_artist_slug to match.
* @param {Array<Object>} songsCsv - The songs CSV data.
* @returns {Array<Object>} - A sorted array of matching song objects.
*/
const collect_songs_for_artist = (artistSlug, songsCsv) => {
return songsCsv
.filter((song) => song.original_artist_slug === artistSlug)
.sort((a, b) => a.slug.localeCompare(b.slug)); // Sort alphabetically by slug
};
const init = () => {
const node_env = process.env.NODE_ENV || 'development';
const environment = process.env.ENVIRONMENT || 'development';
console.log(`environment=${environment} node_env=${node_env}`);
console.log(dotenv.config({ path: `../.env.${environment}` }));
if (environment === "production" || environment === "staging") {
csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${environment}.csv`;
artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${environment}.csv`;
}
console.log("Song csv file", csvFilePath);
console.log("Artist csv file", artistCsvFilePath);
if (!process.env.PUBLIC_URL) {
console.log("setting public url", process.env.REACT_APP_BASE_URL);
process.env.PUBLIC_URL = process.env.REACT_APP_BASE_URL;
}
clear_sitemap();
//const __dirname = path.resolve(path.dirname(''));
console.log("init done successfully")
}
const generateSongPages = async (render) => {
const rows = [];
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log("generatPages starting")
const TemplatePage = TemplatePageModule.default;
fs.createReadStream(csvFilePath)
.pipe(csv())
.on("data", (row) => rows.push(row))
.on("end", async () => {
console.log(`Processing ${rows.length} rows...`);
for (const row of rows) {
// id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, URL, licensor, vendor_id
const { id, original_artist, name, original_artist_slug, name_slug, plan_code, slug, allow_free } = row;
const artist = original_artist;
const song = name;
const location = `/backing-tracks/${original_artist_slug}/${name_slug}`;
const fullPath = process.env.REACT_APP_BASE_URL + location;
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
add_song_to_sitemap(original_artist_slug, name_slug);
console.log(`Generating ${artist} - ${song}`);
const html = render
? ReactDOMServer.renderToStaticMarkup(
React.createElement(TemplatePage, { id, plan_code, slug, artist, song, location })
)
: "";
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="/favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${artist} - ${song} - Free Backing Track</title>
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
<meta name="description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="keywords" content="Backing Track, ${artist}, ${song}, Instrumental">
<meta name="author" content="JamKazam">
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
<meta property="og:title" content="${artist} - ${song} | Free Backing Track">
<meta property="og:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta property="og:image" content="${logoPath}">
<meta property="og:url" content="${fullPath}">
<meta property="og:type" content="music.song">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${artist} - ${song} | Free Backing Track">
<meta name="twitter:description" content="Get free ${song} by ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="twitter:image" content="${logoPath}">
<!-- Canonical URL -->
<link rel="canonical" href="${fullPath}" />
<!-- Structured Data (Schema.org) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "MusicRecording",
"name": "${song}",
"byArtist": {
"@type": "MusicGroup",
"name": "${artist}"
},
"url": "${fullPath}",
"image": "${logoPath}"
}
</script>
<script>
window.jamtrack_data = {
id: "${id}",
plan_code: "${plan_code}",
slug: "${slug}",
artist: "${artist}",
song: "${song}",
location: "${location}"
}
</script>
</head>
<body>
<div id="root">${html}</div>
<script src="/js/client-hydrate.bundle.js"></script>
</body>
</html>`;
const ARTIST_DIR = path.join(OUTPUT_DIR, original_artist_slug);
if (!fs.existsSync(ARTIST_DIR)) {
fs.mkdirSync(ARTIST_DIR, { recursive: false });
}
const finalOutputPath = process.env.NODE_ENV === "development" ? `${name_slug}.html` : `${name_slug}.html`;
const outputFilePath = path.join(ARTIST_DIR, finalOutputPath);
fs.writeFileSync(outputFilePath, fullHtml);
console.log(`Generated: ${outputFilePath}`);
}
console.log("All pages generated!");
});
};
const generateArtistPages = async (render) => {
const rows = [];
const OUTPUT_DIR = path.join(__dirname, "..", "public", "backing-tracks");
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
console.log("generatPages starting")
const TemplatePage = TemplatePageModule.default;
const songs_csv = await load_csv(csvFilePath);
fs.createReadStream(artistCsvFilePath)
.pipe(csv())
.on("data", (row) => rows.push(row))
.on("end", async () => {
console.log(`Processing ${rows.length} rows...`);
for (const row of rows) {
const { original_artist, original_artist_slug, url } = row;
const artist = original_artist;
const matchingSongs = collect_songs_for_artist(original_artist_slug, songs_csv);
console.log(`Found ${matchingSongs.length} songs for ${artist}`);
const location = `/backing-tracks/${original_artist_slug}`;
const fullPath = process.env.REACT_APP_BASE_URL + location;
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
add_artist_to_sitemap(original_artist_slug);
console.log(`Generating ${artist}`);
const songs = matchingSongs.map((song) => {
return {
name: song.name,
plan_code: song.plan_code,
url: process.env.REACT_APP_BASE_URL + "/backing-tracks/" + song.original_artist_slug + "/" + song.name_slug
}
});
const html = render
? ReactDOMServer.renderToStaticMarkup(
React.createElement(ArtistTemplatePage, { artist, original_artist_slug, location })
)
: "";
const fullHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="/favicon.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${artist} - Free Backing Track</title>
<link rel="stylesheet" href="${process.env.REACT_APP_BASE_URL}/css/theme.css">
<meta name="description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="keywords" content="Backing Track, ${artist}, Instrumental">
<meta name="author" content="JamKazam">
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
<meta property="og:title" content="${artist} | Free Backing Track">
<meta property="og:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta property="og:image" content="${logoPath}">
<meta property="og:url" content="${fullPath}">
<meta property="og:type" content="music.song">
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${artist} | Free Backing Track">
<meta name="twitter:description" content="Get a free ${artist} backing track, plus free tools to mute any part, slow down for practice, record yourself, more">
<meta name="twitter:image" content="${logoPath}">
<!-- Canonical URL -->
<link rel="canonical" href="${fullPath}" />
<script>
window.jamtrack_data = {
artist: "${artist}",
original_artist_slug: "${original_artist_slug}",
location: "${location}",
songs: ${JSON.stringify(songs)}
}
</script>
</head>
<body>
<div id="root">${html}</div>
<script src="/js/client-hydrate.bundle.js"></script>
</body>
</html>`;
const finalOutputPath = process.env.NODE_ENV === "development" ? `${original_artist_slug}.html` : `${original_artist_slug}.html`;
const outputFilePath = path.join(OUTPUT_DIR, finalOutputPath);
fs.writeFileSync(outputFilePath, fullHtml);
console.log(`Generated: ${outputFilePath}`);
}
close_sitemap();
console.log("All pages generated!");
});
};
let render = false;
if (process.argv.length > 2) {
render = process.argv[2] === "true" || process.argv[2] === "yes" || process.argv[2] === "1";
}
init()
generateSongPages(render);
generateArtistPages(render);

View File

@ -1,40 +0,0 @@
#!/bin/bash
# requires node 22 at least
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# default NODE_ENV to development if not set
if [ -z "$NODE_ENV" ]; then
NODE_ENV=development
fi
# default ENVIRONMENT to development if not set
if [ -z "$ENVIRONMENT" ]; then
ENVIRONMENT=development
fi
set -eo pipefail
export NODE_ENV
export ENVIRONMENT
echo "cleaning"
rm -rf $SCRIPT_DIR/build
echo "creating build dir - this is for correct image resolution; ideally this wouldn't be needed"
#mkdir -p $SCRIPT_DIR/build/assets/img
#cp -r $SCRIPT_DIR/../src/assets/img/* $SCRIPT_DIR/build/assets/img/
echo "creating client-hydrate.bundle.js for jamtrack landing pages"
# PUBLIC_URL=? for server builds
# NODE_ENV=production for server builds
npx webpack
#cp -r public/* output/
npm run build
echo "run generate.js for all jamtracks defined in the CSV"
NODE_ENV=$NODE_ENV ENVIRONMENT=$ENVIRONMENT npm run generate-song-landing-pages

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
{
"name": "react-static-site-generator",
"version": "1.0.0",
"description": "A React-based static site generator from CSV",
"main": "generate.js",
"dependencies": {
"csv-parser": "^3.0.0",
"react": "^16.4.1",
"react-dom": "^16.4.1"
},
"scripts": {
"generate-song-landing-pages": "node generate.js",
"build": "babel ../src/ --out-dir build/",
"webpack": "webpack"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/register": "^7.25.9",
"@svgr/webpack": "^8.1.0",
"babel-loader": "^9.2.1",
"dotenv-webpack": "^8.1.0",
"image-minimizer-webpack-plugin": "^4.1.3",
"imagemin": "^9.0.0",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
}
}

View File

@ -1,55 +0,0 @@
const path = require("path");
const webpack = require('webpack');
const Dotenv = require("dotenv-webpack");
const environment = process.env.ENVIRONMENT || 'development';
const node_env = process.env.NODE_ENV || 'development';
const debug = node_env == 'development';
module.exports = {
entry: "./client-hydrate.js",
output: {
path: path.resolve(__dirname, "..", "public", "js"),
filename: "client-hydrate.bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
},
},
{
test: /\.svg/,
type: 'asset/inline'
},
{
test: /\.(png|jpe?g|gif)$/i,
type: 'asset',
}
],
},
resolve: {
extensions: [".js", ".jsx"],
alias: {
react: path.resolve('./node_modules/react'),
}
},
devtool: debug ? 'eval-source-map' : 'source-map',
mode: node_env,
plugins: [
new Dotenv({
path: `../.env.${environment}`
})
],
optimization: {
minimize: node_env === 'production', //only minimize in production
},
};

View File

@ -1,22 +0,0 @@
module.exports = {
env: {
legacyBaseUrl: "http://www.jamkazam.local:3000",
apiBaseUrl: "http://www.jamkazam.local:3000/api",
},
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require("./cypress/plugins/index.js")(on, config);
},
baseUrl: "http://beta.jamkazam.local:4000",
},
component: {
devServer: {
framework: "react",
bundler: "webpack",
},
},
};

7
jam-ui/cypress.json Normal file
View File

@ -0,0 +1,7 @@
{
"baseUrl": "http://beta.jamkazam.local:4000",
"env": {
"legacyBaseUrl": "http://www.jamkazam.local:3000",
"apiBaseUrl": "http://www.jamkazam.local:3000/api"
}
}

View File

@ -1,166 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Change Current Email/Password Feature', () => {
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 });
cy.visit('/account/identity');
});
describe('Email update', () => {
it('should display the current email address', () => {
// Assert that the current email address is displayed
cy.get('[data-testid=edit_email_form]').within($form => {
cy.get('input#new_email[placeholder="sam@example.com"]');
});
});
it('shows validation error if no data is entered', () => {
// Assert that an error message is displayed
cy.get('[data-testid=edit_email_form]').within($form => {
cy.get('[data-testid=email_submit]').click();
cy.contains('Current password is required');
cy.contains('New Email is required');
});
});
it('shows validation error if new email is invalid', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter an invalid email address in the input field
cy.get('input#new_email').type('invalidemail');
cy.get('[data-testid=email_submit]').click();
// Assert that an error message is displayed
cy.contains('Email is invalid');
});
});
it('reveal password', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Click on the reveal password button
cy.get('.input-group-append').click();
// Assert that the password is revealed
cy.get('input#current_password').should('have.attr', 'type', 'text');
});
});
describe('when the user submits the form', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+/, {
statusCode: 200
}).as('updateEmail');
});
it('should update the email address', () => {
cy.get('[data-testid=edit_email_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter the new email address
cy.get('input#new_email').type('samsam@example.com');
// Submit the form
cy.get('[data-testid=email_submit]').click();
});
// Assert that the email address is updated
cy.contains('A confirmation email has been sent to your email address');
});
});
});
describe('Password update', () => {
it('shows validation error if no data is entered', () => {
// Assert that an error message is displayed
cy.get('[data-testid=edit_password_form]').within($form => {
cy.get('[data-testid=password_submit]').click();
cy.contains('Current password is required');
cy.contains('New password is required');
});
});
it('reveal password', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Click on the reveal password button
cy.get('.current-password-reveal').click();
// Assert that the password is revealed
cy.get('input#current_password').should('have.attr', 'type', 'text');
// Click on the reveal password button
cy.get('.new-password-reveal').click();
// Assert that the password is revealed
cy.get('input#new_password').should('have.attr', 'type', 'text');
});
});
describe('Submit form with errors', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/set_password/, {
statusCode: 422,
body: {
errors: {
password: ['is too short']
}
}
}).as('updatePassword');
});
it('shows validation error if new password is invalid', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter an invalid new password
cy.get('input#new_password').type('short');
cy.get('[data-testid=password_submit]').click();
// Assert that an error message is displayed
cy.contains('New Password is too short');
});
});
});
describe('Submit with valid data', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/set_password/, {
statusCode: 200
}).as('updatePassword');
});
it('should update the password', () => {
cy.get('[data-testid=edit_password_form]').within($form => {
// Enter the current password
cy.get('input#current_password').type('password');
// Enter the new password
cy.get('input#new_password').type('newpassword');
// Submit the form
cy.get('[data-testid=password_submit]').click();
});
// Assert that the password is updated
cy.contains('password has been successfully updated');
});
})
describe('Forgot password', () => {
beforeEach(() => {
cy.intercept('POST', /\S+\/users\S+\/request_reset_password/, {
statusCode: 200
}).as('forgotPassword');
});
it('sends forgot password email', () => {
cy.get('[data-testid=forgot_password]').click();
cy.contains('A password reset email has been sent to your email');
});
})
});
});

View File

@ -1,34 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Account Preferences Feature', () => {
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',
recording_pref: 0
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/users\S+/, {
statusCode: 200
}).as('updateUser');
cy.visit('/account/preferences');
});
it('should display the current recording preference', () => {
// Assert that the current recording preference is displayed
cy.get('[data-testid=recording_pref_none]').should('be.checked');
});
it('should save the new recording preference', () => {
// Select a new recording preference
cy.get('[data-testid=recording_pref_my_audio]').check('1');
cy.wait('@updateUser');
// Assert that the new recording preference is saved
cy.contains('Recording preference saved successfully.');
});
});

View File

@ -1,117 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('User subscribe to a plan', () => {
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 });
cy.stubGonSubscriptionCodes();
cy.intercept('GET', /\S+\/get_subscription/, {
statusCode: 200,
body: {
past_due: false,
has_billing_info: true,
plan_code: 'jamsubsilver',
desired_plan_code: 'jamsubgold',
admin_overide_plan_code: null,
admin_overide_ends_at: null,
in_trial: false,
trial_ends_at: null,
subscription: {
},
subscription_rules: {
remaining_monthly_play_time: null
}
}
}).as('getSubscription');
cy.intercept('GET', /\S+\/get_subscription/, {
statusCode: 200,
body: {
past_due: false,
has_billing_info: true,
plan_code: '',
desired_plan_code: '',
admin_overide_plan_code: null,
admin_overide_ends_at: null,
in_trial: false,
trial_ends_at: null,
subscription: {
},
subscription_rules: {
remaining_monthly_play_time: null
}
}
}).as('getSubscriptionWithNoPlan');
});
it('should display the current plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscription')
// Assert that the current plan is displayed
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('label').contains('Subscription Plan');
cy.get('.select-plan__single-value').should('contain', 'Gold ($9.99/month)');
cy.get('input[type=submit]').should('have.value', 'Save Plan').should('be.disabled');
});
cy.get('[data-testid=playtime]').within($playtime => {
cy.get('label').contains('You have unlimited play time');
});
cy.get('[data-testid=subscription-explanation]').within($expl => {
cy.get('.alert').contains('You are currently on the Silver (monthly) level, thank you!');
});
});
it.only('should display the current plan when no plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscriptionWithNoPlan')
// Assert that the current plan is displayed
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('label').contains('Subscription Plan');
cy.get('.select-plan__single-value').should('contain', 'Free ($0.00/month)');
cy.get('input[type=submit]').should('have.value', 'Save Plan').should('be.disabled');
});
cy.get('[data-testid=playtime]').within($playtime => {
cy.get('label').contains('You have unlimited play time.');
});
cy.get('[data-testid=subscription-explanation]').within($expl => {
cy.get('.alert').contains('You are currently on the Free (monthly) plan.');
});
});
it('change plan', () => {
cy.visit('/account/subscription');
cy.wait('@getSubscription')
cy.get('[data-testid=changeSubscriptionForm]').within($form => {
cy.get('.select-plan__control').click();
cy.get('.select-plan__option').contains('Platinum ($19.99/month)').click();
cy.get('input[type=submit]').should('be.enabled').click();
});
const text = 'You have selected the PLATINUM MONTHLY PLAN and will be charged US$19.99'
cy.contains('.modal-body', text).should('be.visible');
cy.get('.modal-content').find('.modal-footer').contains('OK').click();
cy.get('.modal-body').should('not.be.visible');
// cy.get('[data-testid=subscription-explanation]').within($expl => {
// cy.get('.alert').contains('You are currently on the Silver (monthly) level, thank you! And your plan and billing will switch to the Platinum (monthly) level on the next billing cycle.');
// });
cy.get('input[type=submit]').should('be.disabled')
});
});

View File

@ -1,29 +0,0 @@
/// <reference types="cypress" />
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 });
cy.intercept('POST', /\S+\/update_email/, { statusCode: 200, body: { ok: true } });
});
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(`/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');
cy.contains('Loading...').should('be.visible');
// Optionally, check for the success message after the email update
cy.wait(1000); // Wait for the email update to complete
cy.contains('Your email has been successfully updated.').should('be.visible');
});
});

View File

@ -1,126 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Profile update', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser();
cy.stubAuthenticate({ ...currentUser });
cy.intercept('GET', /\S+\/users\/\S+\/profile/, {
statusCode: 200,
body: {
first_name: 'David',
last_name: 'Wilson',
name: 'David Wilson',
city: 'Barstow',
state: 'CA',
country: 'US',
location: 'Barstow, CA',
photo_url: null,
biography: 'This is the musician biography. It is a long form text.',
virtual_band: true,
virtual_band_commitment: 2,
traditional_band: false,
traditional_band_commitment: null,
traditional_band_touring: false,
paid_sessions: true,
paid_sessions_hourly_rate: 19900,
paid_sessions_daily_rate: 200000,
free_sessions: true,
cowriting: true,
cowriting_purpose: 2,
subscribe_email: true,
genres: [
{
genre_id: 'asian',
player_type: 'JamRuby::User',
genre_type: 'profile'
},
{
genre_id: 'blues',
player_type: 'JamRuby::User',
genre_type: 'profile'
},
{
genre_id: 'dance',
player_type: 'JamRuby::User',
genre_type: 'profile'
}
],
instruments: [
{
description: 'Acoustic Guitar',
proficiency_level: 3,
priority: 0,
instrument_id: 'acoustic guitar'
},
{
description: 'Bass Guitar',
proficiency_level: 3,
priority: 2,
instrument_id: 'bass guitar'
},
{
description: 'Violin',
proficiency_level: 1,
priority: 0,
instrument_id: 'violin'
},
{
description: 'Banjo',
proficiency_level: 1,
priority: 1,
instrument_id: 'banjo'
}
]
}
}).as('getProfile');
cy.intercept('PUT', /\S+\/users\/\S+/, {
statusCode: 200
}).as('updateProfile');
cy.visit('/profile');
});
it('should render the profile form with persisted data', () => {
// Assert that the profile form is rendered
cy.wait('@getProfile');
cy.get('[data-testid=edit_profile_form]').within($form => {
cy.get('[data-testid=firstName]').should('have.value', 'David');
cy.get('[data-testid=lastName]').should('have.value', 'Wilson');
cy.get('[data-testid=biography]').should('contain', 'This is the musician biography. It is a long form text');
cy.get('[data-testid=subscribeEmail]').should('be.checked');
cy.get('[data-testid=virtualBand]').should('be.checked');
cy.get('[data-testid=traditionalBand]').should('not.be.checked');
cy.get('[data-testid=cowriting]').should('be.checked');
cy.get('[data-testid=instruments] input:checked').should('have.length', 4);
cy.get('[data-testid=genres] input:checked').should('have.length', 3);
});
});
it.only('should update the profile', () => {
// Update the profile form
cy.get('[data-testid=edit_profile_form]').within($form => {
// Update the first name
cy.get('[data-testid=firstName]')
.clear()
.type('Seth');
// Update the last name
cy.get('[data-testid=lastName]')
.clear()
.type('Call');
cy.wait(2000);
});
cy.reload();
cy.get('[data-testid=firstName]').should('have.value', 'Seth');
});
});

View File

@ -1,89 +0,0 @@
import makeFakeUser from '../../factories/user';
describe('Payments Page', () => {
beforeEach(() => {
// Place any setup code here, such as logging in or navigating to the payments page
const currentUser = makeFakeUser();
cy.stubAuthenticate({ ...currentUser });
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=0/, { fixture: 'payments_page1' }).as('getPayments1');
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=10/, { fixture: 'payments_page2' }).as('getPayments2');
cy.intercept('GET', /\S+\/invoice_history\?limit=10&cursor=20/, { fixture: 'payments_page3' }).as('getPayments3');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('should list user\'s payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr td').first().within(() => {
cy.contains('12/01/2022')
})
})
it('paginate through the payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr').first().within(() => {
cy.contains('12/01/2022')
})
// Click the next page button
cy.get('[data-testid="nextPageButton"]').click()
cy.wait('@getPayments2')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr:nth-child(11)').first().within(() => {
cy.contains('02/01/2022')
})
// Click the next page button
cy.get('[data-testid="nextPageButton"]').click()
cy.wait('@getPayments3')
cy.get('[data-testid="paymentsListTable"]').should('be.visible').find('tbody tr:nth-child(22)').first().within(() => {
cy.contains('03/01/2021')
})
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('should list user\'s payments', () => {
// Call the external API that fetches the payment records
cy.visit('/account/payments')
cy.wait('@getPayments1')
cy.get('[data-testid=paymentSwiper]').should('be.visible')
for (let i = 0; i < 9; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.wait('@getPayments2')
for (let i = 0; i < 10; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.get('.swiper-button-next').click();
cy.wait('@getPayments3')
for (let i = 0; i < 2; i++) {
cy.get('.swiper-button-next').click();
cy.wait(500);
}
cy.get('.swiper-button-next').should('have.class', 'swiper-button-disabled');
});
})
})

View File

@ -1,137 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Affiliate Earnings', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
cy.stubGonSubscriptionCodes();
});
describe('When user has no earnings', () => {
it('should show no earnings message', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: [
{
payments: [],
next: null
}
]
}).as('fetchAllEarnings');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarnings');
cy.contains('There is no earnings data yet ');
});
});
describe('When user has earnings', () => {
it('should show earnings', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: {
payments: [
{
payment_type: 'monthly',
year: 2021,
month: 9,
subscriptions: [
{
plan: 'jamsubsilver',
count: 2
}
],
jamtracks_sold: 3,
due_amount_in_cents: 5000
},
{
payment_type: 'monthly',
year: 2021,
month: 8,
subscriptions: [
{
plan: 'jamsubgold',
count: 1
},
{
plan: 'jamsubplatinum',
count: 2
}
],
jamtracks_sold: 2,
due_amount_in_cents: 3000
}
],
next: null
}
}).as('fetchAllEarnings');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarnings');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(0)
.should('have.text', 'September 2021');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(1)
.should('have.text', 'Silver - 2');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(2)
.should('have.text', '3');
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
.eq(3)
.should('have.text', '$50.00');
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('should show earnings', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/payments/, {
body: {
payments: [
{
payment_type: 'monthly',
year: 2021,
month: 9,
subscriptions: [
{
plan: 'jamsubsilver',
count: 2
}
],
jamtracks_sold: 3,
due_amount_in_cents: 5000
},
{
payment_type: 'monthly',
year: 2021,
month: 8,
subscriptions: [
{
plan: 'jamsubgold',
count: 1
},
{
plan: 'jamsubplatinum',
count: 2
}
],
jamtracks_sold: 2,
due_amount_in_cents: 3000
}
],
next: null
}
}).as('fetchAllEarningsOnMobile');
cy.visit('/affiliate/earnings');
cy.wait('@fetchAllEarningsOnMobile');
cy.contains('September 2021');
cy.contains('Silver - 2');
cy.contains('JamTracks Sold: 3');
cy.contains('Earnings: $50.00');
});
});
});

View File

@ -1,90 +0,0 @@
/// <reference types="cypress" />
import { day } from 'is_js';
import makeFakeUser from '../../factories/user';
describe('affiliate signups', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
//cy.stubGonSubscriptionCodes();
});
describe('when user has no signups', () => {
it('should show no signups message', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: [
{
traffics: [],
next: null
}
]
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('There is no signup data yet ');
});
});
describe('when user has signups', () => {
it('should show signups', () => {
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: {
traffics: [
{
day: '2021-09-01',
visits: 100,
signups: 10,
},
{
day: '2021-09-02',
visits: 200,
signups: 20,
}
]
}
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('Signups');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(0).should('have.text', 'September 2021');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(1).should('have.text', '300');
cy.get('[data-testid=affiliateSignupList] tbody tr:first-child > td').eq(2).should('have.text', '30');
});
});
describe('in mobile', () => {
it('should show signups in mobile', () => {
cy.viewport('iphone-6');
cy.intercept('GET', /\S+\/api\/affiliate_partners\/signups?\S+/, {
body: {
traffics: [
{
day: '2021-09-01',
visits: 100,
signups: 25,
},
{
day: '2021-10-01',
visits: 50,
signups: 10,
},
{
day: '2021-10-02',
visits: 10,
signups: 5,
}
]
}
}).as('fetchAllSignups');
cy.visit('/affiliate/signups');
cy.wait('@fetchAllSignups');
cy.contains('Signups');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').should('have.length', 2);
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-button-next').click();
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('October 2021');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('Visits: 60');
cy.get('[data-testid=affiliateSignupsSwiper] .swiper-slide').contains('Signups: 15');
});
})
})

View File

@ -1,54 +0,0 @@
///<reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('forgot password', () => {
beforeEach(() => {
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.clearCookie('remeber_token');
});
it('redirects to forgot password page', () => {
cy.visit('/');
cy.url().should('include', '/auth/login');
cy.get('a')
.contains('Forgot password?')
.click();
cy.url().should('include', '/auth/forget-password');
cy.get('h5').contains('Forgot Your Password');
});
describe('validate forgot password form', () => {
beforeEach(() => {
cy.visit('/auth/forget-password');
cy.get('[data-testid=email]').clear();
cy.get('[data-testid=submit]').should('be.disabled');
});
//invalid email format
it('invalid email format', () => {
cy.get('[data-testid=email]').type('invalid-email-format@example');
cy.get('[data-testid=submit]').click();
cy.url().should('not.include', /\/auth\/confirm-mail?\S+/);
});
//valid email format but non-existing
it('valid email format but non-existing', () => {
cy.get('[data-testid=email]').type('valid-email-format@example.com');
cy.get('[data-testid=submit]').click();
cy.url().should('not.include', /\/auth\/confirm-mail?\S+/);
});
//valid and existing email
it('valid and existing email', () => {
cy.get('[data-testid=email]').type('nuwan@jamkazam.com');
cy.get('[data-testid=submit]').click();
cy.wait(3000);
cy.contains('Please check your email!');
cy.url().should('match', /\S+auth\/confirm-mail?\S+/);
cy.contains('An email has been sent to nuwan@jamkazam.com.')
});
});
});

View File

@ -1,607 +0,0 @@
/// <reference types="cypress" />
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => {
cy.contains('Location: Denver, CO, US')
.and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:')
.and('contain', 'Latency To Me:');
cy.get('.latency-badge').contains('UNKNOWN');
cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments
cy.get('[data-testid=instruments]').contains('Acoustic Guitar: Expert');
cy.get('[data-testid=instruments]').contains('Keyboard: Expert');
//genres
cy.get('[data-testid=genres]').contains('classical, blues');
//bands
cy.get('[data-testid=bands]').contains('The Band');
//performance_samples
cy.get('[data-testid=performance_samples]').contains('Test Recording1')//.should('have.attr', 'href').and('eq', 'https://www.jamkazam.com/test-recording1');
//online presence
cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser');
cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser');
cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
describe('Friends page without data', () => {
beforeEach(() => {
cy.stubAuthenticate();
// cy.intercept('POST', /\S+\/filter/, {
// musicians: []
// }).as("getPeople_empty");
});
it('shows no records found alert', () => {
cy.visit('/friends/my');
//default api call with from_location parameter turned off
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '1st API call');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: false
// });
// //now it automatically turns on from_location parameter and fetches again
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '2nd API call - (prefetched)');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: true
// });
// cy.contains('No Records!');
// cy.get('[data-testid=btnUpdateSearch]').click();
// cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').check();
// cy.wait(1000);
// cy.get('[data-testid=btnSubmitSearch]').click();
// //default api call with from_location parameter turned on
// cy.wait('@getPeople_empty')
// .then(interception => {
// assert.isNotNull(interception.response.body, '3th API call');
// })
// .its('request.body')
// .should('deep.contain', {
// from_location: true
// });
// cy.contains('No Records!');
});
});
describe('Friends page with data', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter\?offset=0/, { fixture: 'people_page1' }).as('getPeople_page1');
cy.intercept('POST', /\S+\/filter\?offset=10/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('POST', /\S+\/filter\?offset=20/, { fixture: 'people_page3' }).as('getPeople_page3');
cy.intercept('GET', /\S+\/profile/, { fixture: 'person' });
});
describe('listing users', () => {
beforeEach(() => {
cy.visit('/friends');
});
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-13');
});
it('paginate', () => {
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
cy.wait('@getPeople_page2')
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
//cy.get('[data-testid=paginate-next-page]').should('not.exist');
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 30);
cy.get('[data-testid=paginate-next-page]').should('not.exist');
});
it('show profiles', () => {
cy.contains('Find New Friends').should('exist');
cy.contains('Update Search').should('exist');
cy.contains('Reset Filters').should('exist');
cy.get('[data-testid=peopleListTable] > tbody tr')
.should('have.length', 10)
.first()
.contains('Test User1');
});
it('click profile name', () => {
//open side panel by clicking name
cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => {
cy.contains("Test User1").click()
})
//cy.get('[data-testid=peopleListTable]').find('.person-link').first().click()
showSidePanelContent();
closeSidePanel();
});
it('click more button', () => {
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click more link', () => {
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
it('click description more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=biography-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click instruments more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=instrumentList]')
.within(() => {
cy.get('[data-testid=instrument]').should('have.length', 4); //show only 4 instruments plus more link
cy.contains('Acoustic Guitar: Expert');
cy.contains('Keyboard: Expert');
cy.contains('Ukulele: Expert');
cy.contains('Voice: Expert');
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
it('click genres more link', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('td[data-testid=genres-col]')
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
});
});
describe('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('show profile', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 10);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.contains('Test User1');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.should('be.visible');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(2)
.should('not.be.visible');
});
it('show all profile description', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=mobBiography]')
.should('not.contain', 'More');
});
it('show all instruments', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList]')
.should('not.contain', 'More');
});
it('show all genres', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList]')
.should('not.contain', 'More');
});
//it.skip('click connect button', () => {});
//it.skip('click message button', () => {});
it.skip('paginate', () => {
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 19; i++) {
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
cy.wait(500);
}
cy.wait(500);
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
});
it('click more button', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
});
});
});
describe('making friendship', () => {
it('add friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User2').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
// cy.get('[data-testid=confirmFriendRequestModal]').within(() => {
// cy.contains('Send a friend request to Test User2');
// cy.contains('Yes').click();
// });
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User2.');
});
it('remove friend', () => {
cy.intercept('GET', /\S+\/profile/, { fixture: 'friend' });
cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } });
cy.visit('/friends');
cy.contains('Test User1').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('exist')
.should('not.be.disabled')
.click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=disconnect]')
.should('not.exist');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.exist')
.should('not.be.disabled');
});
});
describe('chat window', () => {
beforeEach(() => {
cy.visit('/friends');
});
it('is not disabled for friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.should('not.be.disabled')
.trigger('mouseover');
cy.contains('Send a message').should('exist');
});
it('is not disabled for non friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(1)
.find('[data-testid=message]')
.should('not.be.disabled');
//cy.contains('You can message this user once you are friends').should('exist')
});
it('lists text messages', () => {
//initially show the most recent messages on openning chat window modal
let numberOfMessages = 10;
cy.fixture('text_messages_page1').then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json).as('getTextMessages');
});
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.wait('@getTextMessages');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
.within(() => {
cy.get('.text-message-row').should('have.length', numberOfMessages);
//display previous messages by scrolling up
const messageFixtures = ['text_messages_page2', 'text_messages_page3'];
messageFixtures.forEach(fixture => {
cy.fixture(fixture).then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json);
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('bottom');
cy.get('.modal-body .ScrollbarsCustom-Scroller')
.trigger('mouseover')
.scrollTo('top');
numberOfMessages = numberOfMessages + 10;
cy.get('.text-message-row').should('have.length', numberOfMessages);
});
});
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it('sends message by clicking send button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello');
cy.get('button')
.contains('Send')
.should('not.be.disabled')
.click();
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('sends message by pressing enter key', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Send')
.should('be.disabled');
cy.get('textarea').type('Hello{enter}');
cy.get('textarea').should('have.value', '');
cy.get('button')
.contains('Send')
.should('be.disabled');
});
});
it('goes away by clicking close button', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(0)
.find('[data-testid=message]')
.click();
cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button')
.contains('Close')
.should('not.be.disabled')
.click();
});
cy.get('[data-testid=textMessageModal]').should('not.be.visible');
});
it.skip('shows received message by other user', () => {
//TODO: this should be test in e2e test
});
});
describe('coming from email links', () => {
it("opens details sidebar", () => {
cy.visit('/friends?open=details&id=1');
showSidePanelContent();
});
it("opens chat window", () => {
cy.visit('/friends?open=message&id=1');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
cy.contains('Send Message to Test User1').should('exist');
});
it("sends friend request", () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends?open=connect&id=1');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User1.');
});
})
describe('filter', () => {
const fillFilterForm = () => {
//cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_fair]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_high]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_beginner]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_intermediate]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=proficiency_expert]').uncheck();
cy.get('[data-testid=modalUpdateSearch] input[name=from_location]').uncheck();
cy.get('#selInstruments')
.type('Drums{enter}')
.click();
cy.get('#selGenres')
.type('Pop{enter}')
.click();
cy.get('#selLastActive').type('Within last 1 Day{enter}');
cy.get('#selJoinedWithin').type('Within last 7 Day{enter}');
};
beforeEach(() => {
cy.visit('/friends');
});
it('open and close filter modal', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').should('be.visible');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=modalUpdateSearch]').should('not.be.visible');
});
it('render filter form', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').within(() => {
cy.get('input[name=latency_good]')
.should('be.checked')
.next()
.contains('Good (less than 40ms)');
cy.get('input[name=latency_fair]')
.should('be.checked')
.next()
.contains('Fair (40-60ms)');
cy.get('input[name=latency_high]')
.should('not.be.checked')
.next()
.contains('Poor (more than 60ms)');
cy.get('input[name=from_location]')
.should('not.be.checked')
});
});
it('reset filters', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm();
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('not.be.checked');
cy.get('[data-testid=modalUpdateSearch]')
.contains('Cancel')
.click();
cy.get('[data-testid=btnResetSearch]').click(); //click reset button
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch] input[name=latency_good]').should('be.checked');
});
it('submit filter form with params', () => {
//wait for stubbed request sent to fetch data for initial page load
cy.wait('@getPeople_page1').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data');
});
//the subsequent request sent to perfetch data and store in redux prefetched buffer
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
cy.wait(1000);
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form without filling any form field
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: true,
latency_fair: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
instruments: [],
genres: [],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '4th API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm(); // change filter options
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form again. but this time fill form fields
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '5th API call has data');
})
.its('request.body')
.should('deep.contain', {
latency_good: false,
latency_fair: false,
latency_high: false,
proficiency_beginner: false,
proficiency_intermediate: false,
proficiency_expert: false,
instruments: [{ value: 'drums', label: 'Drums' }],
genres: ['pop'],
from_location: false
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '6th API call has data - (prefethed)');
});
});
});
});

View File

@ -1,31 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Unsubscribe from email link', () => {
beforeEach(() => {
// cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
const currentUser = makeFakeUser({
email: 'sam@example.com'
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/unsubscribe\/\S+/, { statusCode: 200, body: { ok: true } });
})
it("redirect to home page if tok is not provided", () => {
cy.visit('/unsubscribe');
cy.location('pathname').should('eq', '/errors/404');
});
it("show unsubscribed message", () => {
cy.visit('/unsubscribe/123');
// cy.location('search')
// .should('equal', '?tok=123')
// .then((s) => new URLSearchParams(s))
// .invoke('get', 'tok')
// .should('equal', '123')
cy.contains("You have successfully unsubscribed from JamKazam emails.").should('be.visible');
cy.contains("Loading...").should('not.exist');
});
})

View File

@ -1,111 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('JamTracks Page', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
cy.visit('/jamtracks');
});
it('should display the JamTracks page', () => {
cy.get('.card-header h5').should('contain', 'Find JamTracks');
});
it('should display the download link', () => {
cy.get('[data-testid=download-pdf]')
.invoke('attr', 'href')
.should('contain', 'https://s3.amazonaws.com/jamkazam-public/public/lists/all-jamkazam-jamtracks.pdf');
});
it('should display the JamTracks search bar', () => {
cy.get('input[type="search"]').should('exist');
});
describe('search', () => {
beforeEach(() => {
//http://www.jamkazam.local:3000/api/jamtracks/autocomplete?match=ac+&limit=5
// cy.intercept('GET', /S+\/jamtracks\/autocomplete\?match=ac\S+/, {
// body: [{ artists: [{ original_artist: 'AC DC' }], songs: [] }]
// }).as('getJamTracksAutoComplete');
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=1\&\S+/, { fixture: 'jamtracks_page1' }).as('getJamTracks_page1');
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=2\&\S+/, { fixture: 'jamtracks_page2' }).as('getJamTracks_page2');
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=3\&\S+/, { fixture: 'jamtracks_page3' }).as('getJamTracks_page3');
cy.intercept('GET', /\S+\/shopping_carts/, {
statusCode: 200,
body: []
}).as('getShoppingCart');
cy.intercept('POST', /\S+\/shopping_carts\/add_jamtrack/, {
statusCode: 200,
body: { id: 1,
product_info: {
total_price: 9.99
}
}
}).as('addJamTrackToCart');
cy.intercept('GET', /\S+\/jamtracks\/purchased/, {
statusCode: 200,
body: []
}).as('getPurchasedJamTracks');
})
it('should display the JamTracks', () => {
cy.get('input[type="search"]').type('ba{enter}');
cy.wait('@getJamTracks_page1');
cy.contains('Search Results: JamTracks including "ba"');
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-name-col').should('contain', 'Back in Black by AC DC');
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col').contains('Show all tracks').click();
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 10);
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col').contains('Show fewer tracks').click();
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
//load more
cy.get('button').contains('Load More').click();
cy.wait('@getJamTracks_page2');
//load more
cy.get('button').contains('Load More').click();
cy.wait('@getJamTracks_page3');
cy.get('[data-testid=jamtracks-table] tbody tr').should('have.length', 30);
//no more pages
cy.get('[data-testid=moreBtn]').should('not.exist');
});
it('let user to purchase a JamTrack', () => {
cy.get('input[type="search"]').type('ba{enter}');
cy.wait('@getJamTracks_page1');
cy.wait('@getShoppingCart');
cy.get('[data-testid=jamtracks-table] tbody tr').eq(2).find('.purchase-button-col button').should('contain', 'Add to Cart').click();
cy.wait('@getShoppingCart');
cy.wait('@addJamTrackToCart');
cy.location('pathname').should('eq', '/shopping-cart')
});
describe.only('mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
it('should display the JamTracks', () => {
cy.get('input[type="search"]').type('ba{enter}');
cy.wait('@getJamTracks_page1');
cy.contains('Search Results: JamTracks including "ba"');
});
});
});
});

View File

@ -1,41 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('JamTracks Page', () => {
beforeEach(() => {
const currentUser = makeFakeUser();
cy.stubAuthenticate({ id: currentUser.id });
cy.visit('/my-jamtracks');
});
it('should display the My JamTracks page', () => {
cy.get('.card-header h5').should('contain', 'My JamTracks');
});
it('should display the search bar', () => {
cy.get('input[type="search"]').should('exist');
});
describe('filter', () => {
beforeEach(() => {
cy.intercept('GET', /\S+\/jamtracks\/purchased\?page=1\&\S+/, { fixture: 'my_jamtracks_page1' }).as('getMyJamTracks_page1');
});
it('should display the JamTracks', () => {
cy.get('input[type="search"]').type('ba');
cy.wait('@getMyJamTracks_page1');
cy.get('[data-testid=myJamTrackList]').should('contain', 'Back in Black by AC DC');
});
it('clicking on a JamTrack should navigate to the JamTrack page', () => {
cy.get('input[type="search"]').type('ba');
cy.wait('@getMyJamTracks_page1');
cy.get('[data-testid=myJamTrackList] a').first().click();
cy.url().should('include', '/jamtrack/1');
});
})
});

View File

@ -1,195 +0,0 @@
/// <reference types="cypress" />
describe('Top Navigation', () => {
const showSubscribeToUpdates = () => {
cy.contains('Keep JamKazam Improving').should('exist');
cy.contains('Subscribe').should('exist');
};
const showProfileDropdown = () => {
cy.get('[data-testid=navbarTopProfileDropdown]').should('exist');
cy.contains('Sign Out').should('exist');
};
describe('when user has not logged in', () => {
beforeEach(() => {
cy.stubUnauthenticate();
});
it('shows homepage', () => {
cy.visit('/');
cy.wait('@getAppFeatures');
cy.contains('Sign in').should('exist');
});
it('not allowed to protected page', () => {
cy.visit('/friends');
cy.wait('@getAppFeatures');
cy.url().should('include', '/auth/login');
cy.contains('Sign in');
cy.get('button').should('have.text', 'Sign in');
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist');
});
});
describe('when user has logged in', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.visit('/');
cy.wait('@getAppFeatures');
});
it('shows user dropdown', () => {
showSubscribeToUpdates();
showProfileDropdown();
});
it('sign out', () => {
cy.get('[data-testid=navbarTopProfileDropdown]')
.click();
cy.stubUnauthenticate();
cy.get('[data-testid=navbarTopProfileDropdown]')
.contains('Sign Out')
.click();
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist');
cy.contains('Sign in');
});
});
describe('header notifications', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.intercept('GET', /\S+\/my_notifications/, { fixture: 'notifications' });
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.visit('/');
cy.wait('@getAppFeatures');
});
it('shows notifications', () => {
cy.get('[data-testid=notificationDropdown]').should('not.be.visible');
cy.get('.bell-icon').click();
cy.get('[data-testid=notificationDropdown]').should('be.visible');
cy.get('[data-testid=notificationDropdown] .list-group-item').should('have.length', 3);
cy.get('[data-testid=notificationDropdown]')
.contains('View all')
.click(); //view all notifications
cy.url().should('include', '/notifications');
});
});
describe.skip('locale switch', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.visit('/');
cy.wait('@getAppFeatures');
});
it('translate', () => {
cy.get('.card-header').contains('Home');
cy.get('[data-testid=langSwitch]')
.contains('ES')
.click();
cy.get('.card-header').contains('Página de inicio');
cy.get('.card-header').should('not.contain', 'Home');
cy.get('[data-testid=langSwitch]')
.contains('EN')
.click();
cy.get('.card-header').contains('Home');
cy.get('.card-header').should('not.contain', 'Página de inicio');
});
});
});
describe('Side Navigation', () => {
describe('backend returns empty set of app features', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.viewport('macbook-13');
});
it('shows all menu items', () => {
cy.visit('/');
cy.wait('@getAppFeatures');
cy.get('[data-testid=verticalNavigation]').contains('Home');
cy.get('[data-testid=verticalNavigation]').contains('Sessions');
cy.get('[data-testid=verticalNavigation]').contains('Create Session');
cy.get('[data-testid=verticalNavigation]').contains('Friends');
});
it('toggles only one menu on click', () => {
cy.visit('/');
cy.wait('@getAppFeatures');
cy.get('[data-testid=verticalNavigation]')
.contains('Sessions')
.click();
cy.get('[data-testid=verticalNavigation]')
.contains('Create Session')
.should('not.visible');
cy.get('[data-testid=verticalNavigation]')
.contains('Browse Sessions')
.should('not.visible');
cy.get('[data-testid=verticalNavigation]')
.contains('Session History')
.should('not.visible');
//the Friends menu is not toggled
cy.get('[data-testid=verticalNavigation]').contains('Friends');
cy.get('[data-testid=verticalNavigation]').contains('My Friends');
});
});
describe('backend returns app features', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.viewport('macbook-13');
cy.intercept('GET', /\S+\/app_features/, {
statusCode: 200,
body: [
{
handle: '/sessions',
is_enabled: true,
feature_type: 'page',
env: 'development'
},
{
handle: '/sessions/new',
is_enabled: false,
feature_type: 'page',
env: 'development'
},
{
handle: '/sessions/history',
is_enabled: true,
feature_type: 'page',
env: 'production'
},
{
handle: '/friends',
is_enabled: true,
feature_type: 'page',
env: 'development'
},
{
handle: '/friends/my',
is_enabled: true,
feature_type: 'page',
env: 'development'
}
]
}).as('getAppFeatures');
});
it('shows only enabled menu items', () => {
cy.visit('/');
cy.wait('@getAppFeatures');
cy.get('[data-testid=verticalNavigation]').contains('Friends');
cy.get('[data-testid=verticalNavigation]').contains('Sessions');
cy.get('[data-testid=verticalNavigation]').should('not.include.text', 'Create Session')
cy.get('[data-testid=verticalNavigation]').should('not.include.text', 'Session History')
});
})
});

View File

@ -1,463 +0,0 @@
/// <reference types="cypress" />
import makeFakeSession from '../../factories/session';
import makeFakeUser from '../../factories/user';
import { faker } from '@faker-js/faker';
describe('Browse sessions', () => {
const currentUser = makeFakeUser();
beforeEach(() => {
cy.stubAuthenticate({ id: currentUser.id });
});
describe('when there are no active sessions', () => {
beforeEach(() => {
cy.intercept('GET', /\S+\/api\/sessions/, {
body: []
});
cy.visit('/sessions');
});
it('alerts when there is no records', () => {
cy.contains('There are no public, open sessions currently available for you to join');
cy.contains('create a session').click();
cy.url().should('include', '/sessions/new');
});
});
describe('when there are active sessions', () => {
describe('in desktop', () => {
beforeEach(() => {
cy.viewport('macbook-15');
});
it('lists the sessions', () => {
const session = makeFakeSession({
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').should('have.length', 1);
});
describe('session description', () => {
it('when user has provided a description', () => {
const session = makeFakeSession({
description: 'custom description',
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('custom description');
});
it('when user has not provided a description and session is public', () => {
const session = makeFakeSession({
description: null,
musician_access: true,
approval_required: false,
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('Public, open session. Feel free to join!');
});
it('when user has not provided a description and session is private and requires approval to join', () => {
const session = makeFakeSession({
description: null,
musician_access: true,
approval_required: true,
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains(
'Private session. Click the enter button in the right column to request to join'
);
});
it('when user has not provided a description and session is RSVP', () => {
const session = makeFakeSession({
description: null,
musician_access: false,
approval_required: false,
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('Only RSVP musicians may join');
});
});
describe('invitation', () => {
it('shows invite detail if the user has been invited', () => {
const session = makeFakeSession({
invitations: [
{
sender_id: '1',
receiver_id: currentUser.id
}
],
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('YOU WERE INVITED TO THIS SESSION');
});
});
describe('friend info', () => {
it('shows if there is a friend in session', () => {
const session = makeFakeSession({
participants: [
{
user: {
is_friend: true,
name: faker.person.fullName()
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
}
]
});
//console.log('_DEBUG_ session', session);
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('YOU HAVE A FRIEND IN THIS SESSION');
});
});
describe('participants', () => {
it('shows the participants', () => {
const session = makeFakeSession({
participants: [
{
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
},
{
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('John Doe');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('Ray Charles');
});
});
describe('instruments', () => {
it('shows the instruments', () => {
const session = makeFakeSession({
participants: [
{
id: 'p1',
user: {
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Electric Guitar',
instrument_id: 'electric_guitar'
},
{
id: '2',
instrument: 'Drums',
instrument_id: 'drums'
}
]
},
{
id: 'p2',
user: {
name: 'Ray Charles'
},
tracks: [
{
id: '2',
instrument: 'Bass Guitar',
instrument_id: 'bass_guitar'
}
]
}
]
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr')
.find(`[data-testid=Participantp1Tracks]`).trigger('mouseover')
//cy.contains('Electric Guitar');
// cy.get('[data-testid=sessionsListTable] tbody tr')
// .find(`[data-testid=Participantp1Tracks]`)
// .contains('Drums');
// cy.get('[data-testid=sessionsListTable] tbody tr')
// .find(`[data-testid=Participantp2Tracks]`)
// .contains('Bass');
});
});
describe('click join button - on a private session', () => {
it('shows toast message', () => {
const session = makeFakeSession({
participants: [
{
user: {
id: '1',
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
}
],
musician_access: true,
approval_required: true
});
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.intercept('GET', /\S+\/api\/users\/\S+\/latencies\?\S+/, { users: [{
user_id: currentUser.id,
first_name:"John",
last_name:"Doe",
audio_latency:5,
audio_latency_unknown:false,
}]});
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr')
.find('[data-testid=joinBtn]')
.click();
cy.contains('You have requested to join this private session.');
// cy.get('[data-testid=native-app-unavailable]').contains('Cancel').click();
});
});
describe('click join button - on a public session', () => {
it('opens native client', () => {
const session = makeFakeSession({
participants: [
{
user: {
id: '1',
name: 'John Doe'
},
tracks: [
{
id: '1',
instrument: 'Guitar'
}
]
}
],
musician_access: true,
approval_required: false
});
const newUrl = `jamkazam://url=http://www.jamkazam.local:3000/client#/findSession/custom~yes%7CjoinSessionId~${
session.id
}`;
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.intercept('GET', /\S+\/api\/users\/\S+\/latencies\?\S+/, { users: [{
user_id: currentUser.id,
first_name:"John",
last_name:"Doe",
audio_latency:5,
audio_latency_unknown:false,
}]});
cy.visit('/sessions').then(win => {
cy.stub(win, 'open').as('windowOpen');
});
// cy.get('[data-testid=sessionsListTable] tbody tr')
// .find('[data-testid=joinBtn]')
// .click();
//cy.get('@windowOpen').should('have.been.calledWith', newUrl);
});
});
});
describe.skip('in mobile', () => {
beforeEach(() => {
cy.viewport('iphone-6');
});
describe('pagination', () => {
it('shows the next page of sessions', () => {
const sessions = [];
for (let i = 0; i < 5; i++) {
sessions.push(makeFakeSession({ id: faker.string.uuid() }));
}
cy.intercept('GET', /\S+\/api\/sessions/, sessions);
cy.visit('/sessions');
cy.get('[data-testid=sessionsSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 4; i++) {
// 4 because the first one is already visible
cy.get('[data-testid=sessionsSwiper] .swiper-button-next').click();
cy.wait(500);
}
cy.get('[data-testid=sessionsSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
});
});
});
});
});

View File

@ -1,92 +0,0 @@
/// <reference types="cypress" />
import useNativeAppCheck from '../../../src/hooks/useNativeAppCheck';
describe('Create new session', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '6' });
cy.intercept('GET', /\S+\/users\/\d+\/friends/, { fixture: 'friends' }).as('friends');
});
it("adds invitees - using autocomplete list click", () => {
cy.visit('/sessions/new');
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 2)
cy.get('[data-testid=autocomplete-list] li:first').click()
cy.get('[data-testid=selected-invitees]').children().first().contains('David Wilson')
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 1)
cy.get('[data-testid=autocomplete-list] li').should('not.contain', 'David Wilson')
});
//skipping this test for now. according to the html specification, when there is a single text field in a form
//the behaviour is to submit the form on hitting the enter key. need to figureout a way to prevent this.
//https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
it.skip("adds invitees using autocomplete enter", () => {
cy.visit('/sessions/new');
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 2)
cy.get('[data-testid=autocomplete-text]').type('{enter}')
cy.get('[data-testid=selected-invitees]').children().first().contains('David Wilson')
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 1)
cy.get('[data-testid=autocomplete-list] li').should('not.contain', 'David Wilson')
});
it("removes invitees", () => {
cy.visit('/sessions/new');
cy.get('[data-testid=autocomplete-text]').type('Seth')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 1)
cy.get('[data-testid=autocomplete-list] li:first').click()
cy.get('[data-testid=selected-invitees]').children().first().contains('Seth Call')
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li').should('have.length', 2)
cy.get('[data-testid=autocomplete-list] li:first').click()
cy.get('[data-testid=selected-invitees]').children().first().find('a').click()
cy.get('[data-testid=selected-invitees]').children().should('have.length', 1)
cy.get('[data-testid=selected-invitees]').children().first().find('a').click()
cy.get('[data-testid=selected-invitees]').should('not.exist')
});
it("choose friends as invitees", ()=> {
cy.visit('/sessions/new');
cy.get('[data-testid=btn-choose-friends]').click();
cy.get('[data-testid=modal-choose-friends]').should('be.visible').contains('Invite Friends to Session')
cy.get('[data-testid=modal-choose-friends]').find('[type="checkbox"]').first().click()
cy.get('[data-testid=modal-choose-friends]').contains('Add Friends').click()
cy.get('[data-testid=selected-invitees]').children().first().contains('Seth Call')
cy.get('[data-testid=btn-choose-friends]').click();
cy.get('[data-testid=modal-choose-friends]').should('not.contain', 'Seth Call')
})
it.only("prefill form using saved data in localStorage", () => {
cy.visit('/sessions/new');
cy.get('[data-testid=session-privacy]').select("2")
cy.get('[data-testid=autocomplete-text]').type('Dav')
cy.get('[data-testid=autocomplete-list] li:first').click()
cy.get('[data-testid=session-description]').type("My test session")
cy.get('[data-testid=btn-create-session]').click();
cy.reload() //refresh browser
cy.get('[data-testid=session-privacy]').should('have.value', "2")
cy.get('[data-testid=selected-invitees]').children().first().contains('David Wilson')
cy.get('[data-testid=session-description]').contains('My test session')
})
it("submits form", () => {
const newUrl = `jamkazam://url=http://www.jamkazam.local:3000/client#/createSession/custom~yes%7Cprivacy~2%7Cdescription~test%7CinviteeIds~1`;
cy.visit('/sessions/new').then((win) => {
cy.stub(win, 'open').as('windowOpen');
});
cy.get('[data-testid=session-privacy]').select("2")
cy.get('[data-testid=autocomplete-text]').type('Seth')
cy.get('[data-testid=autocomplete-list] li:first').click()
cy.get('[data-testid=session-description]').type("test")
cy.get('[data-testid=btn-create-session]').click();
cy.get('@windowOpen').should('have.been.calledWith', newUrl);
cy.get('[data-testid=btn-create-session]').should('be.disabled')
})
})

View File

@ -1,21 +0,0 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Session History', () => {
const currentUser = makeFakeUser();
beforeEach(() => {
cy.stubAuthenticate({ id: currentUser.id });
});
describe('When user has no sessions', () => {
it('should show no sessions message', () => {
cy.intercept('GET', /\S+\/api\/sessions\/history/, {
body: []
}).as('fetchAllSessions')
cy.visit('/sessions/history');
cy.wait('@fetchAllSessions');
cy.contains('No Records!');
});
});
});

View File

@ -1,57 +0,0 @@
import { mergePartially, NestedPartial } from 'merge-partially';
import { faker } from '@faker-js/faker';
import { IUser } from './user';
interface ITrack {
id: string;
instrument: string;
}
interface IParticipant {
id: string;
client_id: string;
user: IUser;
tracks: ITrack[];
}
interface IInvitations {
id: string;
sender_id?: string;
receiver_id?: string;
}
interface ISession {
id: string;
name: string;
description: string;
participants?: IParticipant[];
invitations?: IInvitations[];
}
export default function makeFakeSession(overrides?: NestedPartial<ISession>): ISession {
return mergePartially.deep(
{
id: faker.string.uuid(),
name: faker.lorem.sentence({ min: 3, max: 5 }),
description: faker.lorem.paragraph(),
participants: [
{
id: faker.string.uuid(),
client_id: faker.string.uuid(),
user: {
id: faker.string.uuid(),
},
tracks: [{
id: faker.string.uuid(),
instrument: "Piano"
}]
}],
invitations: [
{
id: faker.string.uuid()
}
]
},
overrides
);
}

View File

@ -1,32 +0,0 @@
import { mergePartially, NestedPartial } from 'merge-partially';
import { faker } from '@faker-js/faker';
export interface IUser {
id: string;
name?: string;
firstName?: string;
lastName?: string;
email?: string;
city?: string;
state?: string;
country?: string;
biography?: string;
online?: boolean;
musician?: boolean;
photo_url?: string;
}
export default function makeFakeUser(overrides?: NestedPartial<IUser>): IUser {
const fname: string = faker.person.firstName();
const lname: string = faker.person.lastName();
return mergePartially.deep(
{
id: faker.string.uuid(),
email: faker.internet.email(),
firstName: fname,
lastName: lname,
name: fname + ' ' + lname,
},
overrides
);
}

View File

@ -1,32 +0,0 @@
[
{
"id": "1",
"first_name": "Seth",
"last_name": "Call"
},
{
"id": "2",
"first_name": "David",
"last_name": "Wilson"
},
{
"id": "3",
"first_name": "David",
"last_name": "Cluff"
},
{
"id": "4",
"first_name": "Bob",
"last_name": "Merrill"
},
{
"id": "5",
"first_name": "Jorge",
"last_name": "Lopez"
},
{
"id": "6",
"first_name": "Jorge",
"last_name": "Lopez"
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,562 +0,0 @@
{
"next": 10,
"count": 3756,
"jamtracks": [
{
"id": "1",
"name": "Back in Black",
"description": "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the AC DC song \"Back in Black\".",
"recording_type": "Cover",
"original_artist": "AC DC",
"songwriter": null,
"publisher": null,
"sales_region": "Worldwide",
"price": "1.99",
"version": "0",
"duration": 221,
"year": null,
"plan_code": "jamtrack-acdc-backinblack",
"allow_free": true,
"download_price": "2.99",
"upgrade_price": "1.0",
"tracks": [
{
"id": "103dea4d-f2a3-4414-8efe-d2ca378dda60",
"part": "Master Mix",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Master",
"position": 1000,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-e9a5a63f34b4d523ee1842fff31f88ce.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-25fcba7ace7086e3cb6b97d7e33ba72e.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-9f0b072ed9f4b546e170fcdfb302137e.mp3"
},
{
"id": "2755cbdd-0476-4f3b-9ba1-e2da561ddb4e",
"part": "Lead",
"instrument": {
"id": "voice",
"description": "Voice",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 1,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-d35c328fc3936dad9a79fe102dc72950.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-b97b37651eae352fae3b3060918c7bcb.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-d35c328fc3936dad9a79fe102dc72950.aac"
},
{
"id": "0db7c4e1-5e8d-43fe-bd35-98acd8f68b26",
"part": "Drums",
"instrument": {
"id": "drums",
"description": "Drums",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 2,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-03aadceb966caf40b96334bdd00234f6.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-854914e3e0d6fdc5f0794325b0ecaead.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-03aadceb966caf40b96334bdd00234f6.aac"
},
{
"id": "2cc79ab6-dab8-4905-85e6-0df5f8e087f1",
"part": "Bass",
"instrument": {
"id": "bass guitar",
"description": "Bass Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 3,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-61c334ac87f811bd010ed3a910764c2e.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-4066dafd7b72e9993b0c0fe1dba2b332.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-61c334ac87f811bd010ed3a910764c2e.aac"
},
{
"id": "ed1d3487-3b32-442f-9c76-8a36fe3bb643",
"part": "Solo",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 4,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-e9fe8572a9ac1022762642cbd92b3c34.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-5fb058042254206cfa9fb4dcb0310b2c.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-e9fe8572a9ac1022762642cbd92b3c34.aac"
},
{
"id": "f4ce7c91-7542-4e03-8fc2-68b31683d33e",
"part": "Rhythm 1",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 5,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-6b498479823d4131a01fa535817d5eab.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-f4cbb31dbde3e1a3e6012730a7e0e10f.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-6b498479823d4131a01fa535817d5eab.aac"
},
{
"id": "2d96c7ec-59f1-4d56-8a7f-7f4c75a0ccef",
"part": "Rhythm 2",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 6,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-a626d7c632560f6737e1b6024141289e.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-06a0e5af451f001f3465992efcd34ec0.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-a626d7c632560f6737e1b6024141289e.aac"
},
{
"id": "fce018ca-c897-4137-aa10-ef56a8e1831f",
"part": "Intro Scrapes",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 7,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-0ddfaa7154e9ba35d05d60477d5dd3e9.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-f53ce3c5f9dcf81af51560f52635fbb0.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-0ddfaa7154e9ba35d05d60477d5dd3e9.aac"
},
{
"id": "c9b3e0a8-4db0-4d0f-9769-398a6d56506e",
"part": "Main",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 8,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-234a224f75a97d7ff8f55442ece6fcde.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-828c9691f5435dea1c90182fa2618c9b.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-234a224f75a97d7ff8f55442ece6fcde.aac"
},
{
"id": "28c3df07-2a88-45a9-9ae6-3399a5d2eb20",
"part": "Sound FX",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2021-02-02T23:16:46.168Z",
"updated_at": "2021-02-02T23:16:46.168Z",
"popularity": 3
},
"track_type": "Track",
"position": 9,
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-6c859c73036cd55bceb65f19f2d2f2f3.mp3",
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-f840d8df4c7388f776477139025ee712.ogg",
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-6c859c73036cd55bceb65f19f2d2f2f3.aac"
}
],
"licensor": null,
"genres": ["Rock", "Pop"],
"added_cart": true,
"can_download": true,
"purchased": true
},
{
"id": "531",
"name": "1234",
"description": "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the Feist song \"1234\".",
"recording_type": "Cover",
"original_artist": "Feist",
"songwriter": null,
"publisher": null,
"sales_region": "Worldwide",
"price": "1.99",
"version": "1",
"duration": 184,
"year": 2007,
"plan_code": "jamtrack-feist-1234",
"allow_free": true,
"download_price": "2.99",
"upgrade_price": "1.0",
"tracks": [
{
"id": "b834ce9c-2624-4977-a079-0b1b5d90ad6e",
"part": "Clicktrack",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Click",
"position": 10000,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234%20-%20Tency%20Music/1234%20Clicktrack-44100-preview-90bed87ea6402ab0f8d283ffe094c95f.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234%20-%20Tency%20Music/1234%20Clicktrack-44100-preview-7198bae1519e40827aff0bd704e2066b.ogg",
"preview_aac_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234%20-%20Tency%20Music/1234%20Clicktrack-44100-preview-90bed87ea6402ab0f8d283ffe094c95f.aac"
},
{
"id": "b5a67d64-f94f-453e-9dab-691304275bc2",
"part": "Master Mix",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Master",
"position": 1000,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1%202%203%204%20Master%20Mix-44100-preview-5fe2a923a614f17dd9c6440b65c1e884.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1%202%203%204%20Master%20Mix-44100-preview-aaefd823d7deaa1bc862eb8ec1e53fe3.ogg",
"preview_aac_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1%202%203%204%20Master%20Mix-44100-preview-6d3edecd174080e88e611984969f1b2d.aac"
},
{
"id": "27353eb1-9f2b-487b-9047-93f49446b4db",
"part": "Lead",
"instrument": {
"id": "voice",
"description": "Voice",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 1,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Vocal%20-%20Lead-44100-preview-426d923380d10fbdf2d5a4e4108b0de2.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Vocal%20-%20Lead-44100-preview-30eaef3aea1499493cbad76c5c3c8a50.ogg",
"preview_aac_url": null
},
{
"id": "78736d23-e657-4922-b145-9f7db40bb36e",
"part": "Backing",
"instrument": {
"id": "voice",
"description": "Voice",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 2,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Vocal%20-%20Backing-44100-preview-c0fb7721f3c8cd3253c8c5a6d6b034dd.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Vocal%20-%20Backing-44100-preview-b7145437bbb6b01b566f10ef9009d001.ogg",
"preview_aac_url": null
},
{
"id": "1978c439-115b-4214-85a6-beec780310a1",
"part": "Drums",
"instrument": {
"id": "drums",
"description": "Drums",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 3,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Drums%20-%20Drums-44100-preview-da3d7e78b9a7b50bea7bcc8f13a7d4b0.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Drums%20-%20Drums-44100-preview-d320dc7600c6d9c08af19b813d6d8176.ogg",
"preview_aac_url": null
},
{
"id": "8457c55d-924d-4048-8c5f-46b5c7668552",
"part": "Bass",
"instrument": {
"id": "bass guitar",
"description": "Bass Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 4,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Bass%20Guitar%20-%20Bass-44100-preview-c4b1ce442a9645f6ead0f078afe48d3d.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Bass%20Guitar%20-%20Bass-44100-preview-8a0b0d664802015a219f3320d4d5c0cd.ogg",
"preview_aac_url": null
},
{
"id": "ca5d5597-1a19-45b1-9d1b-478f6b6c4b19",
"part": "Piano",
"instrument": {
"id": "piano",
"description": "Piano",
"created_at": "2014-02-16T13:10:07.059Z",
"updated_at": "2014-02-16T13:10:07.059Z",
"popularity": 2
},
"track_type": "Track",
"position": 5,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Piano%20-%20Piano-44100-preview-208fb9e053bb445c1d55ec443809211c.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Piano%20-%20Piano-44100-preview-8940844b3972b39ad9b786805496bc1b.ogg",
"preview_aac_url": null
},
{
"id": "755c6c48-fc6c-4dc8-bbf2-9dc0d0a5f2a8",
"part": "Acoustic",
"instrument": {
"id": "acoustic guitar",
"description": "Acoustic Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 6,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Acoustic%20Guitar%20-%20Acoustic-44100-preview-11bde0963b7a833072a49c1096a0b6ef.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Acoustic%20Guitar%20-%20Acoustic-44100-preview-92c44a32feac702880796fd9d6d5f883.ogg",
"preview_aac_url": null
},
{
"id": "508bcbaa-4d29-44c0-8cdd-f57fd3fee717",
"part": "Banjo",
"instrument": {
"id": "banjo",
"description": "Banjo",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 2
},
"track_type": "Track",
"position": 7,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Banjo%20-%20Banjo-44100-preview-6ed9db0acb0616651e297b0c057f453d.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Banjo%20-%20Banjo-44100-preview-1e18e0b352e4b05fefa35ce7fd9c84a1.ogg",
"preview_aac_url": null
},
{
"id": "6ef8af53-6615-450c-b78b-4310fb0e0395",
"part": "Strings",
"instrument": {
"id": "orchestra",
"description": "Orchestra",
"created_at": "2015-08-11T16:08:58.806Z",
"updated_at": "2015-08-11T16:08:58.806Z",
"popularity": 1
},
"track_type": "Track",
"position": 8,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Orchestra%20-%20Strings-44100-preview-7017822e5edaedbe91fea259cc29666c.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Orchestra%20-%20Strings-44100-preview-70f5d96ed08b1d6a625f21c8be3f691c.ogg",
"preview_aac_url": null
},
{
"id": "91427115-63be-49e6-adb1-078f4a7f7ae8",
"part": "Trumpet",
"instrument": {
"id": "trumpet",
"description": "Trumpet",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 2
},
"track_type": "Track",
"position": 9,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Trumpet%20-%20Trumpet-44100-preview-74d7ab8c5af40dc9768176df2afd691a.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Feist/1234/1234%20Stem%20-%20Trumpet%20-%20Trumpet-44100-preview-df093dfebc96d3c22c13ed8d14b44ed0.ogg",
"preview_aac_url": null
}
],
"licensor": { "id": "027d90a1-b126-4d5a-8af6-3167296dfb04", "name": "Tency Music" },
"genres": ["Folk", "Alternative Rock"],
"added_cart": false,
"can_download": false,
"purchased": false
},
{
"id": "1437",
"name": "18 And Life",
"description": "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the Skid Row song \"18 And Life\".",
"recording_type": "Cover",
"original_artist": "Skid Row",
"songwriter": null,
"publisher": null,
"sales_region": "Worldwide",
"price": "1.99",
"version": "1",
"duration": 227,
"year": 1989,
"plan_code": "jamtrack-skidrow-18andlife",
"allow_free": true,
"download_price": "2.99",
"upgrade_price": "1.0",
"tracks": [
{
"id": "7c515f02-bebd-4fdd-b30a-81b72ec277b9",
"part": "Clicktrack",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Click",
"position": 10000,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life%20-%20Tency%20Music/click-44100-preview-424086caaa67c89532635ef0970b2d75.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life%20-%20Tency%20Music/click-44100-preview-d30de7c1353826896110d3ce5f459b23.ogg",
"preview_aac_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life%20-%20Tency%20Music/click-44100-preview-424086caaa67c89532635ef0970b2d75.aac"
},
{
"id": "db0a34e4-71e9-4342-b04a-e7c7f068b4fb",
"part": "Master Mix",
"instrument": {
"id": "computer",
"description": "Computer",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Master",
"position": 1000,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Master%20Mix-44100-preview-5bf5b2872e898dc43875f829e238b62b.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Master%20Mix-44100-preview-3ba1eb2b8ab936212cc299cfb8db34ac.ogg",
"preview_aac_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Master%20Mix-44100-preview-146e9a850f83a2f76dc31dc9489dc3aa.aac"
},
{
"id": "cc237bfd-5d87-413b-a460-55d17594f785",
"part": "Lead",
"instrument": {
"id": "voice",
"description": "Voice",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 1,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/ld-44100-preview-04a953ee8607a97e8533ea5adf4560a1.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/ld-44100-preview-3486ef50eb889b71208040a0a13de52f.ogg",
"preview_aac_url": null
},
{
"id": "a182cf33-1d61-41a8-8e9b-fd23d501885c",
"part": "Drums",
"instrument": {
"id": "drums",
"description": "Drums",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 2,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/drums-44100-preview-7ec143f7aa0dc019a2af48c6861c400b.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/drums-44100-preview-6fd8eacb6a8bd35a85e0d4a3a8ec120c.ogg",
"preview_aac_url": null
},
{
"id": "406c1f87-c6c1-406f-85a9-05768267ff46",
"part": "Bass",
"instrument": {
"id": "bass guitar",
"description": "Bass Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 3,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/bass-44100-preview-37326a28b8d55f4b61a00eb7406a4da9.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/bass-44100-preview-ff2e87a89ae7693fa8f15a3d065c5833.ogg",
"preview_aac_url": null
},
{
"id": "fa46fcd7-0647-4f94-93fd-bddb403abc8b",
"part": "Lead",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 4,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Lead-44100-preview-7b8ac822d2a0ccf341c4607919e5125a.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Lead-44100-preview-3d13650573ebe20f939a7e2d661f9fab.ogg",
"preview_aac_url": null
},
{
"id": "42e6c71e-715c-48b9-b000-fb7fdabd3fe3",
"part": "Rhythm Distorted",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 5,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%20Distorted-44100-preview-4f0312e6d429fa211c873ecd671c2629.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%20Distorted-44100-preview-36e75c706ff27930a54be1159afafd9a.ogg",
"preview_aac_url": null
},
{
"id": "a16a69b1-2b89-4bca-8039-bb927c7f517f",
"part": "Arpeggios Clean",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 6,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Arpeggios%20Clean-44100-preview-f1f3df4288f9ac9bd8183303099f1a40.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Arpeggios%20Clean-44100-preview-f8da9761dbed9afd32d636fb0ba2f757.ogg",
"preview_aac_url": null
},
{
"id": "8246f3fb-6d35-4a31-9bcd-ef81fd39c66f",
"part": "Arpeggios Distorted",
"instrument": {
"id": "electric guitar",
"description": "Electric Guitar",
"created_at": "2013-01-03T01:57:43.040Z",
"updated_at": "2013-01-03T01:57:43.040Z",
"popularity": 3
},
"track_type": "Track",
"position": 7,
"preview_mp3_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Arpeggios%20Distorted-44100-preview-2bf523d0f429eaff366691fff0dbcb5f.mp3",
"preview_ogg_url": "https://jamkazam-public.s3.amazonaws.com/jam_track_previews/Skid%20Row/18%20And%20Life/18%20And%20Life%20Stem%20-%20Electric%20Guitar%20-%20Arpeggios%20Distorted-44100-preview-e25718a81d9d0a011746def84547f1e9.ogg",
"preview_aac_url": null
}
],
"licensor": { "id": "027d90a1-b126-4d5a-8af6-3167296dfb04", "name": "Tency Music" },
"genres": ["Metal", "Rock", "Hard Rock"],
"added_cart": false,
"can_download": false,
"purchased": false
}
]
}

View File

@ -1,7 +1,4 @@
{ [
"next": null,
"unread_total": 3,
"notifications": [
{ {
"description": "TEXT_MESSAGE", "description": "TEXT_MESSAGE",
"source_user_id": "27bd4a30-d1b8-4eea-8454-01a104d59381", "source_user_id": "27bd4a30-d1b8-4eea-8454-01a104d59381",
@ -24,8 +21,7 @@
"notification_id": "63fcd878-9a22-4419-9cee-8a51a615da97", "notification_id": "63fcd878-9a22-4419-9cee-8a51a615da97",
"fan_access": null, "fan_access": null,
"musician_access": null, "musician_access": null,
"approval_required": null, "approval_required": null
"read_at": null
}, },
{ {
"description": "FRIEND_REQUEST", "description": "FRIEND_REQUEST",
@ -49,8 +45,7 @@
"notification_id": "3364b5f1-8946-46a3-b635-86d89d237849", "notification_id": "3364b5f1-8946-46a3-b635-86d89d237849",
"fan_access": null, "fan_access": null,
"musician_access": null, "musician_access": null,
"approval_required": null, "approval_required": null
"read_at": null
}, },
{ {
"description": "FRIEND_REQUEST_ACCEPTED", "description": "FRIEND_REQUEST_ACCEPTED",
@ -74,8 +69,6 @@
"notification_id": "bb9269f3-721c-48cd-9bf6-bcff72877198", "notification_id": "bb9269f3-721c-48cd-9bf6-bcff72877198",
"fan_access": null, "fan_access": null,
"musician_access": null, "musician_access": null,
"approval_required": null, "approval_required": null
"read_at": null
} }
] ]
}

View File

@ -1,74 +0,0 @@
{
"entries": [
{
"id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
"created_at": "2022-12-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2",
"created_at": "2022-11-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g3h4i5j6-k7l8-m9n0-o1p2-q3r4s5t6u7v8w9x0",
"created_at": "2022-10-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y1z2a3b4-c5d6-e7f8-g9h0-i1j2k3l4m5n6o7",
"created_at": "2022-09-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "p8q9r0s1-t2u3-v4w5-x6y7-z8a9b0c1d2e3f4",
"created_at": "2022-08-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g5h6i7j8-k9l0-m1n2-o3p4-q5r6s7t8u9v0w1x2",
"created_at": "2022-07-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y3z4a5b6-c7d8-e9f0-g1h2-i3j4k5l6m7n8o9",
"created_at": "2022-06-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "p2q3r4s5-t6u7-v8w9-x0y1-z2a3b4c5d6e7f8",
"created_at": "2022-05-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g9h0i1j2-k3l4-m5n6-o7p8-q9r0s1t2u3v4w5x6",
"created_at": "2022-04-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y7z8a9b0-c1d2-e3f4-g5h6-i7j8k9l0m1n2o3p4",
"created_at": "2022-03-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
}
]
}

View File

@ -1,82 +0,0 @@
{
"entries": [
{
"id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
"created_at": "2022-02-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2",
"created_at": "2022-01-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g3h4i5j6-k7l8-m9n0-o1p2-q3r4s5t6u7v8w9x0",
"created_at": "2021-12-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y1z2a3b4-c5d6-e7f8-g9h0-i1j2k3l4m5n6o7",
"created_at": "2021-11-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "p8q9r0s1-t2u3-v4w5-x6y7-z8a9b0c1d2e3f4",
"created_at": "2021-10-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g5h6i7j8-k9l0-m1n2-o3p4-q5r6s7t8u9v0w1x2",
"created_at": "2021-09-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y3z4a5b6-c7d8-e9f0-g1h2-i3j4k5l6m7n8o9",
"created_at": "2021-08-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "p2q3r4s5-t6u7-v8w9-x0y1-z2a3b4c5d6e7f8",
"created_at": "2021-07-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "g9h0i1j2-k3l4-m5n6-o7p8-q9r0s1t2u3v4w5x6",
"created_at": "2021-06-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "y7z8a9b0-c1d2-e3f4-g5h6-i7j8k9l0m1n2o3p4",
"created_at": "2021-05-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
"created_at": "2021-04-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
}
]
}

View File

@ -1,19 +0,0 @@
{
"entries": [
{
"id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
"created_at": "2021-03-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
},
{
"id": "q7r8s9t0-u1v2-w3x4-y5z6-a7b8c9d0e1f2",
"created_at": "2021-02-01T00:00:00Z",
"total_in_cents": 999,
"description": "JamKazam Gold Plan - Monthly",
"status": "paid"
}
]
}

View File

@ -311,7 +311,8 @@
"genres": [] "genres": []
} }
], ],
"offset": 10, "offset": 11,
"page_count": 2,
"my_audio_latency": 5, "my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}", "filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me", "description": "Current Search: Sort = Latency to Me",

View File

@ -285,7 +285,8 @@
"genres": [] "genres": []
} }
], ],
"offset": 20, "offset": null,
"page_count": 2,
"my_audio_latency": 5, "my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}", "filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me", "description": "Current Search: Sort = Latency to Me",

View File

@ -1,293 +0,0 @@
{
"musicians": [
{
"id": "21",
"first_name": "Test",
"last_name": "User11",
"name": "Test User11",
"city": "Denver",
"state": "CO",
"country": "US",
"online": true,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User1",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 },
{ "instrument_id": "keyboard", "description": "Keyboard", "proficiency_level": 3, "priority": 8 },
{ "instrument_id": "ukulele", "description": "Ukulele", "proficiency_level": 3, "priority": 11 },
{ "instrument_id": "voice", "description": "Voice", "proficiency_level": 3, "priority": 13 },
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 }
],
"followings": [],
"is_friend": true,
"is_following": false,
"pending_friend_request": false,
"friend_count": 1,
"follow_count": 0,
"recording_count": 0,
"session_count": 10,
"audio_latency": 5,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "22",
"first_name": "Test",
"last_name": "User12",
"name": "Test User12",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "23",
"first_name": "Test",
"last_name": "User13",
"name": "Test User13",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "24",
"first_name": "Test",
"last_name": "User14",
"name": "Test User14",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "25",
"first_name": "Test",
"last_name": "User15",
"name": "Test User15",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "26",
"first_name": "Test",
"last_name": "User16",
"name": "Test User16",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "27",
"first_name": "Test",
"last_name": "User17",
"name": "Test User17",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "28",
"first_name": "Test",
"last_name": "User18",
"name": "Test User18",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "29",
"first_name": "Test",
"last_name": "User19",
"name": "Test User19",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "30",
"first_name": "Test",
"last_name": "User20",
"name": "Test User20",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
}
],
"offset": null,
"my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me",
"is_blank_filter": false
}

View File

@ -50,6 +50,7 @@
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b3", "service_type": "facebook", "username": "testuser" }, { "id": "005a7c78-db8b-4f72-a51f-d64d579c22b3", "service_type": "facebook", "username": "testuser" },
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b4", "service_type": "twitter", "username": "testuser" } { "id": "005a7c78-db8b-4f72-a51f-d64d579c22b4", "service_type": "twitter", "username": "testuser" }
], ],
"performance_samples": [],
"genres": [ "genres": [
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "profile" }, { "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "blues", "player_type": "JamRuby::User", "genre_type": "free_sessions" } { "genre_id": "blues", "player_type": "JamRuby::User", "genre_type": "free_sessions" }
@ -73,16 +74,6 @@
{ "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" }, { "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" },
{ "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" } { "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" }
], ],
"performance_samples": [
{
"id": "1",
"service_type": "youtube",
"claimed_recording": {
"id": "1",
"name": "Test Recording1"
}
}
],
"is_friend": false, "is_friend": false,
"is_following": false, "is_following": false,
"is_liking": false, "is_liking": false,

View File

@ -1,82 +0,0 @@
[
{
"id": "df953ba-7c59-4762-8cc3-279db82e872a",
"name": "Open Session",
"description": "Feel free to join this session, it's open!",
"musician_access": true,
"approval_required": false,
"friends_can_join": false,
"fan_access": true,
"fan_chat": false,
"user_id": "29becbf4-8be5-4078-9405-0edadc9fa42d",
"claimed_recording_initiator_id": null,
"track_changes_counter": 0,
"max_score": 0,
"backing_track_path": null,
"metronome_active": false,
"jam_track_initiator_id": null,
"jam_track_id": null,
"music_session_id_int": 2210,
"use_video_conferencing_server": false,
"music_notations": [],
"participants": [
{
"ip_address": "192.168.1.110",
"client_id": "63cdbcf7-a3c6-49bf-9412-0c09d4a6796b",
"joined_session_at": "2023-10-26T07:16:22.605Z",
"id": "3c1f2a74-0ccf-4ed1-9828-ce909edf61b7",
"metronome_open": false,
"is_jamblaster": false,
"client_role": "parent",
"parent_client_id": null,
"client_id_int": 78125,
"tracks": [
{
"id": "833de71f-7bc0-4d8e-9ea6-8a695181960b",
"connection_id": "3c1f2a74-0ccf-4ed1-9828-ce909edf61b7",
"instrument_id": "electric guitar",
"sound": "stereo",
"client_track_id": "FWAPMulti_2_10200m",
"client_resource_id": "FWAPMulti_2_10200",
"updated_at": "2023-10-26T07:16:22.611Z",
"instrument": "Electric Guitar"
}
],
"backing_tracks": [],
"user": {
"id": "29becbf4-8be5-4078-9405-0edadc9fa42d",
"photo_url": null,
"name": "Nuwan Chathuranga",
"is_friend": true,
"connection_state": "connected",
"subscription": null
}
}
],
"invitations": [
{
"id": "d49f3c07-7f49-4dad-8de1-2020046438de",
"sender_id": "29becbf4-8be5-4078-9405-0edadc9fa42d",
"receiver_id": "27bd4a30-d1b8-4eea-8454-01a104d59381"
}
],
"lesson_session": null,
"mount": {
"id": "c3504b02-dc18-4d85-a562-117eaaffc136",
"name": "/5tZz2_G8kT0-8UlUAna_yQ.mp3",
"sourced": false,
"listeners": 0,
"bitrate": 128,
"subtype": null,
"url": "http://localhost:10000/5tZz2_G8kT0-8UlUAna_yQ.mp3",
"mime_type": "audio/mpeg"
},
"can_join": true,
"genres": [
"Pop"
],
"recording": null,
"share_url": "http://www.jamkazam.local:3000/s/T50BWPH9ICC",
"session_controller_id": "29becbf4-8be5-4078-9405-0edadc9fa42d"
}
]

View File

@ -30,9 +30,9 @@ function submitLogin(){
describe('Unauthenticated users redirect to login page', () => { describe('Unauthenticated users redirect to login page', () => {
it('redirects to login page', () => { it('redirects to login page', () => {
cy.clearCookie('remeber_token') cy.clearCookie('remeber_token')
cy.visit('/') cy.visit('/friends')
cy.url().should('include', '/auth/login') cy.url().should('include', '/authentication/basic/login')
cy.contains('Sign In') cy.contains('Sign in')
}) })
}) })
@ -42,7 +42,7 @@ describe('Login page', () => {
}) })
it('validate login form', () => { it('validate login form', () => {
cy.visit('/auth/login') cy.visit('/authentication/basic/login')
cy.reload() cy.reload()
cy.get('[data-testid=submit]').should('be.disabled') cy.get('[data-testid=submit]').should('be.disabled')
cy.get('[data-testid=email]').type('invalid-email-format@example') cy.get('[data-testid=email]').type('invalid-email-format@example')
@ -54,7 +54,7 @@ describe('Login page', () => {
}) })
it('submit login form with invalid credentials', () => { it('submit login form with invalid credentials', () => {
cy.visit('/auth/login') cy.visit('/authentication/basic/login')
cy.reload() cy.reload()
cy.get('[data-testid=email]').type('peter@example.com') cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=password]').type('wrong') cy.get('[data-testid=password]').type('wrong')
@ -63,7 +63,7 @@ describe('Login page', () => {
}) })
it('submits login form', () => { it('submits login form', () => {
cy.visit('/auth/login') cy.visit('/authentication/basic/login')
submitLogin() submitLogin()
cy.url().should('eq', Cypress.config().baseUrl + '/') // tests won't fail in case the port changes cy.url().should('eq', Cypress.config().baseUrl + '/') // tests won't fail in case the port changes
//cy.contains('Signed in as peter@example.com') //cy.contains('Signed in as peter@example.com')
@ -73,7 +73,7 @@ describe('Login page', () => {
it('redirect to requested page', () => { it('redirect to requested page', () => {
cy.visit('/friends') cy.visit('/friends')
cy.url().should('include', '/auth/login') cy.url().should('include', '/authentication/basic/login')
cy.reload() cy.reload()
submitLogin() submitLogin()
cy.url().should('eq', Cypress.config().baseUrl + '/friends') cy.url().should('eq', Cypress.config().baseUrl + '/friends')
@ -81,13 +81,4 @@ describe('Login page', () => {
}) })
describe('Forget password page', () => {
it('submit forget password form', () => {
cy.visit('/auth/forget-password')
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=submit]').click()
cy.contains('An email is sent')
})
})

View File

@ -2,14 +2,16 @@
const showSidePanelContent = () => { const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1'); cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body').first().within(() => { cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => {
cy.contains('Location: Denver, CO, US') cy.contains('Location: Denver, CO, US')
.and('contain', 'Skill Level: Professional') .and('contain', 'Skill Level: Professional')
.and('contain', 'Joined JamKazam: 08-26-2021') .and('contain', 'Joined JamKazam: 08-26-2021')
.and('contain', 'Last Active:') .and('contain', 'Last Active:')
.and('contain', 'Latency To Me:'); .and('contain', 'Latency To Me:');
cy.get('.latency-badge').contains('UNKNOWN'); cy.get('.latency-badge').contains('UNKNOWN');
});
cy.get('[data-testid=profileSidePanel] .modal-body').within(() => {
cy.get('[data-testid=biography]').contains('Biography of Test User1'); cy.get('[data-testid=biography]').contains('Biography of Test User1');
//instruments //instruments
@ -23,14 +25,17 @@ const showSidePanelContent = () => {
cy.get('[data-testid=bands]').contains('The Band'); cy.get('[data-testid=bands]').contains('The Band');
//performance_samples //performance_samples
cy.get('[data-testid=performance_samples]').contains('Test Recording1')//.should('have.attr', 'href').and('eq', 'https://www.jamkazam.com/test-recording1'); //cy.get('[data-testid=performance_samples]').contains('The Band')
//online presence //online presence
cy.get('[data-testid=online_presences]').contains('Soundcloud').should('have.attr', 'href').and('eq', 'https://www.soundcloud.com/testuser');
cy.get('[data-testid=online_presences]').contains('Reverbnation').should('have.attr', 'href').and('eq', 'https://www.reverbnation.com/testuser');
cy.get('[data-testid=online_presences]').contains('Bandcamp').should('have.attr', 'href').and('eq', 'https://testuser.bandcamp.com');
cy.get('[data-testid=online_presences]').contains('Fandalism').should('have.attr', 'href').and('eq', 'https://www.fandalism.com/testuser');
cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser'); cy.get('[data-testid=online_presences]').contains('Youtube').should('have.attr', 'href').and('eq', 'https://www.youtube.com/testuser');
cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser'); cy.get('[data-testid=online_presences]').contains('Facebook').should('have.attr', 'href').and('eq', 'https://www.facebook.com/testuser');
cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser'); cy.get('[data-testid=online_presences]').contains('Twitter').should('have.attr', 'href').and('eq', 'https://www.twitter.com/testuser');
}); });
}; };
const closeSidePanel = () => { const closeSidePanel = () => {
@ -45,8 +50,7 @@ describe('Friends page without data', () => {
}).as("getPeople_empty"); }).as("getPeople_empty");
}); });
//from_location checkbox is hidden for now. so we skip this test for now it('from_location is unchecked', () => {
it.skip('from_location is unchecked', () => {
cy.visit('/friends'); cy.visit('/friends');
//default api call with from_location parameter turned off //default api call with from_location parameter turned off
@ -59,12 +63,30 @@ describe('Friends page without data', () => {
from_location: false from_location: false
}); });
//now it automatically turns on from_location parameter and fetches again
cy.wait('@getPeople_empty') cy.wait('@getPeople_empty')
.then(interception => { .then(interception => {
assert.isNotNull(interception.response.body, '2nd API call - (prefetched)'); assert.isNotNull(interception.response.body, '2nd API call - (prefetched)');
}) })
.its('request.body') .its('request.body')
.should('deep.contain', {
from_location: false
});
//now it automatically turns on from_location parameter and fetches again
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call');
})
.its('request.body')
.should('deep.contain', {
from_location: true
});
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '4th API call - (prefetched)');
})
.its('request.body')
.should('deep.contain', { .should('deep.contain', {
from_location: true from_location: true
}); });
@ -79,7 +101,16 @@ describe('Friends page without data', () => {
//default api call with from_location parameter turned on //default api call with from_location parameter turned on
cy.wait('@getPeople_empty') cy.wait('@getPeople_empty')
.then(interception => { .then(interception => {
assert.isNotNull(interception.response.body, '3th API call'); assert.isNotNull(interception.response.body, '5th API call');
})
.its('request.body')
.should('deep.contain', {
from_location: true
});
cy.wait('@getPeople_empty')
.then(interception => {
assert.isNotNull(interception.response.body, '6th API call - (prefetched)');
}) })
.its('request.body') .its('request.body')
.should('deep.contain', { .should('deep.contain', {
@ -93,20 +124,15 @@ describe('Friends page without data', () => {
}); });
describe('Friends page with data', () => { describe.only('Friends page with data', () => {
beforeEach(() => { beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter\?offset=0/, { fixture: 'people_page1' }).as('getPeople_page1'); cy.intercept('POST', /\S+\/filter\?page=0/, { fixture: 'people_page1' }).as('getPeople_page1');
cy.intercept('POST', /\S+\/filter\?offset=10/, { fixture: 'people_page2' }).as('getPeople_page2'); cy.intercept('POST', /\S+\/filter\?page=1/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('POST', /\S+\/filter\?offset=20/, { fixture: 'people_page3' }).as('getPeople_page3'); cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('GET', /\S+\/profile/, { fixture: 'person' });
cy.intercept('GET', /\S+\/my_notifications\S+/, {
statusCode: 200,
body: []
});
}); });
describe('listing users', () => { describe.only('listing users', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/friends'); cy.visit('/friends');
}); });
@ -118,11 +144,8 @@ describe('Friends page with data', () => {
it('paginate', () => { it('paginate', () => {
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10); cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
cy.wait('@getPeople_page2')
cy.get('[data-testid=paginate-next-page]').click(); cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20); cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 30);
cy.get('[data-testid=paginate-next-page]').should('not.exist'); cy.get('[data-testid=paginate-next-page]').should('not.exist');
}); });
@ -138,10 +161,9 @@ describe('Friends page with data', () => {
it('click profile name', () => { it('click profile name', () => {
//open side panel by clicking name //open side panel by clicking name
cy.get('[data-testid=peopleListTable]').find('.person-link').first().within(() => { cy.get('[data-testid=peopleListTable]').within(() => {
cy.contains("Test User1").click() cy.contains('Test User1').click();
}) });
//cy.get('[data-testid=peopleListTable]').find('.person-link').first().click()
showSidePanelContent(); showSidePanelContent();
closeSidePanel(); closeSidePanel();
}); });
@ -182,7 +204,7 @@ describe('Friends page with data', () => {
.first() .first()
.find('[data-testid=instrumentList]') .find('[data-testid=instrumentList]')
.within(() => { .within(() => {
cy.get('[data-testid=instrument]').should('have.length', 4); //show only 4 instruments plus more link cy.get('div').should('have.length', 4); //show only 4 instruments plus more link
cy.contains('Acoustic Guitar: Expert'); cy.contains('Acoustic Guitar: Expert');
cy.contains('Keyboard: Expert'); cy.contains('Keyboard: Expert');
cy.contains('Ukulele: Expert'); cy.contains('Ukulele: Expert');
@ -260,13 +282,12 @@ describe('Friends page with data', () => {
//it.skip('click message button', () => {}); //it.skip('click message button', () => {});
it.skip('paginate', () => { it('paginate', () => {
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled'); cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 19; i++) { for (let i = 0; i < 19; i++) {
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click(); cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
cy.wait(500); cy.wait(500);
} }
cy.wait(500);
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled'); cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
}); });
@ -305,7 +326,7 @@ describe('Friends page with data', () => {
}); });
it('remove friend', () => { it('remove friend', () => {
cy.intercept('GET', /\S+\/profile/, { fixture: 'friend' }); cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'friend' });
cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } }); cy.intercept('DELETE', /\S+\/friends\S+/, { statusCode: 204, body: { ok: true } });
cy.visit('/friends'); cy.visit('/friends');
@ -342,11 +363,11 @@ describe('Friends page with data', () => {
cy.contains('Send a message').should('exist'); cy.contains('Send a message').should('exist');
}); });
it('is not disabled for non friends', () => { it('is disabled for non friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr') cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(1) .eq(1)
.find('[data-testid=message]') .find('[data-testid=message]')
.should('not.be.disabled'); .should('be.disabled');
//cy.contains('You can message this user once you are friends').should('exist') //cy.contains('You can message this user once you are friends').should('exist')
}); });
@ -371,10 +392,10 @@ describe('Friends page with data', () => {
messageFixtures.forEach(fixture => { messageFixtures.forEach(fixture => {
cy.fixture(fixture).then(json => { cy.fixture(fixture).then(json => {
cy.intercept('GET', /\S+\/text_messages\S+/, json); cy.intercept('GET', /\S+\/text_messages\S+/, json);
cy.get('.modal-body .ScrollbarsCustom-Scroller') cy.get('.modal-body .ScrollbarsCustom')
.trigger('mouseover') .trigger('mouseover')
.scrollTo('bottom'); .scrollTo('bottom');
cy.get('.modal-body .ScrollbarsCustom-Scroller') cy.get('.modal-body .ScrollbarsCustom')
.trigger('mouseover') .trigger('mouseover')
.scrollTo('top'); .scrollTo('top');
numberOfMessages = numberOfMessages + 10; numberOfMessages = numberOfMessages + 10;
@ -383,7 +404,7 @@ describe('Friends page with data', () => {
}); });
cy.get('button') cy.get('button')
.contains('Close') .contains('Cancel')
.should('not.be.disabled') .should('not.be.disabled')
.click(); .click();
}); });
@ -435,7 +456,7 @@ describe('Friends page with data', () => {
.click(); .click();
cy.get('[data-testid=textMessageModal]').within(() => { cy.get('[data-testid=textMessageModal]').within(() => {
cy.get('button') cy.get('button')
.contains('Close') .contains('Cancel')
.should('not.be.disabled') .should('not.be.disabled')
.click(); .click();
}); });
@ -447,31 +468,6 @@ describe('Friends page with data', () => {
}); });
}); });
describe('coming from email links', () => {
it("opens details sidebar", () => {
cy.visit('/friends?open=details&id=1');
showSidePanelContent();
});
it("opens chat window", () => {
cy.visit('/friends?open=message&id=1');
cy.get('[data-testid=textMessageModal]')
.should('be.visible')
cy.contains('Send Message to Test User1').should('exist');
});
it("sends friend request", () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.intercept('POST', /\S+\/friend_requests/, { statusCode: 201, body: { ok: true } });
cy.visit('/friends?open=connect&id=1');
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Success! Your friend request has been sent to Test User1.');
});
})
describe('filter', () => { describe('filter', () => {
const fillFilterForm = () => { const fillFilterForm = () => {
//cy.get('[data-testid=btnUpdateSearch]').click(); //cy.get('[data-testid=btnUpdateSearch]').click();
@ -606,6 +602,4 @@ describe('Friends page with data', () => {
}); });
}); });
}); });

View File

@ -0,0 +1,95 @@
/// <reference types="cypress" />
describe("Top Navigation", () => {
const showSubscribeToUpdates = () => {
cy.contains('Keep JamKazam Improving').should('exist')
cy.contains('Subscribe').should('exist')
}
const showProfileDropdown = () => {
cy.get('[data-testid=navbarTopProfileDropdown]').should('exist')
cy.contains("Peter Pan").should('exist')
//cy.contains("My Profile").should('exist')
cy.contains("Sign Out").should('exist')
}
describe("when user has not logged in", () => {
beforeEach(() => {
cy.stubUnauthenticate()
});
it('shows homepage', () => {
cy.visit('/')
cy.contains('Home').should('exist')
showSubscribeToUpdates()
})
it("not allowed to protected page", () => {
cy.visit('/friends')
cy.url().should('include', '/authentication/basic/login')
cy.contains("Sign in")
cy.get('button').should('have.text', 'Sign in')
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist')
});
})
describe("when user has logged in", () => {
beforeEach(() => {
cy.stubAuthenticate()
cy.visit('/')
});
it("shows user dropdown", () => {
showSubscribeToUpdates()
showProfileDropdown()
})
it('sign out', () => {
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Peter Pan').click()
cy.stubUnauthenticate()
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Sign Out').click()
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist')
cy.contains("Home")
})
})
describe('header notifications', () => {
beforeEach(() => {
cy.stubAuthenticate()
cy.intercept('GET', /\S+\/notifications/, { fixture: 'notifications'} )
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.visit('/')
})
it('shows notifications', () => {
cy.get('[data-testid=notificationDropdown]').should('not.be.visible')
cy.get('.notification-indicator').click()
cy.get('[data-testid=notificationDropdown]').should('be.visible')
cy.get('[data-testid=notificationDropdown] .list-group-item').should('have.length', 3)
cy.get('[data-testid=notificationDropdown]').contains('View all').click() //view all notifications
cy.url().should('include', '/notifications')
})
})
describe('locale switch', () => {
beforeEach(() => {
cy.stubAuthenticate()
cy.visit('/')
})
it("translate", () => {
cy.get('.card-header').contains('Home')
cy.get('[data-testid=langSwitch]').contains('ES').click()
cy.get('.card-header').contains('Página de inicio')
cy.get('.card-header').should('not.contain', 'Home')
cy.get('[data-testid=langSwitch]').contains('EN').click()
cy.get('.card-header').contains('Home')
cy.get('.card-header').should('not.contain', 'Página de inicio')
})
})
});

View File

@ -31,7 +31,6 @@ Cypress.Commands.add('stubAuthenticate', (attrs = {}) => {
first_name: 'Peter', first_name: 'Peter',
last_name: 'Pan', last_name: 'Pan',
name: 'Peter Pan', name: 'Peter Pan',
email: 'peter@example.com',
photo_url: '' photo_url: ''
} }
const currentUserAtrs = {...defaultAttrs, ...attrs} const currentUserAtrs = {...defaultAttrs, ...attrs}
@ -48,58 +47,3 @@ Cypress.Commands.add('stubUnauthenticate', () => {
body: {} body: {}
}).as('getUnauthenticateCurrentUser') }).as('getUnauthenticateCurrentUser')
}); });
Cypress.Commands.add('stubGonSubscriptionCodes', () => {
Cypress.on('window:before:load', win => {
win.gon = {
//session variables
global: {
subscription_codes: [
{
id: null,
name: 'Free',
price: 0,
cycle: 'month'
},
{
id: 'jamsubsilver',
name: 'Silver',
price: 4.99,
cycle: 'month'
},
{
id: 'jamsubgold',
name: 'Gold',
price: 9.99,
cycle: 'month'
},
{
id: 'jamsubplatinum',
name: 'Platinum',
price: 19.99,
cycle: 'month'
},
{
id: 'jamsubsilveryearly',
name: 'Silver',
price: 49.99,
cycle: 'year'
},
{
id: 'jamsubgoldyearly',
name: 'Gold',
price: 99.99,
cycle: 'year'
},
{
id: 'jamsubplatinumyearly',
name: 'Platinum',
price: 199.99,
cycle: 'year'
},
]
}
}
});
});

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@ -1,53 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
beforeEach(() => {
// Intercept the GET /app_features request and return an empty array
// to simulate the backend returning an empty set of app features
cy.intercept('GET', /\S+\/app_features/, {
statusCode: 200,
body: [],
}).as('getAppFeatures');
cy.intercept('GET', /\S+\/users\/\S+\/profile/, {
statusCode: 200,
body: {
id: 1,
name: 'Jane Doe',
email: 'jane@example.com',
},
}).as('getUserProfile');
cy.intercept('GET', /\S+\/users\/\S+\/my_notifications/, {
statusCode: 200,
body: [],
}).as('getMyNotifications');
cy.intercept('GET', /\S+\/users\/\S+\/friends/, {
statusCode: 200,
body: [],
}).as('getMyFriends');
});

View File

@ -1,5 +1,5 @@
// *********************************************************** // ***********************************************************
// This example support/component.js is processed and // This example support/index.js is processed and
// loaded automatically before your test files. // loaded automatically before your test files.
// //
// This is a great place to put global configuration and // This is a great place to put global configuration and
@ -19,9 +19,5 @@ import './commands'
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(<MyComponent />)

View File

@ -1,8 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}

View File

@ -13,7 +13,7 @@ const cleanCSS = require('gulp-clean-css');
-----------------------------------------------*/ -----------------------------------------------*/
gulp.task('scss', () => gulp.task('scss', () =>
gulp gulp
.src('src/assets/scss/theme.scss') .src('src/assets/scss/*.scss')
.pipe(plumber()) .pipe(plumber())
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe( .pipe(
@ -49,7 +49,7 @@ gulp.task('scss:dark', () =>
gulp.task('scss:rtl', () => gulp.task('scss:rtl', () =>
gulp gulp
.src('src/assets/scss/theme.scss') .src('src/assets/scss/*.scss')
.pipe(plumber()) .pipe(plumber())
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe( .pipe(

Some files were not shown because too many files have changed in this diff Show More