Compare commits

..

201 Commits

Author SHA1 Message Date
Seth Call dfe5facad6 ci: add simple test workflow
Test Runner / test (push) Waiting to run Details
Build Admin / build (push) Waiting to run Details
Environment Orchestrator / orchestrate (push) Waiting to run Details
2026-03-10 19:32:55 -05:00
Seth Call abae34c108 ci: switch to custom dagger runner label
Build Admin / build (push) Failing after 10s Details
Environment Orchestrator / orchestrate (push) Failing after 7s Details
2026-03-10 19:21:45 -05:00
Seth Call df8cf11e37 ci: remove paths filter to force build on mirror sync
Build Admin / build (push) Failing after 12s Details
Environment Orchestrator / orchestrate (push) Failing after 8s Details
2026-03-10 19:17:37 -05:00
Seth Call 7baf003b90 ci: touch admin file to bypass paths filter
Build Admin / build (push) Failing after 10s Details
Environment Orchestrator / orchestrate (push) Failing after 7s Details
2026-03-10 19:12:50 -05:00
Seth Call 1944602b97 ci: fix gitea token reference and checkout action url
Build Admin / build (push) Failing after 19s Details
Environment Orchestrator / orchestrate (push) Failing after 9s Details
2026-03-10 19:03:44 -05:00
Seth Call 86e151d239 ci: add workflow_dispatch to allow manual triggers on mirrors
Build Admin / build (push) Failing after 39s Details
Environment Orchestrator / orchestrate (push) Failing after 17s Details
2026-03-10 18:57:08 -05:00
Seth Call 6a1506bbe8 ci: add action workflow to develop to enable branch triggers
Build Admin / build (push) Failing after 46s Details
Environment Orchestrator / orchestrate (push) Failing after 18s Details
2026-03-10 18:37:32 -05:00
Seth Call 64a93dd42f support beta 2026-02-28 20:39:34 -06:00
Seth Call eb52813822 defaut beta to true 2026-02-21 08:56:52 -06:00
Seth Call 8613a03d00 ok fix the API more 2026-02-16 09:05:12 -06:00
Seth Call 8f0b8929ba ars apis 2026-02-15 22:04:17 -06:00
Seth Call 08e1c5274d track utm_id 2026-02-04 21:53:19 -06:00
Seth Call 5f347ccfac handle All 2026-02-04 21:31:23 -06:00
Seth Call ca2bf19b7a cleanup query 2026-02-04 20:58:16 -06:00
Seth Call fad1f9d6d9 Add paid = cpc on jammers cohots 2026-02-04 19:53:00 -06:00
Seth Call 51838fb413 Remove test drive 2026-02-03 19:05:12 -06:00
Seth Call 45cb401112 Controls for user source 2026-02-01 14:15:41 -06:00
Seth Call 9078515984 user source update 2026-01-31 12:20:37 -06:00
Seth Call 52ae83e2f4 Addtocart instead 2026-01-31 12:15:26 -06:00
Seth Call b454cf9ead AddToCart 2026-01-28 20:04:40 -06:00
Seth Call 65f5624ff9 add array support for facebook ad source 2026-01-24 00:02:44 -06:00
Seth Call e82450dfe9 Support more utm tracking 2026-01-23 23:17:35 -06:00
Seth Call f1992eaa78 build this 2026-01-22 22:36:22 -06:00
Seth Call bdeecc76c6 fix reporting 2026-01-22 21:21:17 -06:00
Seth Call 8c8024c12b event test code 2026-01-19 13:48:26 -06:00
Seth Call c183af3d6b remove rails logger ref 2026-01-19 13:21:26 -06:00
Seth Call ab424c21d2 use puts 2026-01-19 12:49:37 -06:00
Seth Call 753f35b24d click signup redirects to the right place in jam-ui 2026-01-19 12:34:30 -06:00
Seth Call 624853c868 Store UTM cookies 2026-01-16 22:15:36 -06:00
Seth Call 71d8571eb9 JKLayout adding Meta Tracking 2026-01-16 16:44:46 -06:00
Seth Call fae16c7483 better logging on CAPI 2026-01-15 08:00:34 -06:00
Seth Call 253a6c566b Bettor logging 2026-01-15 07:59:27 -06:00
Seth Call ed561be4b5 meta_tracking included 2026-01-14 19:56:42 -06:00
Seth Call eb69640667 fix utm source 2026-01-14 14:54:56 -06:00
Seth Call 9b0a9d1f32 cleanup around app-config 2026-01-14 14:42:22 -06:00
Seth Call e1cc5483a2 facebook pixel id and access token 2026-01-14 12:17:39 -06:00
Seth Call 73d2a7a020 more changes for event rework 2026-01-14 10:30:33 -06:00
Seth Call eb298d6859 fix 2026-01-14 09:20:57 -06:00
Seth Call 93c4154648 add capi transmitter and missing event type 2026-01-14 08:58:37 -06:00
Seth Call fbd871d204 reviewing 2026-01-14 08:52:19 -06:00
Seth Call 0d82f6ee16 allow video tokens to last a long time 2026-01-08 06:03:58 -06:00
Seth Call 1279b16ec0 Update manifest maker 2025-10-26 14:33:00 -05:00
Seth Call e3cff0a825 Uncomment TrialReminders 2025-10-18 17:40:46 -05:00
Nuwan Chaturanga 6a6e4cde09 Merged in fix_plg_email_delivery_timeing_issue (pull request #70)
fix PLG email timing

Approved-by: Seth Call
2025-10-17 13:16:50 +00:00
Seth Call 1dd15fb0aa Add tests for all PLG emails 2025-10-17 08:15:46 -05:00
Seth Call 9282369e54 VRFS-5690 - tag jamtrack and jammers separetly on admin report - fix jammers view 2025-10-13 15:23:01 -05:00
Seth Call 828191d683 VRFS-5690 - tag jamtrack and jammers separetly on admin report - fix jammers view 2025-10-13 15:16:41 -05:00
Seth Call c0031cfe3d VRFS-5690 - tag jamtrack and jammers separetly on admin report 2025-10-13 14:33:45 -05:00
Seth Call 9b17546082 omit trial send reminders for now 2025-10-13 12:02:44 -05:00
Seth Call eed3d51f4b VRFS-5691 - fix both the jamtrack flow and the inability to sign out - take 2 2025-10-13 11:44:17 -05:00
Seth Call 4eac68b645 VRFS-5691 - fix both the jamtrack flow and the inability to sign out 2025-10-12 11:55:50 -05:00
Nuwan fe6157e8cf ensure not to send email 2 and 3 tighltly behind email 1 in this sequence 2025-09-30 13:14:39 +05:30
Nuwan 675bf2b69c fix PLG email timing
do not send emails if the date has passed
2025-09-22 00:07:16 +05:30
Nuwan 4ffc0d9b3b Merge branch 'develop' of ssh://altssh.bitbucket.org:443/jamkazam/jam-cloud into develop 2025-08-20 20:36:18 +05:30
Nuwan 7b665325f7 unsubscribe/change email confirmation fixes 2025-08-20 20:34:52 +05:30
Nuwan eab17b3340 uncomment the lines
uncomment the lines which were disabled for debug purposes in the
prev. commit
2025-08-20 20:34:52 +05:30
Nuwan 4fafe30141 add more changes which were missed in prev. commit 2025-08-20 20:34:52 +05:30
Nuwan 7f85c91601 trail end reminderd
send emails when the trail perieod expired.
2025-08-20 20:34:52 +05:30
Nuwan d424026f17 fix payment method page element loading
this resolves the race condition issue when loading recurly payment element
and hence sometimes it wasn't showing
2025-08-20 20:31:44 +05:30
Nuwan Chaturanga a4f8935b3a Merged in fix_403_errors_in_public_pages (pull request #69)
unsubscribe/change email confirmation fixes

Approved-by: Seth Call
2025-08-19 16:19:12 +00:00
Nuwan Chaturanga 4f837ae67f Merged in 5662-trial_end_reminder_emails (pull request #68)
trail end reminderd

Approved-by: Seth Call
2025-08-19 16:07:57 +00:00
Nuwan 0053775c7e uncomment the lines
uncomment the lines which were disabled for debug purposes in the
prev. commit
2025-08-19 21:04:28 +05:30
Nuwan 9d6c71829f unsubscribe/change email confirmation fixes 2025-08-19 20:39:39 +05:30
Nuwan d00a0c08f7 add more changes which were missed in prev. commit 2025-08-19 02:13:16 +05:30
Nuwan 7e2c917ca0 trail end reminderd
send emails when the trail perieod expired.
2025-08-19 00:07:09 +05:30
Nuwan Chaturanga a84a55f178 Merged in 5661-PLG-play-with-others (pull request #67)
PLG email for 2+ session

Approved-by: Seth Call
2025-08-16 23:50:48 +00:00
Nuwan 25ecab2c65 fix billing details + card data processing
remove saving billing address details to database in the first place
before sending to recurly. Now the backend handles address data saving
after success response from recurly.
2025-08-15 18:31:03 +05:30
Nuwan b2f344fd30 PLG email for 2+ session
email reminders for the users to have 2+ session
VRFS-5661
2025-08-14 22:06:18 +05:30
Nuwan 3bba9ec619 fix error in user_observer 2025-08-13 14:37:31 +05:30
Nuwan 86e03e0ba7 change email confirm page 2025-08-13 14:26:58 +05:30
Nuwan 67fd15c75c wip after changing email show confirmation page within new website 2025-08-13 13:51:54 +05:30
Nuwan b7a41c6465 unsubscribe page
add page in new website to be shown to the users
when they follow unsubscribe link (in email footer)
2025-08-06 20:30:12 +05:30
Nuwan a8d5b8e735 update in the front end showing user payment details persistance 2025-07-29 22:37:54 +05:30
Nuwan f977b7298e fix error when showing if user has stored card
fix the issue of showing this information incorrectly on payment method
page. also this comment disables front end validation of card details
2025-07-28 18:47:02 +05:30
Seth Call 9a41e8a236 Fix cutoff_date not being passed in and requirees of TestGearSetup 2025-07-25 20:06:47 -05:00
Nuwan Chaturanga 3d113e3877 Merged in 5646-test_session_reminder (pull request #66)
PLG feature for Test Session reminder emails

* PLG feature for Test Session reminder emails

Implement the Test Session reminder emails VRFS-5646

* cutoff date for test gear reminder email

add cutoff date config to limit the selection after a certain date
add batch_size:100 to loops


Approved-by: Seth Call
2025-07-25 23:13:21 +00:00
Nuwan Chaturanga c7e80a0694 Merged in 5645-payment_method_page (pull request #65)
5645 payment method page

* wip payment method in new site

* payment method page

new page to add user's payment method (credit card / paypal) alone
with billing address details

* Update recurly/braintree tokens


Approved-by: Seth Call
2025-07-24 03:25:55 +00:00
Nuwan 9058c8af1d load jamserver.js
instead of jamserver.js instead of jamserver_copy.js when loading
react.js front end. jamserver_copy for some reason desn not load
all of the javascripts needed to get for example subscription messages
to be captured in the front end.
2025-07-18 19:38:14 +05:30
Seth Call 08008a6dc3 Fix the determination about profile_completed_at 2025-06-23 19:56:36 -05:00
Seth Call 8645328cce Oops. missed images for obs plugin site 2025-06-15 15:18:37 -05:00
Seth Call 38388d2afb Unblock pkg uploading in admin web 2025-06-15 13:13:05 -05:00
Seth Call e4d5fccdf5 Merged in seth/obs-download (pull request #63)
Seth/obs download

* wip

* Merge branch 'develop' into seth/obs-download

* Working. Waiting on final UI approval

* Merge branch 'develop' into seth/obs-download
2025-06-15 00:44:11 +00:00
Seth Call 5f3a327d35 Enable send reminders code, because saving profile_completed_at now 2025-06-14 17:37:34 -05:00
Seth Call 51ed748013 DB updates after actually updating prod 2025-06-14 10:02:18 -05:00
Seth Call dcdf9e55a3 Fix develop 2025-06-14 01:14:35 -05:00
Seth Call 7c9e449c4b Hookup gear reminder emails 2025-06-14 00:33:03 -05:00
Seth Call e7923dca9b Updated both background jobs to not break HourlyJob 2025-06-14 00:22:02 -05:00
Nuwan Chaturanga 55372bf83d Merged in 5631-signup-survey (pull request #61)
signup survey email sending

* signup survey email sending

Send new user survey email 24 hours after signup to all new users

* add config parameters

add config.signup_survey_url, config.signup_survey_cutoff_date
2025-06-13 12:02:21 +00:00
Seth Call 0f556bfad4 Only allow stopping a recording if you are the owner 2025-06-11 22:00:42 -05:00
Nuwan 817719d539 show pointer cursor on download app links 2025-05-29 16:15:43 +05:30
Nuwan cd229a0b42 fix legacy app client download links 2025-05-29 15:52:31 +05:30
Nuwan b6e2fe7494 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-05-27 06:54:49 +05:30
Nuwan ad1d8ea373 client downloads - fix download link for legacy clients 2025-05-27 06:54:27 +05:30
Nuwan Chaturanga 2d5d93787f Merged in 5539-gear_setup_reminders (pull request #59)
gear setup reminder emails

* gear setup reminder emails

email reminders to setup audio gear


Approved-by: Seth Call
2025-05-23 12:53:49 +00:00
Nuwan 676cbaa656 include subscription_utils script 2025-05-23 16:19:18 +05:30
Nuwan d4e0e41186 fix duplicate sessions in browse sessions page 2025-05-22 19:17:21 +05:30
Nuwan 49d3e2a4ac list sessions according to the fetch status 2025-05-21 13:56:49 +05:30
Nuwan 97d23a6bcb do not show upcoming events in event listing 2025-05-21 12:07:16 +05:30
Nuwan 0f7b9b2884 fix null value error in session list 2025-05-20 18:27:43 +05:30
Nuwan 0d25814e6e list current sessions 2025-05-20 14:24:28 +05:30
Nuwan 1e0faa7309 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-05-19 00:41:21 +05:30
Nuwan 10e6fedb5c revert to list all sesions 2025-05-19 00:40:53 +05:30
Nuwan d6dc79b478 fix data reference error 2025-05-19 00:40:40 +05:30
Nuwan 1071dec044 revert to list all sesions 2025-05-18 20:23:11 +05:30
Nuwan 403a830157 list friends, public and inactive sessions in browse session page 2025-05-18 19:54:12 +05:30
Nuwan Chaturanga c3bd62c0cc Merged in 5534-profile-reminder-emails (pull request #58)
5534 profile reminder emails

* Profile prompts & reminders

3 email reminders to for new users who have not completed their
jamkazam profile

* PR change requests. moved email sernding job to hourly job tasks. and add database table index


Approved-by: Seth Call
2025-05-13 12:02:46 +00:00
Nuwan Chaturanga 71716b2240 Merged in 5534-profile-reminder-emails (pull request #54)
Profile prompts & reminders

* Profile prompts & reminders

3 email reminders to for new users who have not completed their
jamkazam profile

* PR change requests. moved email sernding job to hourly job tasks. and add database table index


Approved-by: Seth Call
2025-04-05 20:18:38 +00:00
Seth Call 3a6d86e23c Fix real session check 2025-04-01 20:15:23 -05:00
Seth Call d1b9c8b19a Fix issue with actually saving the jamtrack right 2025-03-30 10:15:33 -05:00
Seth Call 615158baf2 only search on email when it's clear user is putting in an email 2025-03-30 08:49:28 -05:00
Seth Call a392d59df4 limit users autocomplete to 40 2025-03-29 11:35:27 -05:00
Seth Call 8bfb339fd5 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-03-28 23:27:35 -05:00
Seth Call 15f9beecf6 Fix jamtrack right field 2025-03-28 23:27:27 -05:00
Nuwan fcc49c52a1 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-03-27 12:25:57 +05:30
Seth Call e9d2ed7617 Ooops, missed brace 2025-03-27 12:25:28 +05:30
Seth Call 685a566c85 Stop double-counting page vies 2025-03-27 12:25:28 +05:30
Seth Call 4c95ad58ac Remove extra pageView hit 2025-03-27 12:25:28 +05:30
Seth Call 8519b56c5a Remove public URL from router basename 2025-03-27 12:25:28 +05:30
Seth Call 62d66fac75 Add GA to landing pages and fix build constant 2025-03-27 12:25:28 +05:30
Seth Call f4b1ab59e0 Cleaned up BrowserRouter, and added GA 2025-03-27 12:25:28 +05:30
Seth Call 0b60c8d0cc Load iframe later 2025-03-27 12:25:28 +05:30
Seth Call d4fcb0c19f fix signup path 2025-03-27 12:25:28 +05:30
Seth Call 98b70724a4 sitemap location fix 2025-03-27 12:25:28 +05:30
Seth Call 8f9f7d90fa Convert large pngs to webp, and create sitemap.xml 2025-03-27 12:25:28 +05:30
Seth Call 81627fc37f Add some useful filters 2025-03-27 12:25:28 +05:30
Seth Call 49888b9b5b Update indexs and speed up partner page 2025-03-27 12:25:28 +05:30
Seth Call b2c2129d42 Better reporting for quarterly payments 2025-03-27 12:25:28 +05:30
Seth Call 35a5f9f17c Add more partner fields to affiliate_traffic_total 2025-03-27 12:25:28 +05:30
Seth Call b942265652 Update Referral Admin-web page to be more useful 2025-03-27 12:25:28 +05:30
Seth Call b657511310 Revert "Update Referrals page to list more partner info"
This reverts commit a2c607b449.
2025-03-27 12:25:28 +05:30
Seth Call a687356d72 Update Referrals page to list more partner info 2025-03-27 12:25:28 +05:30
Seth Call f4c71f9778 Why can't I figure this PUBLIC_URL process env out 2025-03-27 12:25:28 +05:30
Seth Call 1120d8be31 add public url 2025-03-27 12:25:28 +05:30
Seth Call 5fba0f8d97 Fix base url 2025-03-27 12:25:28 +05:30
Seth Call ab1575365d Re-attempt build with correct URL 2025-03-27 12:25:28 +05:30
Nuwan 3369b39341 fix error on paypal redirect after checkout 2025-03-26 16:53:35 +05:30
Seth Call f5b090a20b Ooops, missed brace 2025-03-25 07:14:10 -05:00
Seth Call cc664889f8 Stop double-counting page vies 2025-03-25 07:03:51 -05:00
Seth Call cfe0129a6d Remove extra pageView hit 2025-03-24 23:06:45 -05:00
Seth Call a525082f26 Remove public URL from router basename 2025-03-24 22:46:49 -05:00
Seth Call 7f5b6152f0 Add GA to landing pages and fix build constant 2025-03-24 22:01:49 -05:00
Seth Call 0df9beb786 Cleaned up BrowserRouter, and added GA 2025-03-24 21:36:28 -05:00
Seth Call 85c0c5812f Load iframe later 2025-03-23 22:27:27 -05:00
Seth Call 4ede79ba51 fix signup path 2025-03-23 22:12:27 -05:00
Seth Call 155ee69863 sitemap location fix 2025-03-23 21:59:18 -05:00
Seth Call 8d49ea0368 Convert large pngs to webp, and create sitemap.xml 2025-03-23 21:36:36 -05:00
Seth Call 93dc5ccd35 Add some useful filters 2025-03-23 15:38:02 -05:00
Seth Call 7b6a571550 Update indexs and speed up partner page 2025-03-23 15:05:41 -05:00
Seth Call 0db6bac749 Better reporting for quarterly payments 2025-03-23 13:54:53 -05:00
Seth Call c23305634f Add more partner fields to affiliate_traffic_total 2025-03-22 09:41:49 -05:00
Nuwan 481f8099af incorporate paypal checking out
change paypal checkout and confirm screens load inside new website
and not redirecting users to old site.
2025-03-22 20:08:51 +05:30
Seth Call 23ce65d0bd Update Referral Admin-web page to be more useful 2025-03-21 19:05:07 -05:00
Seth Call ce1c4673b2 Revert "Update Referrals page to list more partner info"
This reverts commit a2c607b449.
2025-03-21 19:03:49 -05:00
Seth Call a2c607b449 Update Referrals page to list more partner info 2025-03-21 18:45:29 -05:00
Seth Call e4f6444d35 Why can't I figure this PUBLIC_URL process env out 2025-03-19 19:34:42 -06:00
Seth Call dbdc5e5296 add public url 2025-03-19 19:22:48 -06:00
Seth Call 3b45014af0 Fix base url 2025-03-19 19:07:19 -06:00
Seth Call 55c532b44f Re-attempt build with correct URL 2025-03-19 18:53:54 -06:00
Nuwan c73e78d6e6 fix checkout total not showing correctly 2025-03-13 12:17:00 +05:30
Nuwan 488aa4c03d fix showing 0.00 for cart total 2025-03-12 11:06:01 +05:30
Seth Call 2cc56f7ad7 Fix fee in cents 2025-03-10 23:24:59 -05:00
Seth Call e6046f74b6 Merged in feature/affiliate-testing (pull request #56)
Update signups aggressively as they occur (realtime), and fix some affiliate and jamtrack flows
2025-03-10 20:38:02 +00:00
Seth Call aa9555aa1e Update signups aggressively as they occur (realtime), and fix some affiliate and jamtrack flows 2025-03-10 15:36:25 -05:00
Seth Call 39dfbadbd8 Add affiliate code to backing track pages 2025-03-05 07:46:17 -06:00
Seth Call b7732c11f8 Try force full page redirect of marketing site 2025-03-04 21:46:52 -06:00
Seth Call 7faac7b61b Back to profile 2025-03-04 21:35:08 -06:00
Seth Call 98cae96d5d logout go to marketing site 2025-03-04 21:30:56 -06:00
Seth Call 0f30107481 Revert back the REACT_APP_BASE_URL 2025-03-04 21:05:49 -06:00
Seth Call 42c77a8049 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-03-04 21:02:36 -06:00
Seth Call 69f4bc837a Profile default on logout 2025-03-04 21:02:01 -06:00
Nuwan 89411738cb save affiliate_visitor cookie when there is affiliate param when accessing pages 2025-03-04 23:40:08 +05:30
Seth Call 9d7fea284a Redirect to profile 2025-03-04 08:26:54 -06:00
Seth Call 198adfe395 Make /profile be the default location 2025-03-04 07:36:39 -06:00
Seth Call 383d9deeb3 Fix welcome email links 2025-03-04 07:04:13 -06:00
Seth Call 7945c52cbb Fix links for knowledge-base, help-desk, and forum 2025-03-03 22:55:30 -06:00
Seth Call 9e84bcc0df Fix move of domains 2025-03-03 20:10:02 -06:00
Seth Call 6007abd6a8 Fix broken download links to bad /downloads 2025-03-03 19:58:14 -06:00
Seth Call be5a127b4c Fix bad REACT_ vars in backing-track pages 2025-03-03 19:48:25 -06:00
Nuwan 93a824501b Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2025-03-03 22:32:39 +05:30
Seth Call 3309c7f69a Guard production pushes behind manual step 2025-03-03 22:31:38 +05:30
Seth Call b04bb0c55b Merged in VRFS-5512-jamtrack-landing-pages-2 (pull request #55)
Getting in the artist pages

* Add in artist pages

* Update bitbucket yml to push on both develop
2025-03-03 22:31:38 +05:30
Nuwan 8cafb23b3e change authenticate routes
authentication related routes are scoped in /auth/... namespace with was
earlier /authntication/...
2025-03-03 22:27:43 +05:30
Seth Call 2c76628926 Guard production pushes behind manual step 2025-03-03 07:25:01 -06:00
Seth Call b678bfd70d Merged in VRFS-5512-jamtrack-landing-pages-2 (pull request #55)
Getting in the artist pages

* Add in artist pages

* Update bitbucket yml to push on both develop
2025-03-03 13:18:54 +00:00
Seth Call 9d63882e69 Merged in seth/VRFS-5512-jamtrack-landing-pages (pull request #53)
jamtrack landing pages
2025-02-28 03:03:22 +00:00
Seth Call d4546c6975 MR is ready for review 2025-02-27 07:55:08 -06:00
Seth Call a9b9e592bb resolved 2025-02-27 07:47:12 -06:00
Nuwan dcb842035f fix removing all shopping cart items when removing one item 2025-02-22 02:11:58 +05:30
Nuwan 9c0e643b23 prevent user avatar refresh
prevent referesing the user avatar on every page navigation
2025-02-22 01:47:26 +05:30
Nuwan 94747d9fe0 fix autocomplete artist search for affiliate links 2025-02-21 18:45:52 +05:30
Nuwan 81cf0dadb0 change affiliate links format 2025-02-20 19:19:27 +05:30
Nuwan 254d1ecac7 fix reset password email link 2025-02-14 19:44:20 +05:30
Nuwan 754be7877e remove showing audioUrl 2025-02-14 19:07:06 +05:30
Nuwan 8a8f1d14f9 fix fingerprint loading issue in jamtrack page 2025-02-14 17:00:42 +05:30
Nuwan bd2d9410b5 fix jamtrack autocomplete component
add debouncing technique to fetch data when fetching the data
move the FingerprintJS initialization to the appDataContext so it is not
instantiate eveytime
2025-02-13 17:52:14 +05:30
Nuwan 1e623e77de affiliate links fix artist_name_slug 2025-02-12 15:07:57 +05:30
Nuwan a0b255a5d5 change to affiliate link format
affiliate links points to the landing page and they have now
name_slug and original_artist_slug embeded
2025-02-12 07:53:10 +05:30
Nuwan 342960e57b refactor jamtracks loading
reduce the nertwork calls it had when loading jamtracks on
jamtacks listing which increases the loading time.
2025-02-12 06:14:12 +05:30
Nuwan 36a184638f remove unwanted web/venv/ directory which was added mistakenly 2025-02-09 22:57:29 +05:30
Nuwan 1ce4d4f4e6 rename the migration files to comply with the order and naming 2025-02-09 21:07:54 +05:30
Seth Call 810ae15769 Merged in mc/sluggarize-fixes (pull request #52)
Update sluggarization and sluggarize existing jamtracks

* wip

* sluggarize script

* comment out copy/pastable sql

* add readme
2025-02-09 05:09:54 +00:00
327 changed files with 35144 additions and 37430 deletions

View File

@ -0,0 +1,25 @@
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,8 @@
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 = 'due_amount_in_cents DESC'
config.sort_order = 'year desc, quarter desc, due_amount_in_cents desc'
config.batch_actions = false
config.clear_action_items!
config.filters = true
@ -14,18 +14,32 @@ 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 "Due (\u00A2)" do |oo| oo.due_amount_in_cents end
column 'JamTracks Sold' do |oo| oo.jamtracks_sold 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 'Paid' do |oo| oo.paid end
column 'Closed' do |oo| oo.paid end

View File

@ -23,7 +23,9 @@ 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' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) 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 'Signups' do |oo| oo.signups end
column 'Visits' do |oo| oo.visits end
@ -31,6 +33,16 @@ 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,27 +8,32 @@ 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 'Email' do |oo| oo.email end
column 'User Email' do |oo| oo.email end
column 'Created' do |oo| oo.created_at end
column 'Partner' do |oo| oo.affiliate_referral.display_name 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
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']
end
@users ||= rel.where(qq)
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
end
end

View File

@ -5,19 +5,35 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
config.sort_order = 'referral_user_count DESC'
config.batch_actions = false
# config.clear_action_items!
config.filters = false
config.per_page = 50
config.filters = true
config.per_page = 100
config.paginate = true
#form :partial => 'form'
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
#filter :partner_user
filter :partner_name
filter :id
filter :current_quarter_in_cents
filter :cumulative_earnings_in_cents
filter :jamtracks_sold
filter :subscriptions_count
filter :referral_user_count
scope("Sorted By Current Quarter", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('current_quarter_in_cents desc') }
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('jamtracks_sold desc') }
scope("Sorted By Subs", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('subscriptions_count desc') }
scope("Sorted By Signups", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Sorted By Newest First") { |scope| scope.where('partner_user_id IS NOT NULL').order('id desc') }
scope("Any") { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
scope("Unpaid") { |partner| partner.unpaid }
controller do
helper 'active_admin/subscription'
end
form do |f|
f.inputs 'Fields' do
f.input(:partner_name, :input_html => { :maxlength => 128 })
@ -45,14 +61,47 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
column 'Code' do |oo|
oo.id
end
column 'Referral Count' do |oo|
column 'Signups' do |oo|
oo.referral_user_count
end
column 'Earnings' do |oo|
sprintf("$%.2f", oo.cumulative_earnings_in_dollars)
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
end
column 'Amount Owed' do |oo|
sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
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
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
@ -71,6 +120,15 @@ 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,7 +640,12 @@ 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)
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]}%"])
term = parameters[:term]
if term.include?('@')
User.select("email, first_name, last_name, id, last_jam_addr").where(["email = ?", term]).limit(5)
else
User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{term}%", "%#{term}%", "%#{term}%"]).limit(40)
end
end

View File

View File

@ -17,6 +17,14 @@ 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
@ -47,9 +55,14 @@ 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 :user, :required=>true, collection: User.all, include_blank: false
f.input :can_download, :required => true, as: :boolean
#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' }
end
f.actions
end

View File

@ -1,5 +1,7 @@
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'
@ -19,6 +21,19 @@ 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

@ -0,0 +1,185 @@
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

@ -0,0 +1,73 @@
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

@ -1,41 +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
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,15 +6,18 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
config.batch_actions = false
config.clear_action_items!
config.filters = false
config.per_page = 250
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
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') }
index do
column "Email" do |user|
user.email
end
column "Bought TestDrive" do |user|
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
column "Signup (CST)" do |user|
user.created_at.in_time_zone("Central Time (US & Canada)")
end
column "UTM Source" do |user|
user.origin_utm_source
@ -25,8 +28,23 @@ 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,6 +7,38 @@ 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');
@ -74,4 +106,5 @@ function handleLatencyRecommendationForm(){
$(document).ready(function() {
handleUserLatencyForm();
handleLatencyRecommendationForm();
handleJamTrackRightsForm();
});

View File

@ -2,55 +2,97 @@ class ArsesController < ApplicationController
respond_to :json
# create or update a client_artifact row
def get_or_create
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}"
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?
def index
if params[:code] != Rails.application.config.data_dump_code
render :json => {error: "Unauthorized"}, :status => 401
return
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
@arses = JamRuby::Ars.all
render :json => @arses
end
@ars = ars
unless @ars.errors.any?
if ars_id.nil?
ars.reload
ars_id = ars.id_int
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
@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
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]
# 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
end
end
end

View File

@ -112,6 +112,19 @@ 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)

1
admin/bin/start_mx Executable file
View File

@ -0,0 +1 @@
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

@ -0,0 +1,18 @@
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,4 +3,8 @@ class JamRuby::JamTrack
# add a custom validation
def autocomplete_display_name
"#{original_artist} - #{name}"
end
end

View File

@ -20,6 +20,10 @@ 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
@ -40,6 +44,8 @@ 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"

1
admin/trigger.txt Normal file
View File

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

View File

@ -1,4 +1,4 @@
image: node:14.17.1
image: node:14.21.3
pipelines:
branches:
@ -6,33 +6,93 @@ 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
- 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
- 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
artifacts:
- jam-ui/build/**
- step:
name: Deploy to staging
deployment: staging
name: Deploy to staging - SPA
script:
- pipe: atlassian/aws-s3-deploy:1.1.0
- pipe: atlassian/aws-s3-deploy:1.6.2
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
- 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
- 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
artifacts:
- jam-ui/build/**
- step:
name: Deploy to production
deployment: production
name: Deploy to production - SPA
script:
- pipe: atlassian/aws-s3-deploy:1.1.0
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd"
LOCAL_PATH: "jam-ui/build"
LOCAL_PATH: "jam-ui/build"
EXTRA_ARGS: "--exclude=*backing-tracks/*"
- step:
name: Deploy to production - backing-tracks
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/backing-tracks"
LOCAL_PATH: "jam-ui/build/backing-tracks"
EXTRA_ARGS: "--exclude=*.js --content-type text/html"
- step:
name: Deploy to production - backing-tracks js
script:
- pipe: atlassian/aws-s3-deploy:1.6.2
variables:
S3_BUCKET: "jamkazam-ui/prd/js"
LOCAL_PATH: "jam-ui/build/js"
EXTRA_ARGS: "--content-type text/javascript"
#- step:
# name: Deploy to production - invalidate cloudfront distribution
# deployment: production
# script:
# - pipe: atlassian/aws-cloudfront-invalidate:0.10.1
# variables:
# DISTRIBUTION_ID: "ESQDIABYLT0RV"

View File

@ -1,10 +1,16 @@
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_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
PUBLIC_URL=
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -1,8 +1,13 @@
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_ENV=development
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
REACT_APP_RECURLY_PUBLIC_API_KEY=
REACT_APP_BRAINTREE_TOKEN=

View File

@ -1,8 +1,13 @@
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_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh

View File

@ -1,6 +1,13 @@
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_ENV=staging
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

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

6
jam-ui/.gitignore vendored
View File

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

View File

@ -1,5 +1,14 @@
# 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)
@ -29,3 +38,19 @@ 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)

6
jam-ui/cicd/.gitignore vendored Normal file
View File

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

1
jam-ui/cicd/.nvmrc Normal file
View File

@ -0,0 +1 @@
22

View File

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

View File

@ -0,0 +1,29 @@
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

@ -0,0 +1,25 @@
#!/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

@ -0,0 +1,39 @@
#!/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"

337
jam-ui/cicd/generate.js Normal file
View File

@ -0,0 +1,337 @@
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);

40
jam-ui/cicd/generate.sh Executable file
View File

@ -0,0 +1,40 @@
#!/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

View File

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

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

3657
jam-ui/cicd/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
jam-ui/cicd/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"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

@ -0,0 +1,55 @@
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

@ -0,0 +1,29 @@
/// <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', '/authentication/basic/login');
cy.url().should('include', '/auth/login');
cy.get('a')
.contains('Forgot password?')
.click();
cy.url().should('include', '/authentication/basic/forget-password');
cy.url().should('include', '/auth/forget-password');
cy.get('h5').contains('Forgot Your Password');
});
describe('validate forgot password form', () => {
beforeEach(() => {
cy.visit('/authentication/basic/forget-password');
cy.visit('/auth/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', /\/authentication\/basic\/confirm-mail?\S+/);
cy.url().should('not.include', /\/auth\/confirm-mail?\S+/);
});
//valid email format but non-existing
it('valid email format but non-existing', () => {
cy.get('[data-testid=email]').type('valid-email-format@example.com');
cy.get('[data-testid=submit]').click();
cy.url().should('not.include', /\/authentication\/basic\/confirm-mail?\S+/);
cy.url().should('not.include', /\/auth\/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+authentication\/basic\/confirm-mail?\S+/);
cy.url().should('match', /\S+auth\/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', '/authentication/basic/login')
cy.url().should('include', '/auth/login')
cy.contains('Sign In')
})
})
@ -42,7 +42,7 @@ describe('Login page', () => {
})
it('validate login form', () => {
cy.visit('/authentication/basic/login')
cy.visit('/auth/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('/authentication/basic/login')
cy.visit('/auth/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('/authentication/basic/login')
cy.visit('/auth/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', '/authentication/basic/login')
cy.url().should('include', '/auth/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('/authentication/basic/forget-password')
cy.visit('/auth/forget-password')
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=submit]').click()
cy.contains('An email is sent')

View File

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

View File

@ -25,7 +25,7 @@ describe('Top Navigation', () => {
it('not allowed to protected page', () => {
cy.visit('/friends');
cy.wait('@getAppFeatures');
cy.url().should('include', '/authentication/basic/login');
cy.url().should('include', '/auth/login');
cy.contains('Sign in');
cy.get('button').should('have.text', 'Sign in');
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist');

View File

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

1134
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.4.3",
"@fingerprintjs/fingerprintjs": "^4.6.0",
"@fortawesome/fontawesome-free": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-brands-svg-icons": "^5.14.0",
@ -61,6 +61,7 @@
"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",
@ -106,6 +107,7 @@
"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

After

Width:  |  Height:  |  Size: 158 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 158 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,43 @@
<?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>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,43 @@
<?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>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,12 @@
<?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>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,13 @@
<?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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,12 @@
<?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>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,12 @@
<?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>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,6 @@
<?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>

After

Width:  |  Height:  |  Size: 2.4 KiB

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