Compare commits

...

574 Commits

Author SHA1 Message Date
Seth Call dfe5facad6 ci: add simple test workflow
Build Admin / build (push) Failing after 2s Details
Environment Orchestrator / orchestrate (push) Failing after 9s Details
Test Runner / test (push) Successful in 1s 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
Seth Call f26733fa46 Merged in seth/jamtrack-lambda-importer (pull request #51)
Lambda & local importer for jamtracks

* # This is a combination of 9 commits.
# This is the 1st commit message:

inital attempt before upgrading Rails in lambda

# The commit message #2 will be skipped:

# unzip works

# The commit message #3 will be skipped:

# wip

# The commit message #4 will be skipped:

# Add in gemspec

# The commit message #5 will be skipped:

# wip

# The commit message #6 will be skipped:

# imported one locally

# The commit message #7 will be skipped:

# Add in jmep

# The commit message #8 will be skipped:

# add in some baked in env vars

# The commit message #9 will be skipped:

#  ran a local container build finally

* inital attempt before upgrading Rails in lambda

* Minor tweak for docker usage case of lambda running in AWS/lambda
2025-02-08 17:20:26 +00:00
Nuwan 55f6839521 chage menu items order under account submenu 2025-02-08 18:51:08 +05:30
Nuwan 4a3630f786 user_pref change
recording_pref chage from enum to normal integer values in user.rb
2025-02-06 09:17:10 +05:30
Nuwan 71458e3983 add tooltips to the recording options in preferences page 2025-02-03 12:35:26 +05:30
Nuwan 5ff152233b account preferences page
new page to change user's recording preferences
2025-02-03 12:25:21 +05:30
Nuwan 3677181e09 add jamtrack and artist landing pages 2025-01-30 11:57:01 +05:30
Nuwan 46111f041d fix joinSession custom URL param name 2025-01-29 14:38:16 +05:30
Nuwan 1dd160e19f customURL - show modal popup instead of redirecting to a new page 2025-01-29 12:55:14 +05:30
Nuwan f07a96165d change email layout
change to the new layout of the emails
reset password and after reset password
2025-01-24 08:10:27 +05:30
Nuwan 3e9d2e371e change mailer layouts
change the layout of the updating_email and updated_email templates
2025-01-22 09:53:15 +05:30
Nuwan 3d14ddbe74 affiliate links page changes 2025-01-18 22:42:30 +05:30
Nuwan 28487f271e add fields to capture venmo details
* add new field: venmo_user_id
* add new field: phone_last_4

changed the forms to replace paypal with venmo
2025-01-18 19:14:28 +05:30
Nuwan 51f1a7e58e jamtracks: force to new site
force the user to the new (beta) website jamtracks page
if they click the jamtracks tile on home page or they
load the url of that page directly in browser
2025-01-18 07:24:13 +05:30
Nuwan a027ed7096 change favicon 2025-01-17 22:42:53 +05:30
Nuwan 70dd62606b remove debug recaptcha 2025-01-16 14:27:28 +05:30
Nuwan 21301017ca debug recaptcha 2025-01-16 13:48:30 +05:30
Nuwan 1772f5a46f fixes & improvements in signup and downloads pages 2025-01-16 13:17:25 +05:30
Nuwan 4804877452 fix the size of the play/pause buttons in JamTracks listings 2025-01-09 13:35:26 +05:30
Nuwan 1d0a01c7ee signup & download pages
signup and download pages behave the same way to the legacy site
2025-01-09 08:21:26 +05:30
Nuwan 04a45394a0 wip new signup and download pages 2025-01-02 08:38:37 +05:30
Nuwan 2c6f1ef0de discard - FULL from jamtrack name in listings 2024-12-29 23:45:21 +05:30
Nuwan 2786c74175 my jamtracks page - increase the content size to be 75% in desktop window 2024-12-29 22:01:31 +05:30
Nuwan 8835455795 UI improments in JamTrack player 2024-12-29 18:47:55 +05:30
Nuwan 2d7a6db541 debug js uninitialized val in audio player 2024-12-28 00:24:35 +05:30
Nuwan 9b427b7a24 fix audio player js ref error 2024-12-28 00:11:13 +05:30
Nuwan 315f532719 fix audio player js ref error 2024-12-27 23:29:50 +05:30
Nuwan 04a7445f68 fix js variable reference error on loading the audio player component 2024-12-27 13:21:38 +05:30
Nuwan 6368897b21 fix ref error in audio player 2024-12-27 02:12:27 +05:30
Nuwan d7eef09946 custom audio player
change the defaut html audio player to a custom player
2024-12-27 01:47:08 +05:30
Nuwan f8462dbb84 fix jamTrack query params: page 2024-12-25 20:16:38 +05:30
Nuwan 8ddb4ce80d changes in locale file 2024-12-25 19:18:33 +05:30
Nuwan 6b66764082 improvements to JamTracks pages 2024-12-25 18:56:16 +05:30
Nuwan 5673d9b21e jamtracks related misc fixes 2024-12-25 00:04:11 +05:30
Nuwan c163d6b0ba UI layout fixes in checkout and shopping cart pages 2024-12-23 15:35:16 +05:30
Nuwan 61eddbb2bc prevent calling /shopping_carts api multiple times in jamtracks listing 2024-12-22 21:59:28 +05:30
Nuwan 48547d2cb1 JamTracks related ui fixes 2024-12-22 02:11:08 +05:30
Nuwan c3563a9197 wip jamtrack page creating and playing custom jamtracks 2024-12-17 16:22:31 +05:30
Nuwan 08fafbf2de work on JamTrack player 2024-12-10 11:23:02 +05:30
Nuwan cbaf1ea4f9 fix links in the jamtrack help resources section 2024-12-07 09:36:58 +05:30
Nuwan d9004980e6 fix presentation styles of jamtracks in jamtracks list page 2024-12-07 09:19:23 +05:30
Nuwan 5d87d1a358 handle purchasing a JamTrack
handle adding to cart of redeemable vs paid jamtracks
2024-12-05 20:01:56 +05:30
Nuwan 3245024925 more JamTrack page fixes 2024-12-03 19:35:38 +05:30
Nuwan d3a9a7c1e5 jamtracks related UI fixes 2024-12-03 19:03:21 +05:30
Nuwan f389250aa8 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-12-02 09:52:09 +05:30
Nuwan d5dcc71e35 fix error showing user affiliate agreement status and date 2024-12-02 09:46:01 +05:30
Seth Call 67241e7be7 Fix URLs in welcome email 2024-12-01 22:00:24 -06:00
Seth Call 5ab863bc33 Fix extra styles added to beta footer 2024-12-01 17:15:45 -06:00
Seth Call 89c4df9650 Align footer images better 2024-12-01 16:54:31 -06:00
Seth Call e6ac929303 Switch to pngs from svg for new beta email footer 2024-12-01 15:53:06 -06:00
Seth Call fb3cf004b9 once more with the asset paths 2024-12-01 14:14:45 -06:00
Seth Call 531bf25376 Fix svg paths 2024-12-01 13:38:29 -06:00
Seth Call 27b66783c1 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-12-01 10:01:21 -06:00
Seth Call 2402cf6992 Fix social urls 2024-12-01 10:01:14 -06:00
Nuwan 387a9b141d Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-12-01 06:07:16 +05:30
Nuwan 625a9dc0e4 fix app launching using custom URL scheme 2024-12-01 06:06:50 +05:30
Seth Call a09f922463 Force build 2024-11-30 13:21:49 -06:00
Seth Call 9b6f6f74e9 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-11-30 13:17:28 -06:00
Nuwan db9ad36627 debug console print custom mix audio url 2024-11-29 08:41:24 +05:30
Nuwan ea0131cb4e add help links in JamTracks page 2024-11-28 23:25:51 +05:30
Nuwan c5439f7a18 change the order of the affiliates menu items 2024-11-26 19:14:01 +05:30
Nuwan 78debaa8ab fix errors in JamTracks page and shopping cart page 2024-11-26 18:55:09 +05:30
Nuwan 8bbdedd082 fix affiliate link: use the correct id 2024-11-25 13:39:24 +05:30
Nuwan 3c4e59faa9 fix useHistory reference error 2024-11-23 18:59:44 +05:30
Nuwan b532308e28 custom url scheme: add ability to suppoer different type of custom actions. ex: createSession, joinSession 2024-11-23 18:42:12 +05:30
Nuwan 63289a29af change paramater names in joinSession custom Url 2024-11-22 22:56:25 +05:30
Nuwan 5d60c3db81 remove leading ? in the custom url query string 2024-11-22 22:13:18 +05:30
Nuwan 8f58fe4017 fixes in affiliate section - more textual and style fixes 2024-11-21 20:01:43 +05:30
Nuwan 82007bb41c minor textual and style updates to affiliate pages 2024-11-21 13:15:01 +05:30
Nuwan 358f42bba2 affliate page tweeks wip 2024-11-21 08:04:08 +05:30
Nuwan ab5b6a5fb5 enable forgot password form
make available the forgot password link so forgot password flow
is handled within the app. previously it redirected to the legacy site
2024-11-19 12:59:14 +05:30
Nuwan b04b8cfee2 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-11-19 12:53:04 +05:30
Nuwan 45e6122439 more tweaks to email template 2024-11-19 01:24:39 +05:30
Nuwan d0115a8cd9 new mailer template update 2024-11-19 01:24:39 +05:30
Nuwan ec7a79c8bb forgot password feature 2024-11-19 01:24:36 +05:30
Nuwan df9cbf3ba7 forgot password feature wip 2024-11-19 01:24:06 +05:30
Nuwan 33d0de6f0c wip forgot password 2024-11-19 01:24:06 +05:30
Nuwan 8b350ca3b6 wip forgot password 2024-11-19 01:24:06 +05:30
Nuwan abade1915e more tweaks to email template 2024-11-17 21:58:41 +05:30
Nuwan eba86ccdd5 new mailer template update 2024-11-17 21:19:29 +05:30
Nuwan a388d5e1d0 open jamkazam:// custom url in same window 2024-11-14 19:15:01 +05:30
Nuwan 98dbb358bd change the format of custom Url scheme 2024-11-14 18:41:07 +05:30
Nuwan 93961806eb skip showing latency badge if user is currentUser 2024-11-13 21:35:27 +05:30
Nuwan 6366bc5c6e fix type in showing instruments in session history 2024-11-13 21:13:21 +05:30
Nuwan ae138b79c0 session history latency not showing
includes fix for showing unknown latency badge for all sessions
and reformat the session time
2024-11-13 20:48:41 +05:30
Nuwan ed9887f9e9 add timezone to format session history date 2024-11-13 19:55:34 +05:30
Nuwan 3dc4af65e0 format session history date 2024-11-13 16:16:10 +05:30
Nuwan ab5947d94a error fix in sessions listing page
prevent errors that caused by null created_at values
when rendering session details
2024-11-13 07:31:13 +05:30
Seth Call 8979d732f2 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-11-11 18:40:42 -06:00
Nuwan b9eff8dac0 debug session history participat letancy 2024-11-11 00:42:34 +05:30
Nuwan c42dfdbb30 session history pagination 2024-11-10 21:10:08 +05:30
Nuwan 94ac48c8b5 fix images on beta email template 2024-11-08 16:40:20 +05:30
Nuwan ac469c2741 remove mailcatcher gem from Gemfile.alt 2024-11-08 13:03:47 +05:30
Nuwan d7a4e46355 add murali, bob & jorge to the jamkazam users that get the new_musicians_match email 2024-11-07 20:50:40 +05:30
Nuwan 051477d19f new user match email - jamkazam team only
add a config to restrict new_musicians_match email to be sent only
to jamkazam team users
2024-11-07 20:45:51 +05:30
Seth Call 47fd0cb68d Clean up load_history in terms of list all unique participants for a session 2024-11-07 07:35:51 -06:00
Nuwan 371f68a616 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-11-06 18:00:34 +05:30
Nuwan 69a37495b5 new user confirm email
update user confirm email contents and change to the new layout
2024-11-06 17:58:40 +05:30
Seth Call 78ae753d87 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-11-05 20:04:35 -06:00
Seth Call 4944c5983d Use current user to scope history 2024-11-05 20:04:28 -06:00
Nuwan 182deaa177 fix missing file error 2024-11-06 01:04:36 +05:30
Nuwan 1281caccf2 change welcome_message email to the new email layout and update content 2024-11-06 00:59:23 +05:30
Nuwan 604b6f6b59 localize matched new users email 2024-11-04 17:27:23 +05:30
Nuwan 996fbe51d0 new email template chages
this email template is used for the emails with the new design
which is used in the beta site.
2024-11-01 20:18:05 +05:30
Nuwan d4b412e223 change svg of other instrument icon 2024-10-30 08:36:52 +05:30
Nuwan 2d408eb3a7 show generic icon image for instruments which we do not have an icon 2024-10-29 22:32:20 +05:30
Nuwan c8a8ecc40d fix minor css issue in instrument list 2024-10-29 09:22:55 +05:30
Nuwan b51207dd3e UI style fixes in find friends page 2024-10-29 09:04:58 +05:30
Nuwan f2f6b978f8 hide 'from_location' checkbox
hide this checkbox in find friends feature. Just remove the UI
element without changing any logic
2024-10-28 21:29:24 +05:30
Nuwan b8b1146320 change format of custom URL schema according to the new way of handling it in the back end 2024-10-23 11:55:47 +05:30
Nuwan 541bca6b28 message modal - scroll to bottom
on the initial loading of messages, scroll to the bottom of the
message modal.
2024-10-23 08:22:06 +05:30
Nuwan c1a2e76186 fix profile avatar not showing
use src parameter when passing photo_url to the JKProfileAvatar
2024-10-23 08:00:50 +05:30
Nuwan 79ba0ebc70 use var instead of const
In layout.js. use var instead of const/let. because consts or let keywords are
not been supported in legacy client
2024-10-22 22:11:39 +05:30
Nuwan 8718ac4588 improvement to friend connect button 2024-10-22 21:57:56 +05:30
Nuwan 0dd8e90a7f edit profile button - make it a component with tooltip 2024-10-22 21:00:10 +05:30
Nuwan 3b0c2d1a42 fix musician filter
change instruments parameter names that are been passed to
the lambda function to be matched with the expected parameters in
labda function
2024-10-22 08:28:56 +05:30
Nuwan ccfb48459d improvements in friend connect/disconect function 2024-10-22 04:43:40 +05:30
Nuwan 6d1ad051fe fix alignment issues inside message modal contents 2024-10-19 16:03:02 +05:30
Nuwan 652b33ec51 prevent message modal scrolling on message arrive 2024-10-18 23:29:27 +05:30
Nuwan ca615eb9d9 misc fixes related to friends page 2024-10-18 22:48:11 +05:30
Nuwan 8e3272c7d5 fix data loading related to pagination in /friends page 2024-10-18 12:06:13 +05:30
Seth Call 5831e7d709 Make the previously-slow query target feed query configurable. Default to on 2024-10-14 11:18:50 -05:00
Nuwan 3c3ea6cbba custom URL scheme: use window.document.href instead of window.open 2024-10-11 11:55:12 +05:30
Nuwan 3db0a30fef styling change on join session button 2024-10-08 02:27:36 +05:30
Nuwan 7ed94b5dc0 useUserProfile hook - ability to turn on/off cache 2024-10-08 01:01:46 +05:30
Nuwan b83dfb2114 fix syntax error in browse sessions mobile view 2024-10-07 13:34:47 +05:30
Nuwan 29d64b7d0f debug browse session mobile view 2024-10-07 13:26:46 +05:30
Nuwan 36bd2788b2 another fix for managing profile edit page user genres 2024-10-07 11:24:37 +05:30
Nuwan 119a180a82 display only genres with type = profile in edit profile 2024-10-07 10:48:09 +05:30
Nuwan 91c85d5e59 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-10-07 08:49:45 +05:30
Nuwan 5d171ec016 prevent duplicated genres are being sent when updating user profile 2024-10-07 08:49:32 +05:30
Seth Call 4732138cbd Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-10-05 15:54:26 -05:00
Seth Call d07cff3721 Fix recurly bug where updated 2x from a RJS token 'ruins' the account until the user re-deploys 2024-10-05 15:54:20 -05:00
Nuwan 2afd8500bd change browse session no records alert 2024-10-05 02:58:48 +05:30
Nuwan 0377f73b9f show notification on friend request accept 2024-10-03 19:00:27 +05:30
Nuwan 47b1e843ed Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-10-03 04:38:41 +05:30
Nuwan 77359f7fc2 friends page state
use a separate redux slice for friends page
add musician slice to prevent mess up with the other user objects
fetched globally. people redux slice would serve
as global store having user records that can be referenced
elsewhere
2024-10-03 04:34:03 +05:30
Seth Call d603dd2911 bump paypayl recurly gem 2024-10-01 20:45:32 -05:00
Seth Call eea46feb5d Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-09-29 11:24:51 -05:00
Seth Call 7dbd061a86 Fix latency url 2024-09-29 11:24:39 -05:00
Nuwan db6a94ded1 forgot password: redirect to current site forgot password page 2024-09-23 20:16:03 +05:30
Nuwan 15af2d7469 fix for genres and instruments loading in profile edit page 2024-09-22 10:15:18 +05:30
Nuwan 9a0b4f4ddb profile edit: fix genres select edge case on when the user does not already have selected any genres 2024-09-21 09:38:26 +05:30
Nuwan c779277b4d change help desk page url 2024-09-20 20:24:12 +05:30
Nuwan a0dc7fa92d force user to login when accessed from email message link 2024-09-19 18:18:24 +05:30
Nuwan 0993c9a6b6 fix for genres and instrument loading in profile page 2024-09-19 13:50:34 +05:30
Nuwan b876e1e253 open help pages on new tab 2024-09-19 04:56:21 +05:30
Nuwan 3300b5a37a edit profile page: fixing genres and instrument loading issue 2024-09-18 03:12:51 +05:30
Nuwan 3695b2f5d4 help links: redirect to relevent external urls 2024-09-14 12:05:47 +05:30
Nuwan d976cdf866 remove redundent association keys
In the api_users_controller#profile_show remove :genres, :instruments
keys from the association includes
2024-09-13 08:02:34 +05:30
Nuwan fa35930c75 fix for user instruements and genres are not been loaded on edit profile page load 2024-09-10 19:28:44 +05:30
Nuwan cd1930a2bc fix for user instruements and genres are not been loaded on edit profile page load 2024-09-10 18:10:18 +05:30
Nuwan b5a18f6501 trying to figure out why user instruements and genres are not being loaded on edit profile page load 2024-09-10 15:46:27 +05:30
Nuwan fd1c0714d3 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-09-09 15:27:21 +05:30
Nuwan 4f6bf663dd fix for instruments and genres initial loading in profile edit form 2024-09-09 15:26:56 +05:30
Seth Call 0c2102b308 clean it up again 2024-09-08 14:46:16 -05:00
Seth Call ba59e7b20b Newer build 2024-09-08 14:19:44 -05:00
Seth Call 3f1a3402d8 Merged in feature/all-clients-are-modern (pull request #49)
Move all clients to the 'modern' track.

* Force old client website to upgrade to new client

* Also add

* wording fix
2024-09-08 18:18:02 +00:00
Nuwan c60959126c Flip positions of Instruments and Genres UI cards on Profile pag 2024-09-08 07:22:01 +05:30
Nuwan c404cc0bb1 add filestack-js in package.json 2024-09-07 23:35:51 +05:30
Nuwan ef79d3a8c0 now notifications in header drawer scrollable 2024-09-06 23:15:48 +05:30
Nuwan 499cd7e16b header notifications
show number of unread notifiacation count on header
allow user to scroll through the notification in header notificaton
drawer
2024-09-03 00:11:36 +05:30
Nuwan 727147ffee Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-08-29 17:10:01 +05:30
Nuwan 3175f77b7f implement profile photo upload 2024-08-29 17:08:19 +05:30
Seth Call 6833c68e40 Merge branch 'develop' into feature/legacy-download 2024-08-28 06:23:41 -05:00
Nuwan 9668b59e23 integration with filestack for profile avatar image uploading - wip 2024-08-27 00:50:01 +05:30
Nuwan 8aa463c054 in header show notification number badge 2024-08-23 16:03:23 +05:30
Nuwan 136a3c399d edit profile page ui changes 2024-08-21 00:49:58 +05:30
Nuwan e973a5f55c add control to prevent unsaved changes
in profile edit if user tries to navigate away from the site
show native confirmation message asking user's consent
2024-08-19 19:53:04 +05:30
Nuwan 5a00f45d5c profile edit page ui improvements 2024-08-18 16:41:48 +05:30
Nuwan 39be8f2955 adujest the width of instrument selection section 2024-08-16 21:49:44 +05:30
Nuwan bca4f17c8f profile edit - make the profile photo bigger and fix alignments 2024-08-16 12:05:55 +05:30
Nuwan f9aee04dd5 add missing api call to rest.js 2024-08-15 13:58:36 +05:30
Nuwan 3945e97319 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-08-15 13:55:43 +05:30
Nuwan be1cca46b1 my jam tracks loading fixes 2024-08-14 08:44:46 +05:30
Nuwan 898154a775 fix my jam track filtering 2024-08-14 08:44:46 +05:30
Nuwan 94308e9c1d fix my jam track filtering 2024-08-14 08:44:46 +05:30
Nuwan 828939ff68 fix my jam track filtering 2024-08-14 08:44:46 +05:30
Nuwan 7d7dc7a620 fix not loading next page results in my jam tracks 2024-08-14 08:44:46 +05:30
Nuwan 3b451427fc jamtrack player, cusmtom mixes and download mixes 2024-08-14 08:44:43 +05:30
Nuwan f904bdfc1c wip jamtrack player 2024-08-14 08:44:08 +05:30
Nuwan faecb69db9 jamtrack player WIP 2024-08-14 08:43:29 +05:30
Nuwan 2cc14be759 WIP: jamtracks, shopping cart and checkout pages 2024-08-14 08:43:18 +05:30
Nuwan 6e29efd307 jamtracks pages
includes jamtrack listing, my jamtracks, jamtrack details pages
2024-08-14 08:40:38 +05:30
Nuwan fb537806ca my jam tracks loading fixes 2024-08-13 23:43:34 +05:30
Nuwan 22d4553f6f fix my jam track filtering 2024-08-13 01:19:24 +05:30
Nuwan a0e4c5fc33 fix my jam track filtering 2024-08-13 00:46:27 +05:30
Nuwan 44f78edc63 fix my jam track filtering 2024-08-13 00:11:26 +05:30
Nuwan 4a09e56d8b fix not loading next page results in my jam tracks 2024-08-12 14:15:37 +05:30
Nuwan 7cf5648b78 jamtrack player, cusmtom mixes and download mixes 2024-08-12 11:48:04 +05:30
Seth Call e55ad12b61 missed files 2024-08-11 22:10:47 -05:00
Seth Call c907d9c3ba Be sure to use the same product names as in the database 2024-08-11 20:12:33 -05:00
Seth Call c44db20385 Create /downloads-legacy, and update /downloads for the 3 clients 2024-08-11 19:39:56 -05:00
Nuwan 7af01a6c61 wip jamtrack player 2024-07-31 11:27:56 +05:30
Nuwan 5b750cc3d9 jamtrack player WIP 2024-07-19 21:03:28 +05:30
Nuwan 106ea91361 make login compulsory to access the other pages 2024-07-17 18:14:18 +05:30
Nuwan 041ebccaf1 active admin CRUD for app feature 2024-07-17 15:27:50 +05:30
Nuwan 86d77df2c9 app features on/off
add ability to control the visibility of beta site menu items.
the visibility state is stored in the back end api and the front
end menu items are been shown accordingly.
2024-07-16 17:26:48 +05:30
Nuwan a3c511d2b0 WIP: jamtracks, shopping cart and checkout pages 2024-07-13 15:17:13 +05:30
Nuwan adafcb8569 work in progress on jamtracks shopping 2024-06-21 18:16:29 +05:30
Nuwan 48335a9d9c add spaces between artists list items 2024-06-10 23:02:41 +05:30
Nuwan 5fd4015ee2 fix track preview alignment issue 2024-06-10 22:57:57 +05:30
Nuwan e2b4c901dc Jamtrack shopping page 2024-06-10 21:28:34 +05:30
Nuwan 176ba1febe JamTracks
includes JamTrack filter page
2024-06-10 13:45:15 +05:30
Nuwan 24aab60556 add cypress tests for affiliates pages 2024-05-23 17:22:41 +05:30
Nuwan 8868ff718f add tests for affiliate earnings 2024-05-23 00:55:03 +05:30
Nuwan bbe83a008f affiliate earnings page 2024-05-17 14:15:19 +05:30
Nuwan e878c64c3c affiliate signups and links pages 2024-05-17 10:03:36 +05:30
Nuwan 8f63547f34 affiliate program and payee pages 2024-05-09 18:42:55 +05:30
Nuwan 97e0a8d36a session history page:
list all the sessions in decending order they were created
2024-05-05 18:38:29 +05:30
Nuwan 7176fecd7b sessions history page wip 2024-05-03 10:31:57 +05:30
Nuwan 37ed2dddd4 beta site sessions history page wip 2024-05-03 08:25:51 +05:30
Nuwan cb0cb654c8 limit CORS only to /api/* 2024-04-26 16:35:45 +05:30
Nuwan 40fbc7b430 fix error when unmounting paymemnt page 2024-04-25 20:48:06 +05:30
Nuwan 81c1c17a2f add more tests for payment history page, responsive mobile view 2024-04-24 20:44:00 +05:30
Nuwan c2ffbff216 account payments page and cypress tests 2024-04-24 08:28:02 +05:30
Nuwan 4dd95af2fa tweaks to subscription page 2024-04-20 17:27:30 +05:30
Nuwan 20eb17b044 initial release of my friends page 2024-04-20 16:44:24 +05:30
Nuwan 703309aa65 add more tests for subscription page 2024-04-17 11:33:52 +05:30
Nuwan 3ae52098c6 fix for subscription display error 2024-04-16 18:30:37 +05:30
Nuwan bd45275107 prevent duplicate entries in friends page 2024-04-12 19:39:55 +05:30
Nuwan edb6bd0b90 fix friends page related cypress tests 2024-04-11 00:21:53 +05:30
Nuwan cdd12d621d Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-04-10 13:46:51 +05:30
Nuwan cb1a8f54c5 fix default plan selection in account subscription 2024-04-10 13:39:37 +05:30
Nuwan 05d8db9cfa add cypress tests for subscription page 2024-04-10 13:39:00 +05:30
Nuwan af1140fa05 fix default plan selection in account subscription 2024-04-05 19:36:44 +05:30
Nuwan 7b0d6c153e fix null value error 2024-04-05 11:11:13 +05:30
Nuwan dbb5c4a520 account subscription
let user to change subscription
2024-04-05 10:52:06 +05:30
Nuwan ee95d07dfc fix style of join session button 2024-03-21 19:22:44 +05:30
Nuwan 884203844b Join session link
add link under the join button in browse sessions page
2024-03-21 08:13:56 +05:30
Nuwan a420767446 Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2024-03-21 00:31:47 +05:30
Nuwan 4ff3d2d83c enter.svg - change css color attribute 2024-03-21 00:31:35 +05:30
Nuwan Chaturanga d80627b5d4 Merged in develop-promise (pull request #46)
Develop promise

Approved-by: Seth Call
2024-03-20 14:00:10 +00:00
Seth Call 73c566fa9e Merged develop into develop-promise 2024-03-20 13:59:21 +00:00
Nuwan e305898a74 resize the join button icon in browser sessions page 2024-03-20 09:12:26 +05:30
Nuwan 2a31e81c65 remove whitespace 2024-03-18 08:52:18 +05:30
Nuwan 6ad9aed71f use var instead of let/const
let and const keywords in javascript can not use because the code based
is also been used in the production client.
2024-03-18 08:44:40 +05:30
Nuwan 2459b3d447 websocket-gateway copy updates to init 2024-03-16 22:53:15 +05:30
Nuwan 0476e5ebb6 set default for accept_desktop_notifications 2024-03-16 22:47:15 +05:30
Nuwan e5dc8b57ec fix syntax typo in websocket-gateway router.rb 2024-03-16 16:38:33 +05:30
Nuwan 52c8b48d81 merge websocket-gateway changes
from promised branch get the changes on router file and apply
on develop branch
2024-03-16 14:34:03 +05:30
Nuwan 4144d6d123 extract changes to artifacts from promised based branch 2024-03-16 14:18:31 +05:30
Nuwan 1c0a6b2138 change default value setting
change users migration not to lock the table by directly setting
the default value of the accept_desktop_notifications colums
when running the migration
2024-03-16 10:52:32 +05:30
Nuwan 17b0ea6111 fix errors while selecting country, region and city 2024-03-11 22:23:12 +05:30
Nuwan 95008f49e6 debug error on profile edit (in server) 2024-03-11 16:38:22 +05:30
Nuwan 9164a1058d debug error on profile edit (in server) 2024-03-11 07:57:30 +05:30
Nuwan 23830144d8 debug error on profile edit (in server) 2024-03-11 07:50:49 +05:30
Nuwan eb488e374b profile page update synchronously.
making this chage to fetch all the data that need to be present
for the page on initial loading
2024-03-11 07:27:00 +05:30
Nuwan 8f84e5d12d disable filestack code in image upload for now 2024-02-28 22:19:29 +05:30
Nuwan 238fc8a382 fix error on creating database index: wrong table 2024-02-28 21:58:03 +05:30
Nuwan 9dc101d3f6 fix instrument proficiency chage
update the state correctly of the instrument list when
proficiency is changed
2024-02-28 19:48:52 +05:30
Nuwan 1a11cc331c add e2e test cases for account/identity and profile edit pages 2024-02-23 16:22:55 +05:30
Nuwan 8cf7048b7f fixes related to beta site profile edit and lobby page 2024-02-19 19:00:53 +05:30
Nuwan 4c161025e8 profile image upload wip 2024-02-16 18:25:05 +05:30
Nuwan 6546cc08bc add language translation keys to session pages 2024-02-14 17:31:30 +05:30
Nuwan fd9632a26c add translations 2024-02-14 11:37:01 +05:30
Nuwan 861722e5d4 includes fixes in profile edit form and lobby page 2024-02-12 19:11:11 +05:30
Nuwan 30aa9eeed2 merge conflicts fix 2024-02-05 19:10:40 +05:30
Nuwan 1de7a1f2f2 fix merge conflicts 2024-02-05 19:07:10 +05:30
Nuwan 963cc238e8 remove duplicate method call in rest.js 2024-02-05 19:03:42 +05:30
Nuwan dd4239f1f3 account identity update
allow users to update their JamKazam email and password securly.
2024-02-05 18:26:01 +05:30
Nuwan fe6c372d3d wip: profile edit 2024-02-05 18:26:01 +05:30
Nuwan df90496d30 change session enter button icon 2024-02-05 18:24:08 +05:30
Nuwan b85a2b236c fix instrument icon size in mobile view 2024-02-05 18:23:51 +05:30
Nuwan 0406a64531 fix: validate latency badge props 2024-02-05 18:22:39 +05:30
Nuwan 3fed41899a fix /filter api - prevent error on exception conditions 2024-02-05 18:22:39 +05:30
Nuwan 6e131f6b5d fix query string construction when fetching user latencies 2024-02-05 18:22:39 +05:30
Nuwan c29d3459e6 fix lobby users query 2024-02-05 18:21:18 +05:30
Nuwan c16be14207 fix errors due to null values 2024-02-04 23:39:54 +05:30
Nuwan aeb2e50692 account identity update
allow users to update their JamKazam email and password securly.
2024-02-03 18:29:57 +05:30
Nuwan d2c525f498 more updates to lobby page
includes showing chat notifications. also ui improvements
2024-01-22 11:28:16 +05:30
Nuwan b563f22a32 wip: profile edit 2024-01-14 13:32:15 +05:30
Nuwan 4accb2296f merge conflict resolved 2024-01-02 02:28:36 +05:30
Nuwan 23e0a7096c change session enter button icon 2024-01-02 02:23:48 +05:30
Nuwan 3bf694d816 fix instrument icon size in mobile view 2024-01-02 02:23:48 +05:30
Nuwan b4200a1b87 fix: validate latency badge props 2024-01-02 02:23:48 +05:30
Nuwan 286841c3d0 fix /filter api - prevent error on exception conditions 2024-01-02 02:23:48 +05:30
Nuwan 9b59ef6121 fix query string construction when fetching user latencies 2024-01-02 02:23:48 +05:30
Nuwan 5a8c85e765 session lobby with global chat feature 2024-01-02 02:23:21 +05:30
Nuwan c07bf115e6 change session enter button icon 2023-12-27 20:44:15 +05:30
Nuwan 06e085fd13 fix instrument icon size in mobile view 2023-12-27 18:15:50 +05:30
Nuwan 9b72852a54 wip session lobby 2023-12-22 15:27:26 +05:30
Nuwan 0f25b00571 wip session lobby 2023-12-20 21:32:01 +05:30
Nuwan 8e792d1658 fix: validate latency badge props 2023-12-14 20:02:39 +05:30
Nuwan 84481a4fec fix /filter api - prevent error on exception conditions 2023-12-14 11:34:42 +05:30
Nuwan cff277d437 session lobby wip 2023-12-14 04:19:25 +05:30
Nuwan b0672a9cb3 fix query string construction when fetching user latencies 2023-12-06 04:51:17 +05:30
Nuwan 4fed7cbb1a wip session lobby 2023-12-06 04:23:17 +05:30
Nuwan 684fff910c wip session lobby 2023-12-05 15:04:19 +05:30
Nuwan 64c42b5de3 update cypress e2e tests 2023-12-05 12:07:19 +05:30
Nuwan 38f9d4869b fix UI related alignment issues in browse sessions page 2023-12-03 15:56:11 +05:30
Nuwan 317fdb2171 Instrument icon convered as a react component 2023-12-01 01:25:43 +05:30
Nuwan 6d5edf93bf tweek styles in use selection 2023-11-30 23:34:56 +05:30
Nuwan 335d7667b5 fix latency badge and avatar image 2023-11-30 09:59:11 +05:30
Nuwan 0a871c738a add condition to display latency badge only 2023-11-29 09:37:53 +05:30
Nuwan 1d355a3a96 hide latency numbers in latency badge in sessions page 2023-11-29 09:13:07 +05:30
Nuwan c1a4481bf2 latency badge - fix data 2023-11-29 08:59:04 +05:30
Nuwan 5315d88903 fix api response of latency data 2023-11-29 08:36:37 +05:30
Nuwan e5afb83f81 fix rest api getLatencyToUsers parameter 2023-11-29 08:04:14 +05:30
Nuwan 812cedb0e1 fix for latency badge in browse sessions page 2023-11-29 01:23:50 +05:30
Nuwan a07ff74dc0 fix musician avatar image src in large browser views 2023-11-28 17:26:08 +05:30
Nuwan 27a3b3c09b tweek ui and styles in browse sessions page 2023-11-28 16:48:55 +05:30
Nuwan 21d6801185 fix styles in browse sessions 2023-11-26 00:22:44 +05:30
Nuwan 66a7f81317 show instrument icons in profile details side panel 2023-11-25 23:10:50 +05:30
Nuwan 6fed528e84 fix styles in browse session page 2023-11-25 11:16:16 +05:30
Nuwan 2b96b4d8ef Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop 2023-11-24 20:03:55 +05:30
Nuwan d8685a00bd fix errornious src value to profile avatar image 2023-11-24 20:03:31 +05:30
Nuwan d0d2a41e96 change firends autocomplete default avatar 2023-11-24 20:03:31 +05:30
Nuwan 2edfccda87 browse sessions page ui improvements 2023-11-24 20:03:11 +05:30
Nuwan 3062238465 fix errornious src value to profile avatar image 2023-11-17 22:35:40 +05:30
Nuwan 67fbf932ee change firends autocomplete default avatar 2023-11-17 18:53:41 +05:30
Nuwan 5598b2ef17 fix responsive view of new session window in small and medium size windows 2023-11-15 19:43:36 +05:30
Nuwan 60ef40a51b fix styles in mobile view of create new session screen 2023-11-15 18:23:38 +05:30
Nuwan fcd9dd15f4 show native app unavailable modal
show this modal if the native JamKazam app is not available on user's computer.
occurs on submission of new session form and on clicking join session in
browse session page.
2023-11-09 16:19:15 +05:30
Nuwan b7615c3fcd hide preview session button 2023-11-05 19:49:34 +05:30
Nuwan 10e67e4cbb fix merge conflicts 2023-11-03 09:22:09 +05:30
Nuwan fef31f097f merge new beta site features to develop branch 2023-11-02 17:51:42 +05:30
Nuwan c108bdd058 fixes to cypress test of friends list 2023-11-02 17:00:45 +05:30
Seth Call f42383f0b4 build bump 2023-11-02 08:41:35 +05:30
Seth Call 2102f8807a Rollback pg 2023-11-02 08:41:35 +05:30
Seth Call c8a111f5f5 Beta download page 2023-11-02 08:41:35 +05:30
Seth Call e491461f64 notes 2023-11-02 08:41:35 +05:30
Nuwan 9b94ea6351 limits musicians in weekly email
this limits the number of users in weekly email to be 20
2023-11-02 08:41:35 +05:30
Nuwan 7149392fe2 Limits weekly email entries
this limits number of musician to be listed in weekly email to be 20
2023-11-02 08:41:35 +05:30
Nuwan 20c8df7121 show last active time in words
in user recommendation email show the last active time using
action_view time_ago_in_words helper
2023-11-02 08:41:35 +05:30
Nuwan 1ca3f5f245 improvements to sending weekly emails
- optimize user filtering sql
- dealing with default values
- eliminate null value errors in mailer templates
2023-11-02 08:41:35 +05:30
Nuwan 1e988931a4 sending weekly email to users about new users joined in 2023-11-02 08:41:29 +05:30
Nuwan f75d2c8c46 send weekly email to users about new musicians 2023-11-02 08:39:16 +05:30
Nuwan fad3b73d67 wip new user recommendation email 2023-11-02 08:33:48 +05:30
Nuwan 46202a2373 sip on user match mailer 2023-11-02 08:33:48 +05:30
Nuwan bbab6cdb9f user recom email wip 2023-11-02 08:33:48 +05:30
Nuwan 6a57530a8b beta site current session listing with e2e tests 2023-11-01 18:58:08 +05:30
Nuwan b3922ec025 wip browse music sessions 2023-10-26 17:25:02 +05:30
Nuwan dd03c215c4 new session opening jamkazam app
submit the form to open jamkazam app in to
new session window with the selected privacy level.
also contains e2e tests
2023-10-19 17:09:02 +05:30
Nuwan 20c02f0f2a wip on session create in beta site 2023-10-17 08:30:23 +05:30
Nuwan 02f3201cb2 wip new session form 2023-10-14 06:56:39 +05:30
Nuwan 65bc188af1 wip beta create session 2023-10-03 00:40:53 +05:30
Nuwan 484482e5bd modificaions to the leve side navigation and new sessions page 2023-09-29 22:45:26 +05:30
Nuwan 036d982ab7 package lock updated 2023-09-28 16:52:45 +05:30
Seth Call fb7e4a287e build bump 2023-09-08 06:48:18 -05:00
Seth Call 0a32c1fac9 Rollback pg 2023-09-01 17:53:20 -05:00
Seth Call c5767efbaa Beta download page 2023-09-01 17:18:41 -05:00
Seth Call 3981c47f9f notes 2023-02-17 12:32:34 -06:00
Seth Call 84e3609602 Merged in feature/user_recommendations_email (pull request #41)
WIP: Feature/user recommendations email
2023-02-14 15:56:01 +00:00
Nuwan 199086d06b add prop-types validations and cypress e2e tests 2023-02-14 01:59:55 +05:30
Nuwan fa082230b8 create new session page for beta site 2023-02-12 12:06:16 +05:30
Nuwan b589ad8553 working on new session screen 2023-02-09 09:35:29 +05:30
Nuwan 981b83cea2 wip jam-ui new session window 2023-02-07 10:05:03 +05:30
Nuwan e25b88d062 wip jam-ui session 2023-02-03 00:24:26 +05:30
Nuwan 550a458c8d limits musicians in weekly email
this limits the number of users in weekly email to be 20
2023-01-27 17:36:07 +05:30
Nuwan 100e14cad0 Limits weekly email entries
this limits number of musician to be listed in weekly email to be 20
2023-01-27 17:11:06 +05:30
Nuwan 529d3fc891 show last active time in words
in user recommendation email show the last active time using
action_view time_ago_in_words helper
2023-01-26 00:30:17 +05:30
Nuwan 6ea439b435 improvements to sending weekly emails
- optimize user filtering sql
- dealing with default values
- eliminate null value errors in mailer templates
2023-01-25 22:50:07 +05:30
Nuwan 2b80e277c9 sending weekly email to users about new users joined in 2023-01-24 09:53:04 +05:30
Nuwan 96a93c7daf send weekly email to users about new musicians 2023-01-20 04:50:27 +05:30
Nuwan 6ff25ad1b7 wip new user recommendation email 2023-01-13 11:10:50 +05:30
Nuwan 4405e08c0a sip on user match mailer 2023-01-04 21:41:57 +05:30
Nuwan db7525ad64 user recom email wip 2023-01-02 16:39:05 +05:30
Nuwan 50ed5116f7 fix for pagination 2022-10-27 12:16:02 +05:30
Nuwan 17207fc0b8 fix for offset var 2022-10-27 11:53:47 +05:30
Nuwan 2de5f3a3c2 improve cypress integration test for filter musicians 2022-10-27 11:29:20 +05:30
Nuwan e943a3235e use offset for pagination instead of page number 2022-10-26 22:44:20 +05:30
Nuwan 957c5dc3e9 clean up musician filter codes 2022-10-26 12:46:27 +05:30
Nuwan 5c0ed0d773 remove currentPage and NextPage variables and just use page variable for pagination 2022-10-26 12:15:11 +05:30
Nuwan 6fd7a6531b fix for pagination. just show what ever data received from api without checking unique 2022-10-26 10:45:09 +05:30
Nuwan a9c0315697 fixes for pagination people filter component 2022-10-25 23:27:58 +05:30
Nuwan 0cac99e407 friends list pagination fix 2022-10-25 21:42:30 +05:30
Nuwan f8eca9d538 fixes to musicians list filter prefetcing records 2022-10-25 18:45:48 +05:30
Nuwan 6e78f61f9c improve prefetching
Only fetch the 'next set of records that aren't yet shown' once the
initial request is done. When you click 'Load', it should be only making one request at that point -- which would be the next page after the current set being shown
2022-10-23 16:19:57 +05:30
Nuwan Chaturanga 78b9cd0156 Merged in improve/musician_filter_api (pull request #39)
Improve/musician filter api

Approved-by: Seth Call
2022-10-20 15:03:49 +00:00
Nuwan 295d613165 reduce number of sql queries in musicians filter
default last active option is set to 30 days
make it 5ms when neo4j returns 0 for audio latency
2022-10-20 17:53:23 +05:30
Nuwan 1080ac4a47 add pg_data directory to gitignore 2022-10-18 22:12:12 +05:30
Nuwan c2f7fe7c32 change to api musician filter to minimize the database queries 2022-10-18 22:11:09 +05:30
Nuwan 5bd67d431c change to api musician filter to minimize the database queries 2022-10-18 22:07:15 +05:30
814 changed files with 126749 additions and 40726 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!"

1
.gitignore vendored
View File

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

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
2.4.1

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

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

View File

@ -640,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

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

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_LEGACY_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
- 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_LEGACY_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
- 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"

1
build
View File

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

View File

@ -1,7 +1,16 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
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_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_RECAPTCHA_ENABLED=false
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
PUBLIC_URL=
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms

View File

@ -1,7 +1,13 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
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_BITBUCKET_COMMIT=dev
REACT_APP_ENV=development
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
REACT_APP_RECURLY_PUBLIC_API_KEY=
REACT_APP_BRAINTREE_TOKEN=

View File

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

View File

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

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

1
jam-ui/.nvmrc Normal file
View File

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

1
jam-ui/.python-version Normal file
View File

@ -0,0 +1 @@
2.7.18

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)
@ -8,7 +17,7 @@ The DOMAIN and PORT running this app is defined in env.production file. This fil
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_CLIENT_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
## Subdomains setup (development)
@ -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
},
};

22
jam-ui/cypress.config.js Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
/// <reference types="cypress" />
import makeFakeUser from '../../factories/user';
describe('Account Preferences Feature', () => {
beforeEach(() => {
// Log in to the application or navigate to the account page
// where the change email feature is available
const currentUser = makeFakeUser({
email: 'sam@example.com',
recording_pref: 0
});
cy.stubAuthenticate({ ...currentUser });
cy.intercept('POST', /\S+\/users\S+/, {
statusCode: 200
}).as('updateUser');
cy.visit('/account/preferences');
});
it('should display the current recording preference', () => {
// Assert that the current recording preference is displayed
cy.get('[data-testid=recording_pref_none]').should('be.checked');
});
it('should save the new recording preference', () => {
// Select a new recording preference
cy.get('[data-testid=recording_pref_my_audio]').check('1');
cy.wait('@updateUser');
// Assert that the new recording preference is saved
cy.contains('Recording preference saved successfully.');
});
});

View File

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

View File

@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,9 +30,9 @@ function submitLogin(){
describe('Unauthenticated users redirect to login page', () => {
it('redirects to login page', () => {
cy.clearCookie('remeber_token')
cy.visit('/friends')
cy.url().should('include', '/authentication/basic/login')
cy.contains('Sign in')
cy.visit('/')
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')
@ -81,4 +81,13 @@ describe('Login page', () => {
})
describe('Forget password page', () => {
it('submit forget password form', () => {
cy.visit('/auth/forget-password')
cy.get('[data-testid=email]').type('peter@example.com')
cy.get('[data-testid=submit]').click()
cy.contains('An email is sent')
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,74 +1,81 @@
[
{
"description": "TEXT_MESSAGE",
"source_user_id": "27bd4a30-d1b8-4eea-8454-01a104d59381",
"target_user_id": "a09f9a7e-afb7-489d-870d-e13a336e0b97",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": null,
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "TEXT_MESSAGE",
"message": "Hello",
"created_at": "2021-10-07T00:09:57.704Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Nuwan Chaturanga"
{
"next": null,
"unread_total": 3,
"notifications": [
{
"description": "TEXT_MESSAGE",
"source_user_id": "27bd4a30-d1b8-4eea-8454-01a104d59381",
"target_user_id": "a09f9a7e-afb7-489d-870d-e13a336e0b97",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": null,
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "TEXT_MESSAGE",
"message": "Hello",
"created_at": "2021-10-07T00:09:57.704Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Nuwan Chaturanga"
},
"notification_id": "63fcd878-9a22-4419-9cee-8a51a615da97",
"fan_access": null,
"musician_access": null,
"approval_required": null,
"read_at": null
},
"notification_id": "63fcd878-9a22-4419-9cee-8a51a615da97",
"fan_access": null,
"musician_access": null,
"approval_required": null
},
{
"description": "FRIEND_REQUEST",
"source_user_id": "a09f9a7e-afb7-489d-870d-e13a336e0b97",
"target_user_id": "b1ddadd0-0263-47c4-bf91-e7767f386970",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": "7c842904-24f5-4515-8886-0c3d25ee641b",
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "Seth Call has sent you a friend request.",
"message": null,
"created_at": "2021-10-15T05:36:48.527Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Seth Call"
{
"description": "FRIEND_REQUEST",
"source_user_id": "a09f9a7e-afb7-489d-870d-e13a336e0b97",
"target_user_id": "b1ddadd0-0263-47c4-bf91-e7767f386970",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": "7c842904-24f5-4515-8886-0c3d25ee641b",
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "Seth Call has sent you a friend request.",
"message": null,
"created_at": "2021-10-15T05:36:48.527Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Seth Call"
},
"notification_id": "3364b5f1-8946-46a3-b635-86d89d237849",
"fan_access": null,
"musician_access": null,
"approval_required": null,
"read_at": null
},
"notification_id": "3364b5f1-8946-46a3-b635-86d89d237849",
"fan_access": null,
"musician_access": null,
"approval_required": null
},
{
"description": "FRIEND_REQUEST_ACCEPTED",
"source_user_id": "29becbf4-8be5-4078-9405-0edadc9fa42d",
"target_user_id": "b1ddadd0-0263-47c4-bf91-e7767f386970",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": null,
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "Peter Walker has accepted your friend request.",
"message": null,
"created_at": "2021-10-05T12:38:53.134Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Peter Walker"
},
"notification_id": "bb9269f3-721c-48cd-9bf6-bcff72877198",
"fan_access": null,
"musician_access": null,
"approval_required": null
}
]
{
"description": "FRIEND_REQUEST_ACCEPTED",
"source_user_id": "29becbf4-8be5-4078-9405-0edadc9fa42d",
"target_user_id": "b1ddadd0-0263-47c4-bf91-e7767f386970",
"session_id": null,
"recording_id": null,
"invitation_id": null,
"join_request_id": null,
"friend_request_id": null,
"band_id": null,
"band_invitation_id": null,
"formatted_msg": "Peter Walker has accepted your friend request.",
"message": null,
"created_at": "2021-10-05T12:38:53.134Z",
"lesson_session_id": null,
"purpose": null,
"source_user": {
"name": "Peter Walker"
},
"notification_id": "bb9269f3-721c-48cd-9bf6-bcff72877198",
"fan_access": null,
"musician_access": null,
"approval_required": null,
"read_at": null
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ const cleanCSS = require('gulp-clean-css');
-----------------------------------------------*/
gulp.task('scss', () =>
gulp
.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(

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