Compare commits

..

4 Commits

Author SHA1 Message Date
Seth Call c1f1a7e233 add readme 2025-02-08 23:08:05 -06:00
Seth Call 7e425471a0 comment out copy/pastable sql 2025-02-08 23:06:00 -06:00
Seth Call 7191358b8d sluggarize script 2025-02-08 23:02:32 -06:00
Seth Call 7c4f7f2042 wip 2025-02-08 22:40:18 -06:00
327 changed files with 37433 additions and 35147 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!"

View File

@ -2,7 +2,7 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
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.clear_action_items!
config.filters = true
@ -14,32 +14,18 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
filter :quarter
filter :closed
filter :paid
filter :jamtracks_sold
filter :subscriptions_count
filter :due_amount_in_cents
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
# default_actions # use this for all view/edit/delete links
column 'Year' do |oo| oo.year 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 "Tot ($)" do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end
column "Sub ($)" do |oo| sprintf("$%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f) 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 "Due (\u00A2)" do |oo| oo.due_amount_in_cents end
column 'JamTracks Sold' do |oo| oo.jamtracks_sold end
column 'Paid' 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
column 'Day' do |oo| oo.day end
column 'Partner ID' do |oo| oo.affiliate_partner.id 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 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
column 'Signups' do |oo| oo.signups end
column 'Visits' do |oo| oo.visits end
@ -33,16 +31,6 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
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

View File

@ -8,32 +8,27 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
config.filters = true
filter :affiliate_referral
filter :email
## scope("Has Signups", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') }
index do
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 'Partner ID' do |oo| oo.affiliate_referral.id 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
column 'Partner' do |oo| oo.affiliate_referral.display_name end
end
controller do
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_referral])
.order('created_at DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_referral_id = ?', ref_id]
else
qq = ['affiliate_referral_id IS NOT NULL']
def scoped_collection
rel = end_of_association_chain
.includes([:affiliate_referral])
.order('created_at DESC')
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
qq = ['affiliate_referral_id = ?', ref_id]
else
qq = ['affiliate_referral_id IS NOT NULL']
end
@users ||= rel.where(qq)
end
@users ||= rel.where(qq)
end
end
end

View File

@ -5,35 +5,19 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
config.sort_order = 'referral_user_count DESC'
config.batch_actions = false
# config.clear_action_items!
config.filters = true
config.per_page = 100
config.filters = false
config.per_page = 50
config.paginate = true
#form :partial => 'form'
#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("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Unpaid") { |partner| partner.unpaid }
controller do
helper 'active_admin/subscription'
end
form do |f|
f.inputs 'Fields' do
f.input(:partner_name, :input_html => { :maxlength => 128 })
@ -61,47 +45,14 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
column 'Code' do |oo|
oo.id
end
column 'Signups' do |oo|
column 'Referral Count' do |oo|
oo.referral_user_count
end
column 'JamTracks' do |oo|
oo.jamtracks_sold
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
column 'Earnings' do |oo|
sprintf("$%.2f", oo.cumulative_earnings_in_dollars)
end
column 'Amount Owed' do |oo|
div do
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
sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
end
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
@ -120,15 +71,6 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
row :address
row :tax_identifier
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

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]
def get_autocomplete_items(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
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]}%"])
end

View File

@ -17,14 +17,6 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
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
actions
@ -55,14 +47,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
form do |f|
f.inputs 'New Jam Track Right' do
#f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
f.input :jam_track_id_val, :required=>true, :as => :hidden
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' }
f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
f.input :user, :required=>true, collection: User.all, include_blank: false
f.input :can_download, :required => true, as: :boolean
end
f.actions
end

View File

@ -1,7 +1,5 @@
ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
collection_action :autocomplete_jam_track_name, :method => :get
menu :label => 'JamTracks', :parent => 'JamTracks'
config.sort_order = 'name_asc'
@ -21,19 +19,6 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
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
# 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.clear_action_items!
config.filters = false
config.per_page = 250
scope("Paid", default: true) { |scope| scope.unscoped.where(:origin_utm_medium => 'cpc').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') }
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
index do
column "Email" do |user|
user.email
end
column "Signup (CST)" do |user|
user.created_at.in_time_zone("Central Time (US & Canada)")
column "Bought TestDrive" do |user|
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
end
column "UTM Source" do |user|
user.origin_utm_source
@ -28,23 +25,8 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
column "UTM Campaign" do |user|
user.origin_utm_campaign
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|
user.origin_referrer
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

View File

@ -7,38 +7,6 @@ function intToIP(int) {
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(){
var $userLatenciesForm = $('form#user_latencies_form');
var $latenciesMyUser = $userLatenciesForm.find('#latencies_my_user');
@ -106,5 +74,4 @@ function handleLatencyRecommendationForm(){
$(document).ready(function() {
handleUserLatencyForm();
handleLatencyRecommendationForm();
handleJamTrackRightsForm();
});

View File

@ -2,97 +2,55 @@ class ArsesController < ApplicationController
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
def get_or_create
begin
name = params[:name]
provider = params[:provider]
active = params[:active]
beta = params.has_key?(:beta) ? params[:beta] : true
ip = params[:ip]
username = params[:username]
password = params[:password]
topology = params[:topology]
ars_id = params[:ars_id]
name = params[:name]
provider = params[:provider]
active = params[:active]
ip = params[:ip]
username = params[:username]
password = params[:password]
topology = params[:topology]
ars_id = params[:ars_id]
puts "TOPOLOGY #{topology}"
# Explicit field-based lookups
ars = nil
ars = JamRuby::Ars.find_by_id_int(ars_id) if ars_id
ars ||= JamRuby::Ars.find_by_name(name) if name
if ars.nil?
ars = JamRuby::Ars.new
ars.name = name
end
ars.id_int = ars_id if !ars_id.nil?
ars.provider = provider
ars.active = active
ars.beta = params[:beta]
ars.beta = beta
ars.ip = ip
ars.password = password
ars.username = username
if topology
ars.city = topology['city']
ars.country = topology['country']
ars.continent = topology['continent']
ars.latitude = topology['latitude']
ars.longitude = topology['longitude']
ars.subdivision = topology['subdivision']
end
ars.save!
@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
rescue => e
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
if ars_id
ars = Ars.find_by_id_int(ars_id)
end
if ars.nil?
ars = Ars.new
ars.name = name
ars.id_int = ars_id if !ars_id.nil?
end
ars.provider = provider
ars.active = active
ars.ip = ip
ars.password = password
ars.username = username
if topology
ars.city = topology['city']
ars.country = topology['country']
ars.continent = topology['continent']
ars.latitude = topology['latitude']
ars.longitude = topology['longitude']
ars.subdivision = topology['subdivision']
end
ars.save
@ars = ars
unless @ars.errors.any?
if ars_id.nil?
ars.reload
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

View File

@ -112,19 +112,6 @@ class Cohort < ActiveRecord::Base
def self.cohort_users(cohort)
User.where(created_at: cohort.group_start..cohort.group_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)
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

@ -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
def autocomplete_display_name
"#{original_artist} - #{name}"
end
end

View File

@ -20,10 +20,6 @@ JamAdmin::Application.routes.draw do
post :user_latencies, on: :collection
post :user_latency_recommendation, on: :collection
end
resources :jam_tracks do
get :autocomplete_jam_track_name, :on => :collection
end
end
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/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"

View File

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

View File

@ -1,4 +1,4 @@
image: node:14.21.3
image: node:14.17.1
pipelines:
branches:
@ -6,93 +6,33 @@ pipelines:
- step:
name: Build Staging
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
- 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_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 npm run build
artifacts:
- jam-ui/build/**
- step:
name: Deploy to staging - SPA
name: Deploy to staging
deployment: staging
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
S3_BUCKET: "jamkazam-ui/stg"
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:
name: Build Production
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
- 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_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 npm run build
artifacts:
- jam-ui/build/**
- step:
name: Deploy to production - SPA
name: Deploy to production
deployment: production
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
S3_BUCKET: "jamkazam-ui/prd"
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"
LOCAL_PATH: "jam-ui/build"

View File

@ -1,16 +1,10 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=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
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B

View File

@ -1,13 +1,8 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_BASE_URL=http://beta.jamkazam.local:4000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
REACT_APP_BITBUCKET_BUILD_NUMBER=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=
REACT_APP_ENV=development

View File

@ -1,13 +1,8 @@
HOST=beta.jamkazam.com
PORT=4000
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_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
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B

View File

@ -1,13 +1,6 @@
HOST=beta.staging.jamkazam.com
PORT=4000
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_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
REACT_APP_ENV=staging

View File

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

6
jam-ui/.gitignore vendored
View File

@ -30,8 +30,4 @@ yarn-error.log*
/test-results
/cypress/videos/
/cypress/screenshots/
/public/backing-tracks
/public/js
/cypress/screenshots/

View File

@ -1,14 +1,5 @@
# 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
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)
@ -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.
## 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,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

@ -12,17 +12,17 @@ describe('forgot password', () => {
it('redirects to forgot password page', () => {
cy.visit('/');
cy.url().should('include', '/auth/login');
cy.url().should('include', '/authentication/basic/login');
cy.get('a')
.contains('Forgot password?')
.click();
cy.url().should('include', '/auth/forget-password');
cy.url().should('include', '/authentication/basic/forget-password');
cy.get('h5').contains('Forgot Your Password');
});
describe('validate forgot password form', () => {
beforeEach(() => {
cy.visit('/auth/forget-password');
cy.visit('/authentication/basic/forget-password');
cy.get('[data-testid=email]').clear();
cy.get('[data-testid=submit]').should('be.disabled');
});
@ -31,14 +31,14 @@ describe('forgot password', () => {
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+/);
cy.url().should('not.include', /\/authentication\/basic\/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+/);
cy.url().should('not.include', /\/authentication\/basic\/confirm-mail?\S+/);
});
//valid and existing email
@ -47,7 +47,7 @@ describe('forgot password', () => {
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.url().should('match', /\S+authentication\/basic\/confirm-mail?\S+/);
cy.contains('An email has been sent to nuwan@jamkazam.com.')
});
});

View File

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

@ -1,31 +1,23 @@
/// <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 } });
beforeEach(() =>{
cy.intercept('POST', /\S+\/unsubscribe_user_match\/\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');
cy.location('pathname').should('eq', '/');
});
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');
it.only("show unsubscribed message", () => {
cy.visit('/unsubscribe?tok=123');
cy.location('search')
.should('equal', '?tok=123')
.then((s) => new URLSearchParams(s))
.invoke('get', 'tok')
.should('equal', '123')
cy.contains("successfully unsubscribed")
});
})

View File

@ -25,7 +25,7 @@ describe('Top Navigation', () => {
it('not allowed to protected page', () => {
cy.visit('/friends');
cy.wait('@getAppFeatures');
cy.url().should('include', '/auth/login');
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');

View File

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

1132
jam-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@farfetch/react-context-responsive": "^1.5.0",
"@fingerprintjs/fingerprintjs": "^4.6.0",
"@fingerprintjs/fingerprintjs": "^4.4.3",
"@fortawesome/fontawesome-free": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-brands-svg-icons": "^5.14.0",
@ -61,7 +61,6 @@
"react-dropzone": "^10.2.2",
"react-es6-progressbar.js": "^1.1.0",
"react-flatpickr": "^3.10.6",
"react-ga4": "^2.1.0",
"react-google-recaptcha": "^3.1.0",
"react-hook-form": "^7.11.1",
"react-https-redirect": "^1.1.0",
@ -107,7 +106,6 @@
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-react-hooks": "^1.7.0",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^6.1.0",
"gulp-clean-css": "^4.3.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 158 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 158 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 499 146" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g>
<g transform="matrix(1,0,0,1,-57.9807,-32.8429)">
<path d="M556.58,69.309L556.58,142.277C556.58,162.413 540.232,178.761 520.096,178.761L94.466,178.761C74.33,178.761 57.982,162.413 57.982,142.277L57.982,69.309C57.982,49.173 74.33,32.825 94.466,32.825L520.096,32.825C540.232,32.825 556.58,49.173 556.58,69.309Z" style="fill:rgb(64,124,222);stroke:rgb(64,124,222);stroke-width:1px;"/>
</g>
<g transform="matrix(0.735399,0,0,0.685712,16.2125,4.37876)">
<use xlink:href="#_Image1" x="0" y="0" width="200px" height="200px"/>
</g>
<g transform="matrix(0.835193,0,0,0.769777,7.35556,11.1156)">
<g transform="matrix(0,45.5199,41.8804,0,154.724,117.573)">
<clipPath id="_clip2">
<path d="M0.544,-0.515C0.828,-0.515 1.059,-0.285 1.059,-0C1.059,0.285 0.828,0.515 0.544,0.515C0.259,0.515 0.029,0.285 0.029,-0C0.029,-0.285 0.259,-0.515 0.544,-0.515Z" clip-rule="nonzero"/>
</clipPath>
<g clip-path="url(#_clip2)">
<g transform="matrix(-0,0.0285892,0.0285387,-0,-2.90011,-3.90471)">
<use xlink:href="#_Image3" x="118.559" y="102.621" width="37px" height="37px"/>
</g>
</g>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,147.879,151.575)">
<path d="M0,-154.033C-2.474,-154.033 -5.313,-153.848 -8.173,-153.3L-9.523,-154.65L-31.649,-132.524C-3.542,-126.838 27.743,-109.611 54.701,-82.653C80.678,-56.677 97.623,-26.68 103.909,0.617L125.571,-21.044C126.606,-27.607 126.092,-35.629 123.906,-44.621C118.523,-66.773 103.788,-91.125 83.481,-111.433C69.838,-125.077 54.383,-136.259 38.787,-143.771C24.855,-150.484 11.443,-154.033 0.004,-154.033L0,-154.033Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,166.511,149.078)">
<path d="M0,-507.546C-18,-523.546 -68,-504.459 -68,-504.459L-68,-475.459L-101,-475.459L-278.654,-297.806C-305.358,-307.529 -331.964,-304.975 -348.535,-288.403C-348.856,-288.083 -349.147,-287.739 -349.458,-287.411C-352.297,-285.827 -354.972,-283.839 -357.385,-281.425L-521.669,-117.142C-493.562,-111.455 -462.276,-94.229 -435.318,-67.271C-409.342,-41.294 -392.397,-11.297 -386.11,16L-223.034,-147.075C-223.021,-147.089 -223.009,-147.105 -222.995,-147.116C-219.827,-149.073 -216.869,-151.37 -214.186,-154.054C-196.466,-171.772 -194.776,-200.962 -206.983,-229.477L-24,-412.46L-30,-440.459L2,-446.459C2,-446.459 18,-491.546 0,-507.546" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,143.804,133.272)">
<path d="M0,480.02C6.414,473.606 15.482,472.258 21.961,472.258L21.965,472.258C40.656,472.258 62.085,482.528 79.286,499.729C93.4,513.843 103.109,531.325 105.923,547.69C107.259,555.464 108.137,569.874 98.994,579.015C92.578,585.432 83.511,586.779 77.034,586.779C65.598,586.779 53.138,582.929 41.248,576.007C59.173,571.815 68.087,566.259 71.21,557.708C74.738,548.046 69.982,536.425 55.308,518.85C48.082,510.198 37.366,505.993 22.547,505.993C14.982,505.993 7.308,507.006 -0.115,507.987C-2.572,508.311 -4.976,508.628 -7.297,508.897C-8.341,500.815 -8.253,488.273 0,480.02M881.888,28.315C881.909,27.528 881.772,26.673 881.425,25.76L881.43,25.758L864.385,-14.544C864.338,-14.654 864.29,-14.768 864.24,-14.884C863.968,-15.528 863.808,-15.906 863.786,-15.957L863.754,-15.944C863.409,-16.629 862.982,-17.33 862.412,-17.934C814.184,-120.121 738.177,-205.502 641.446,-265.845C592.524,-296.364 539.249,-319.919 483.101,-335.857C427.332,-351.688 369.766,-359.714 312.001,-359.714C152.069,-359.714 2.72,-298.442 -108.537,-187.183C-174.663,-121.058 -225.269,-38.981 -254.883,50.173C-284.492,139.309 -293.085,235.418 -279.735,328.11C-269.758,397.378 -239.036,460.967 -193.227,507.163C-169.749,530.837 -143.033,549.315 -113.818,562.083C-83.092,575.512 -50.22,582.321 -16.116,582.321C-8.628,582.321 -1.049,581.981 6.571,581.318C28.379,601.205 54.305,611.779 77.034,611.779C92.498,611.779 106.484,606.881 116.672,596.693C118.715,594.65 120.52,592.438 122.141,590.109L192.627,519.623C193.661,513.059 193.147,505.039 190.962,496.046C185.579,473.894 170.844,449.543 150.536,429.234C136.893,415.59 121.439,404.409 105.843,396.896C91.91,390.183 78.499,386.634 67.06,386.634L67.056,386.634C64.582,386.634 61.742,386.819 58.883,387.368L57.532,386.017L-12.853,456.403C-14.665,458.214 -16.23,460.178 -17.573,462.243C-17.607,462.277 -17.644,462.308 -17.678,462.342C-29.263,473.928 -33.994,490.42 -32.477,508.418C-68.221,503.794 -101.782,488.342 -130.175,463.302C-160.397,436.651 -182.628,400.421 -192.773,361.29C-215.521,273.543 -212.216,178.442 -183.218,86.269C-169.219,41.772 -149.57,-0.79 -124.817,-40.235C-99.785,-80.125 -70.023,-116.034 -36.357,-146.966C12.265,-191.639 67.591,-225.83 128.083,-248.589C185.326,-270.125 246.008,-281.045 308.442,-281.045C370.17,-281.045 431.402,-270.437 490.438,-249.517C549.446,-228.607 603.681,-198.309 651.635,-159.465C701.102,-119.396 741.967,-71.723 773.098,-17.77C806.02,39.288 827.213,102.071 836.09,168.835C843.646,225.67 841.637,283.236 830.116,339.932C818.887,395.194 798.935,448.225 770.812,497.553C742.676,546.908 707.203,591.077 665.38,628.832C622.43,667.606 573.873,698.592 521.06,720.931C457.531,747.801 386.94,762.004 316.918,762.006C270.199,762.006 223.948,755.852 179.45,743.713C126.396,729.239 76.185,706.575 30.214,676.349C22.573,671.326 15.793,665.93 9.236,660.713C-8.354,646.716 -24.969,633.497 -51.561,633.497C-51.883,633.497 -52.202,633.499 -52.526,633.502C-53.249,633.51 -53.974,633.515 -54.695,633.515C-82.262,633.515 -110.339,627.374 -138.146,615.26L-161.46,605.105L-145.145,624.611C-116.379,659.002 -83.567,690.177 -47.623,717.269C-12.579,743.682 25.619,766.355 65.91,784.656C145.235,820.686 233.265,839.732 320.48,839.734L320.491,839.734C424.057,839.734 525.681,813.159 614.362,762.886C741.202,690.982 837.576,573.648 885.73,432.499C931.153,299.356 929.635,156.816 881.888,28.315" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,-4.7583,-15.589)">
<text x="181.54px" y="81.156px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:28px;fill:white;">Download JamKazam</text>
<g transform="matrix(28,0,0,28,453.091,110.072)">
</g>
<text x="182.333px" y="110.072px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:28px;fill:white;">for Windows (Legacy)</text>
</g>
</g>
<defs>
<image id="_Image1" width="200px" height="200px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAyKADAAQAAAABAAAAyAAAAACbWz2VAAAFCElEQVR4Ae3bwU0bARiEUTaiDBqhkRQJhdAIfWzMBcvHDySLMC8nkGZZz/s1N+fhwT8CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwP8qcNzrg5/n+XR51/O93vfD3/N2HMd7+Yz8brSy383T4ZfHkP1u9GMcL9/9I7/k+b+XHq+xC78r2Ff8rk+Hn/6ErCiBOQEDmTu5wkXAQIqW7JyAgcydXOEiYCBFS3ZOwEDmTq5wETCQoiU7J2AgcydXuAgYSNGSnRMwkLmTK1wEDKRoyc4JGMjcyRUuAgZStGTnBAxk7uQKFwEDKVqycwIGMndyhYuAgRQt2TkBA5k7ucJFwECKluycgIHMnVzhImAgRUt2TsBA5k6ucBEwkKIlOydgIHMnV7gIGEjRkp0TMJC5kytcBAykaMnOCRjI3MkVLgIGUrRk5wQMZO7kChcBAylasnMCBjJ3coWLgIEULdk5AQOZO7nCRcBAipbsnICBzJ1c4SJgIEVLdk7AQOZOrnARMJCiJTsnYCBzJ1e4CBhI0ZKdEzCQuZMrXAQMpGjJzgkYyNzJFS4CBlK0ZOcEDGTu5AoXAQMpWrJzAgYyd3KFi4CBFC3ZOQEDmTu5wkXAQIqW7JyAgcydXOEiYCBFS3ZOwEDmTq5wETCQoiU7J2AgcydXuAgYSNGSnRMwkLmTK1wEDKRoyc4JGMjcyRUuAgZStGTnBAxk7uQKFwEDKVqycwIGMndyhYuAgRQt2TmB416Nz/N8urzr+V7v++HveTuO4718Rn43Wtnv5mm/ECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBdBXxZ8a7cny/LX7bzZcVPu48fst/N0+GXx5D9bvTjm7wv3/0jv+T5v5cer7ELvyvYV/yuT4ef/H+QgCW6J2AgezfXOAgYSMAS3RMwkL2baxwEDCRgie4JGMjezTUOAgYSsET3BAxk7+YaBwEDCViiewIGsndzjYOAgQQs0T0BA9m7ucZBwEACluiegIHs3VzjIGAgAUt0T8BA9m6ucRAwkIAluidgIHs31zgIGEjAEt0TMJC9m2scBAwkYInuCRjI3s01DgIGErBE9wQMZO/mGgcBAwlYonsCBrJ3c42DgIEELNE9AQPZu7nGQcBAApbonoCB7N1c4yBgIAFLdE/AQPZurnEQMJCAJbonYCB7N9c4CBhIwBLdEzCQvZtrHAQMJGCJ7gkYyN7NNQ4CBhKwRPcEDGTv5hoHAQMJWKJ7Agayd3ONg4CBBCzRPQED2bu5xkHAQAKW6J6AgezdXOMgYCABS3RPwED2bq5xEDCQgCW6J2AgezfXOAgYSMAS3RMwkL2baxwEDCRgie4JGMjezTUOAgYSsET3BAxk7+YaBwEDCViiewIGsndzjYOAgQQs0T0BA9m7ucZBwEACluiegIHs3VzjIGAgAUt0T8BA9m6ucRAwkIAluidw3KvyeZ5Pl3c93+t9P/w9b8dxvJfPyO9GK/vdPO0XAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCEwD8AEzCh1V2GrQAAAABJRU5ErkJggg=="/>
<image id="_Image3" width="37px" height="37px" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAlACUDAREAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAABQQGBwH/xAAcEAACAwEBAQEAAAAAAAAAAAAAAwQhMQECIhH/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCus9c88NgfKdtgRUl22SFSnbYEY5/1pFoEp22LAqU7bIipT9siKlO2wIxz/vQTQJTts0yKlO2yIqU/bBCpT9sCMa3vfVEWgSnbZpgVKdtgRUp+2BFSG973850ijkl3lM9WLIqUz1YETKZ6siidsk4Sf//Z"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 499 146" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g>
<g transform="matrix(1,0,0,1,-57.9807,-32.8429)">
<path d="M556.58,69.309L556.58,142.277C556.58,162.413 540.232,178.761 520.096,178.761L94.466,178.761C74.33,178.761 57.982,162.413 57.982,142.277L57.982,69.309C57.982,49.173 74.33,32.825 94.466,32.825L520.096,32.825C540.232,32.825 556.58,49.173 556.58,69.309Z" style="fill:rgb(64,124,222);stroke:rgb(64,124,222);stroke-width:1px;"/>
</g>
<g transform="matrix(0.735399,0,0,0.685712,16.2125,4.37876)">
<use xlink:href="#_Image1" x="0" y="0" width="200px" height="200px"/>
</g>
<g transform="matrix(0.835193,0,0,0.769777,7.35556,11.1156)">
<g transform="matrix(0,45.5199,41.8804,0,154.724,117.573)">
<clipPath id="_clip2">
<path d="M0.544,-0.515C0.828,-0.515 1.059,-0.285 1.059,-0C1.059,0.285 0.828,0.515 0.544,0.515C0.259,0.515 0.029,0.285 0.029,-0C0.029,-0.285 0.259,-0.515 0.544,-0.515Z" clip-rule="nonzero"/>
</clipPath>
<g clip-path="url(#_clip2)">
<g transform="matrix(-0,0.0285892,0.0285387,-0,-2.90011,-3.90471)">
<use xlink:href="#_Image3" x="118.559" y="102.621" width="37px" height="37px"/>
</g>
</g>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,147.879,151.575)">
<path d="M0,-154.033C-2.474,-154.033 -5.313,-153.848 -8.173,-153.3L-9.523,-154.65L-31.649,-132.524C-3.542,-126.838 27.743,-109.611 54.701,-82.653C80.678,-56.677 97.623,-26.68 103.909,0.617L125.571,-21.044C126.606,-27.607 126.092,-35.629 123.906,-44.621C118.523,-66.773 103.788,-91.125 83.481,-111.433C69.838,-125.077 54.383,-136.259 38.787,-143.771C24.855,-150.484 11.443,-154.033 0.004,-154.033L0,-154.033Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,166.511,149.078)">
<path d="M0,-507.546C-18,-523.546 -68,-504.459 -68,-504.459L-68,-475.459L-101,-475.459L-278.654,-297.806C-305.358,-307.529 -331.964,-304.975 -348.535,-288.403C-348.856,-288.083 -349.147,-287.739 -349.458,-287.411C-352.297,-285.827 -354.972,-283.839 -357.385,-281.425L-521.669,-117.142C-493.562,-111.455 -462.276,-94.229 -435.318,-67.271C-409.342,-41.294 -392.397,-11.297 -386.11,16L-223.034,-147.075C-223.021,-147.089 -223.009,-147.105 -222.995,-147.116C-219.827,-149.073 -216.869,-151.37 -214.186,-154.054C-196.466,-171.772 -194.776,-200.962 -206.983,-229.477L-24,-412.46L-30,-440.459L2,-446.459C2,-446.459 18,-491.546 0,-507.546" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.0344151,0,0,0.0374059,143.804,133.272)">
<path d="M0,480.02C6.414,473.606 15.482,472.258 21.961,472.258L21.965,472.258C40.656,472.258 62.085,482.528 79.286,499.729C93.4,513.843 103.109,531.325 105.923,547.69C107.259,555.464 108.137,569.874 98.994,579.015C92.578,585.432 83.511,586.779 77.034,586.779C65.598,586.779 53.138,582.929 41.248,576.007C59.173,571.815 68.087,566.259 71.21,557.708C74.738,548.046 69.982,536.425 55.308,518.85C48.082,510.198 37.366,505.993 22.547,505.993C14.982,505.993 7.308,507.006 -0.115,507.987C-2.572,508.311 -4.976,508.628 -7.297,508.897C-8.341,500.815 -8.253,488.273 0,480.02M881.888,28.315C881.909,27.528 881.772,26.673 881.425,25.76L881.43,25.758L864.385,-14.544C864.338,-14.654 864.29,-14.768 864.24,-14.884C863.968,-15.528 863.808,-15.906 863.786,-15.957L863.754,-15.944C863.409,-16.629 862.982,-17.33 862.412,-17.934C814.184,-120.121 738.177,-205.502 641.446,-265.845C592.524,-296.364 539.249,-319.919 483.101,-335.857C427.332,-351.688 369.766,-359.714 312.001,-359.714C152.069,-359.714 2.72,-298.442 -108.537,-187.183C-174.663,-121.058 -225.269,-38.981 -254.883,50.173C-284.492,139.309 -293.085,235.418 -279.735,328.11C-269.758,397.378 -239.036,460.967 -193.227,507.163C-169.749,530.837 -143.033,549.315 -113.818,562.083C-83.092,575.512 -50.22,582.321 -16.116,582.321C-8.628,582.321 -1.049,581.981 6.571,581.318C28.379,601.205 54.305,611.779 77.034,611.779C92.498,611.779 106.484,606.881 116.672,596.693C118.715,594.65 120.52,592.438 122.141,590.109L192.627,519.623C193.661,513.059 193.147,505.039 190.962,496.046C185.579,473.894 170.844,449.543 150.536,429.234C136.893,415.59 121.439,404.409 105.843,396.896C91.91,390.183 78.499,386.634 67.06,386.634L67.056,386.634C64.582,386.634 61.742,386.819 58.883,387.368L57.532,386.017L-12.853,456.403C-14.665,458.214 -16.23,460.178 -17.573,462.243C-17.607,462.277 -17.644,462.308 -17.678,462.342C-29.263,473.928 -33.994,490.42 -32.477,508.418C-68.221,503.794 -101.782,488.342 -130.175,463.302C-160.397,436.651 -182.628,400.421 -192.773,361.29C-215.521,273.543 -212.216,178.442 -183.218,86.269C-169.219,41.772 -149.57,-0.79 -124.817,-40.235C-99.785,-80.125 -70.023,-116.034 -36.357,-146.966C12.265,-191.639 67.591,-225.83 128.083,-248.589C185.326,-270.125 246.008,-281.045 308.442,-281.045C370.17,-281.045 431.402,-270.437 490.438,-249.517C549.446,-228.607 603.681,-198.309 651.635,-159.465C701.102,-119.396 741.967,-71.723 773.098,-17.77C806.02,39.288 827.213,102.071 836.09,168.835C843.646,225.67 841.637,283.236 830.116,339.932C818.887,395.194 798.935,448.225 770.812,497.553C742.676,546.908 707.203,591.077 665.38,628.832C622.43,667.606 573.873,698.592 521.06,720.931C457.531,747.801 386.94,762.004 316.918,762.006C270.199,762.006 223.948,755.852 179.45,743.713C126.396,729.239 76.185,706.575 30.214,676.349C22.573,671.326 15.793,665.93 9.236,660.713C-8.354,646.716 -24.969,633.497 -51.561,633.497C-51.883,633.497 -52.202,633.499 -52.526,633.502C-53.249,633.51 -53.974,633.515 -54.695,633.515C-82.262,633.515 -110.339,627.374 -138.146,615.26L-161.46,605.105L-145.145,624.611C-116.379,659.002 -83.567,690.177 -47.623,717.269C-12.579,743.682 25.619,766.355 65.91,784.656C145.235,820.686 233.265,839.732 320.48,839.734L320.491,839.734C424.057,839.734 525.681,813.159 614.362,762.886C741.202,690.982 837.576,573.648 885.73,432.499C931.153,299.356 929.635,156.816 881.888,28.315" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,-4.7583,-15.589)">
<text x="181.54px" y="81.156px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:28px;fill:white;">Download JamKazam</text>
<g transform="matrix(28,0,0,28,394.732,110.072)">
</g>
<text x="240.691px" y="110.072px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:28px;fill:white;">for Windows</text>
</g>
</g>
<defs>
<image id="_Image1" width="200px" height="200px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAyKADAAQAAAABAAAAyAAAAACbWz2VAAAFCElEQVR4Ae3bwU0bARiEUTaiDBqhkRQJhdAIfWzMBcvHDySLMC8nkGZZz/s1N+fhwT8CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwP8qcNzrg5/n+XR51/O93vfD3/N2HMd7+Yz8brSy383T4ZfHkP1u9GMcL9/9I7/k+b+XHq+xC78r2Ff8rk+Hn/6ErCiBOQEDmTu5wkXAQIqW7JyAgcydXOEiYCBFS3ZOwEDmTq5wETCQoiU7J2AgcydXuAgYSNGSnRMwkLmTK1wEDKRoyc4JGMjcyRUuAgZStGTnBAxk7uQKFwEDKVqycwIGMndyhYuAgRQt2TkBA5k7ucJFwECKluycgIHMnVzhImAgRUt2TsBA5k6ucBEwkKIlOydgIHMnV7gIGEjRkp0TMJC5kytcBAykaMnOCRjI3MkVLgIGUrRk5wQMZO7kChcBAylasnMCBjJ3coWLgIEULdk5AQOZO7nCRcBAipbsnICBzJ1c4SJgIEVLdk7AQOZOrnARMJCiJTsnYCBzJ1e4CBhI0ZKdEzCQuZMrXAQMpGjJzgkYyNzJFS4CBlK0ZOcEDGTu5AoXAQMpWrJzAgYyd3KFi4CBFC3ZOQEDmTu5wkXAQIqW7JyAgcydXOEiYCBFS3ZOwEDmTq5wETCQoiU7J2AgcydXuAgYSNGSnRMwkLmTK1wEDKRoyc4JGMjcyRUuAgZStGTnBAxk7uQKFwEDKVqycwIGMndyhYuAgRQt2TmB416Nz/N8urzr+V7v++HveTuO4718Rn43Wtnv5mm/ECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBdBXxZ8a7cny/LX7bzZcVPu48fst/N0+GXx5D9bvTjm7wv3/0jv+T5v5cer7ELvyvYV/yuT4ef/H+QgCW6J2AgezfXOAgYSMAS3RMwkL2baxwEDCRgie4JGMjezTUOAgYSsET3BAxk7+YaBwEDCViiewIGsndzjYOAgQQs0T0BA9m7ucZBwEACluiegIHs3VzjIGAgAUt0T8BA9m6ucRAwkIAluidgIHs31zgIGEjAEt0TMJC9m2scBAwkYInuCRjI3s01DgIGErBE9wQMZO/mGgcBAwlYonsCBrJ3c42DgIEELNE9AQPZu7nGQcBAApbonoCB7N1c4yBgIAFLdE/AQPZurnEQMJCAJbonYCB7N9c4CBhIwBLdEzCQvZtrHAQMJGCJ7gkYyN7NNQ4CBhKwRPcEDGTv5hoHAQMJWKJ7Agayd3ONg4CBBCzRPQED2bu5xkHAQAKW6J6AgezdXOMgYCABS3RPwED2bq5xEDCQgCW6J2AgezfXOAgYSMAS3RMwkL2baxwEDCRgie4JGMjezTUOAgYSsET3BAxk7+YaBwEDCViiewIGsndzjYOAgQQs0T0BA9m7ucZBwEACluiegIHs3VzjIGAgAUt0T8BA9m6ucRAwkIAluidw3KvyeZ5Pl3c93+t9P/w9b8dxvJfPyO9GK/vdPO0XAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCEwD8AEzCh1V2GrQAAAABJRU5ErkJggg=="/>
<image id="_Image3" width="37px" height="37px" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAlACUDAREAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAABQQGBwH/xAAcEAACAwEBAQEAAAAAAAAAAAAAAwQhMQECIhH/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCus9c88NgfKdtgRUl22SFSnbYEY5/1pFoEp22LAqU7bIipT9siKlO2wIxz/vQTQJTts0yKlO2yIqU/bBCpT9sCMa3vfVEWgSnbZpgVKdtgRUp+2BFSG973850ijkl3lM9WLIqUz1YETKZ6siidsk4Sf//Z"/>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<g>
<g>
<path d="M124.9,25.9L123.1,27.5L122.5,156.7L104.4,139.1C87.8,122.8 86.2,121.5 84.1,121.5C82.4,121.5 81.4,122 80.4,123.2C77.5,126.9 77.3,126.7 101.8,151.2C114.3,163.7 125.3,174.2 126.2,174.5C127.2,174.9 128.4,174.9 129.4,174.5C130.2,174.2 141.3,163.8 153.8,151.4C175.3,130.2 176.7,128.7 176.7,126.6C176.7,123.6 174.5,121.4 171.5,121.4C169.5,121.4 167.7,123 151.4,139.1L133.4,156.9L133.3,92.7C133.2,46.7 132.9,28.1 132.4,27.1C131.7,25.7 129.3,24.2 127.6,24.2C127.2,24.3 125.9,25.1 124.9,25.9Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
<path d="M11.6,147.2L10,148.8L10,227.9L13.8,231.7L127.7,231.7C213,231.7 241.9,231.5 243,230.9C246.1,229.3 246,230.3 246.1,188.6C246.1,149.6 246.1,149 244.7,147.3C242.9,145 239.5,145 237.3,147.2L235.7,148.8L235.7,221.2L20.4,221.2L20.4,185.1C20.4,149.6 20.4,149 19,147.3C17.2,145 13.8,145 11.6,147.2Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<g>
<g>
<path d="M114.7,10.6C80.5,14.5 49.1,34 29.4,63.5C25.2,69.9 18.4,83.8 15.9,91.1C8,115.2 8,140.8 16.1,165.1C33.5,217 85.2,250.8 139.3,245.4C183.9,241 223.1,210.6 238.9,168.2C246.2,148.4 248,125.3 243.7,104.4C233.7,55.6 192.3,17.2 142.7,10.9C135.6,9.9 121.7,9.8 114.7,10.6ZM140.5,19C196.1,25 237.5,71.6 237.5,128.1C237.5,165.1 219.6,198.8 189,219.3C167.9,233.4 141.1,239.8 115,236.8C77.7,232.5 46,210.1 29.6,176.5C14.7,146.2 14.8,109.6 29.7,79.3C35.3,67.7 41,59.8 50.4,50.3C67.4,33 90.5,21.8 114.9,19C120.9,18.3 134.3,18.3 140.5,19Z" style="fill:rgb(64,124,222);fill-rule:nonzero;stroke:rgb(64,124,222);stroke-width:1px;"/>
<path d="M104.3,86.7C104.1,87.1 104.1,104.9 104.1,126.3L104.3,165.2L118.9,165.2L118.9,86.3L111.7,86.1C106.3,86 104.5,86.2 104.3,86.7Z" style="fill:rgb(64,124,222);fill-rule:nonzero;stroke:rgb(64,124,222);stroke-width:1px;"/>
<path d="M136.6,87.3C136.4,88.4 136.4,149.5 136.7,162L136.8,165.5L144.3,165.3L151.7,165.1L151.7,86.3L144.3,86.1C136.9,86 136.8,86 136.6,87.3Z" style="fill:rgb(64,124,222);fill-rule:nonzero;stroke:rgb(64,124,222);stroke-width:1px;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<g>
<g>
<path d="M115,10.4C93.4,12.8 72.1,21.7 54.5,35.4C49.4,39.5 40,48.8 35.7,54.2C22.7,70.3 14.1,90.4 10.9,111.5C9.7,120.1 9.7,136 10.9,144.5C15.3,174 29.7,199.7 52.8,219C78.4,240.4 113.5,250.1 146.3,244.8C197.7,236.4 237.4,196.2 245.1,144.7C246.3,136.3 246.3,119.9 245.1,111.4C241.4,86.2 230.7,64.4 213,46.1C202.5,35.2 192.4,27.9 179.3,21.6C159.4,12 136.4,8 115,10.4ZM146,20.7C193.8,29.3 229.1,65.9 235.9,114.2C236.8,120.8 236.7,136.8 235.6,143.7C230.8,174.4 214.5,200.4 189.1,217.8C168.3,232 142.2,238.7 117.3,236.2C78.4,232.3 44.2,207.5 28.6,172C21.3,155.4 18.5,139.2 19.6,120.5C21.5,86.8 40.8,54.3 69.6,36.2C82.5,28.1 98,22.3 111.8,20.4C114.4,20 117.3,19.6 118.3,19.5C122.1,19.1 140.9,19.8 146,20.7Z" style="fill:rgb(64,124,222);fill-rule:nonzero;stroke:rgb(64,124,222);stroke-width:1px;"/>
<path d="M110,81.1C106.9,82.4 107,80.4 107,128.1L107,172.5L108.3,173.9C109.1,174.7 110.4,175.2 111.6,175.2C113.4,175.2 116.8,172.5 139.5,153.8C153.7,142 165.9,131.8 166.5,131.2C167,130.6 167.5,129.1 167.5,128.1C167.5,127 167,125.6 166.5,125C165.9,124.4 155.1,115.3 142.4,104.8C129.8,94.4 118.2,84.8 116.7,83.6C113.8,81 111.8,80.3 110,81.1ZM136.7,112.3C146.9,120.7 155.3,127.8 155.3,127.9C155.4,128.1 148,134.4 138.9,141.9C129.8,149.5 121.1,156.6 119.6,157.9L116.8,160.3L116.8,128C116.8,102.2 117,95.9 117.6,96.3C117.9,96.7 126.6,103.9 136.7,112.3Z" style="fill:rgb(64,124,222);fill-rule:nonzero;stroke:rgb(64,124,222);stroke-width:1px;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667C760,416.656 760,416.644 760,416.633C760,228.305 605.029,73.333 416.7,73.333C416.689,73.333 416.678,73.333 416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM433.333,566.667L400,566.667L400,279.433L334.067,306.267L321.5,275.4L433.333,229.867L433.333,566.667Z" style="fill:currentColor;fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-104,0)">
<path d="M433.333,566.667L400,566.667L400,279.433L334.067,306.267L321.5,275.4L433.333,229.867L433.333,566.667Z" style="fill:rgb(54,64,79);"/>
</g>
<path d="M416.667,73.333L416.7,73.333C605.029,73.333 760,228.305 760,416.633L760,416.667C760,605.014 605.014,760 416.667,760C228.32,760 73.333,605.014 73.333,416.667C73.333,228.32 228.32,73.333 416.667,73.333ZM416.667,726.667C586.659,726.484 726.484,586.659 726.667,416.667C726.667,246.606 586.728,106.667 416.667,106.667C246.606,106.667 106.667,246.606 106.667,416.667C106.667,586.728 246.606,726.667 416.667,726.667Z" style="fill:rgb(65,64,79);"/>
<g transform="matrix(1,0,0,1.04274,86,-16.8444)">
<path d="M400,237.5C436.38,237.5 466.315,255.853 486.572,290.573C503.293,319.229 512.5,358.093 512.5,400C512.5,441.904 503.293,480.765 486.572,509.424C466.315,544.147 436.38,562.5 400,562.5C363.62,562.5 333.685,544.147 313.428,509.424C296.707,480.765 287.5,441.904 287.5,400C287.5,358.093 296.707,319.229 313.428,290.573C333.685,255.853 363.62,237.5 400,237.5ZM400,537.5C460.443,537.5 487.5,468.442 487.5,400C487.5,331.558 460.443,262.5 400,262.5C339.557,262.5 312.5,331.558 312.5,400C312.5,468.442 339.557,537.5 400,537.5Z" style="fill:rgb(54,64,79);"/>
</g>
<rect x="0" y="0" width="800" height="800" style="fill:none;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667L760,416.633C760,228.305 605.029,73.333 416.7,73.333L416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM533.333,566.667L300,566.667L300,550C300,478.1 358.733,446.5 410.567,418.6C456.933,393.667 496.967,372.133 496.967,327.9C494.662,293.374 465.616,266.203 431.013,266.203C428.402,266.203 425.793,266.358 423.2,266.667C398.972,265.071 375.105,273.376 357.1,289.667C342.449,305.897 334.023,326.812 333.333,348.667L300,349C300.61,318.375 312.427,289.011 333.2,266.5C357.361,243.563 389.933,231.559 423.2,233.333C425.859,233.118 428.526,233.011 431.194,233.011C483.988,233.011 528.006,275.156 530.3,327.9C530.3,392.033 475.067,421.733 426.367,447.967C383.933,470.8 343.633,492.467 335.033,533.333L533.333,533.333L533.333,566.667Z" style="fill:rgb(54,64,79);fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667C760,416.656 760,416.644 760,416.633C760,228.305 605.029,73.333 416.7,73.333C416.689,73.333 416.678,73.333 416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM416.667,566.667C360.685,569.763 311.954,526.55 308.333,470.6L341.667,470.6C345.357,508.221 378.986,536.349 416.667,533.333C454.347,536.349 487.976,508.221 491.667,470.6C491.667,437.367 459.433,412.333 416.667,412.333L416.667,379C459.433,379 491.667,355.767 491.667,325C486.519,288.736 453.082,262.73 416.667,266.667C380.251,262.73 346.815,288.736 341.667,325L308.333,325C313.332,270.347 361.94,229.217 416.667,233.333C471.394,229.217 520.001,270.347 525,325C524.463,354.849 507.653,382.132 481.233,396.033C508.041,411.288 524.751,439.757 525,470.6C521.379,526.55 472.648,569.763 416.667,566.667Z" style="fill:rgb(54,64,79);fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667C760,416.656 760,416.644 760,416.633C760,228.305 605.029,73.333 416.7,73.333C416.689,73.333 416.678,73.333 416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM500,566.667L466.667,566.667L466.667,500L252.867,500L500,226.433L500,466.667L566.667,466.667L566.667,500L500,500L500,566.667ZM333.333,466.667L466.667,466.667L466.667,306.9L333.333,466.667Z" style="fill:rgb(54,64,79);fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667C760,416.656 760,416.644 760,416.633C760,228.305 605.029,73.333 416.7,73.333C416.689,73.333 416.678,73.333 416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM533.333,266.667L366.667,266.667L366.667,363.267C374.872,360.763 383.266,358.924 391.767,357.767C399.958,356.574 408.223,355.961 416.5,355.933C432.137,355.766 447.69,358.246 462.5,363.267C475.904,367.827 488.308,374.923 499.033,384.167C509.284,393.089 517.474,404.133 523.033,416.533C528.785,429.545 531.672,443.642 531.5,457.867C531.639,472.749 528.747,487.505 523,501.233C517.519,514.275 509.339,526.011 499,535.667C488.189,545.598 475.54,553.32 461.767,558.4C446.484,564.054 430.294,566.856 414,566.667C391.296,567.421 368.813,561.945 349,550.833C331.63,540.587 317.436,525.723 308,507.9L340.533,494.567C346.744,507.907 356.782,519.106 369.367,526.733C382.233,534.696 397.103,538.836 412.233,538.667C423.613,538.78 434.919,536.826 445.6,532.9C455.358,529.331 464.309,523.858 471.933,516.8C479.326,509.848 485.214,501.452 489.233,492.133C497.765,471.869 497.584,448.96 488.733,428.833C484.386,419.375 478.053,410.962 470.167,404.167C461.968,397.233 452.521,391.926 442.333,388.533C431.023,384.747 419.16,382.877 407.233,383C394.458,383.005 381.734,384.629 369.367,387.833C357.012,390.985 345.007,395.374 333.533,400.933L333.333,233.333L533.333,233.333L533.333,266.667Z" style="fill:rgb(54,64,79);fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M416.667,73.333C228.32,73.333 73.333,228.32 73.333,416.667C73.333,605.014 228.32,760 416.667,760C605.014,760 760,605.014 760,416.667C760,416.656 760,416.644 760,416.633C760,228.305 605.029,73.333 416.7,73.333C416.689,73.333 416.678,73.333 416.667,73.333ZM416.667,726.667C246.606,726.667 106.667,586.728 106.667,416.667C106.667,246.606 246.606,106.667 416.667,106.667C586.728,106.667 726.667,246.606 726.667,416.667C726.484,586.659 586.659,726.484 416.667,726.667ZM376,375.2L376.933,376.133C383.292,371.871 390.428,368.903 397.933,367.4C406.491,365.325 415.261,364.251 424.067,364.2C437.723,364.077 451.278,366.566 464,371.533C475.89,376.187 486.751,383.13 495.967,391.967C505.139,400.949 512.357,411.73 517.167,423.633C522.417,436.541 525.046,450.366 524.9,464.3C525.072,478.615 522.362,492.82 516.933,506.067C511.885,518.266 504.254,529.229 494.567,538.2C484.632,547.271 473.058,554.365 460.467,559.1C432.753,569.194 402.347,569.194 374.633,559.1C362.042,554.365 350.468,547.271 340.533,538.2C330.836,529.238 323.203,518.273 318.167,506.067C312.718,492.814 310.008,478.594 310.2,464.267C310.056,448.233 313.08,432.328 319.1,417.467C324.959,403.138 331.983,389.314 340.1,376.133L430.167,233.333L468.867,233.333L376,375.2ZM343.8,464.267C343.684,474.407 345.506,484.476 349.167,493.933C352.572,502.676 357.735,510.629 364.333,517.3C371.016,523.984 378.943,529.295 387.667,532.933C406.838,540.574 428.229,540.574 447.4,532.933C456.123,529.295 464.051,523.984 470.733,517.3C477.338,510.614 482.51,502.652 485.933,493.9C489.564,484.447 491.373,474.392 491.267,464.267C491.354,454.326 489.626,444.453 486.167,435.133C482.92,426.392 477.904,418.414 471.433,411.7C464.837,404.977 456.882,399.738 448.1,396.333C428.389,388.991 406.678,388.991 386.967,396.333C378.179,399.726 370.221,404.967 363.633,411.7C357.186,418.42 352.193,426.398 348.967,435.133C345.485,444.449 343.734,454.322 343.8,464.267Z" style="fill:rgb(54,64,79);fill-rule:nonzero;"/>
<rect x="0" y="0" width="800" height="800" style="fill:none;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

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