Merged in VRFS-5232-new_react_frontend (pull request #34)

New UI mvp1
This commit is contained in:
Seth Call 2021-10-21 16:36:59 +00:00
commit 6c35c02bdf
948 changed files with 74257 additions and 36 deletions

21
bitbucket-pipelines.yml Normal file
View File

@ -0,0 +1,21 @@
image: node:14.17.1
pipelines:
branches:
VRFS-5232-new_react_frontend:
- step:
name: Build Production
script:
- cd jam-ui
- npm install
- CI=false npm run build
artifacts:
- jam-ui/build/**
- step:
name: Deploy to S3
deployment: production
script:
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
S3_BUCKET: "jamkazam-ui/prd"
LOCAL_PATH: "jam-ui/build"

6
jam-ui/.browserslistrc Normal file
View File

@ -0,0 +1,6 @@
# Browsers that we support
last 1 version
> 0.2%
not op_mini all
not dead

View File

@ -0,0 +1,5 @@
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_ORIGIN=jamkazam.local
REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api

5
jam-ui/.env.production Normal file
View File

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

8
jam-ui/.eslintrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": ["react-app", "prettier", "plugin:react/recommended"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"react/no-unescaped-entities": false
}
}

30
jam-ui/.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.cypress.env.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.idea
/.vscode
/test-results

31
jam-ui/.gitlab-ci.yml Normal file
View File

@ -0,0 +1,31 @@
# Using the node alpine image to build the React app
image: node:alpine
# Announce the URL as per CRA docs
# https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration
variables:
PUBLIC_URL: /react-falcon
# Cache node modules - speeds up future builds
cache:
paths:
- node_modules
# Name the stages involved in the pipeline
stages:
- deploy
# Job name for gitlab to recognise this results in assets for Gitlab Pages
# https://docs.gitlab.com/ee/user/project/pages/introduction.html#gitlab-pages-requirements
pages:
stage: deploy
script:
- npm install # Install all dependencies
- npm run build --prod # Build for prod
- mv public _public # CRA and gitlab pages both use the public folder. Only do this in a build pipeline.
- mv build public # Move build files to public dir for Gitlab Pages
- cp public/index.html public/404.html # Required for react router browser history, but helps with https://blog.pshrmn.com/how-single-page-applications-work/
artifacts:
paths:
- public # The built files for Gitlab Pages to serve
only:
- master # Only run on master branch

4
jam-ui/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"printWidth": 120
}

31
jam-ui/README.md Normal file
View File

@ -0,0 +1,31 @@
# JamKazam new react frontend UI/UX
## 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)
The DOMAIN and PORT running this app is defined in env.production file. This file also has env variables for connecting with Rails app. (When setting up in development you can copy the content of env.development.example in to env.development.local and change them according to your host setup)
HOST=beta.jamkazam.local
PORT=4000
REACT_APP_LEGACY_BASE_URL=http://www.jamkazam.local:3000
REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
## Subdomains setup (development)
You need 2 host records created for React and and Rails app. For example
127.0.0.1 www.jamkazam.local #for Rails app
127.0.0.1 beta.jamkazam.local #for React app
## Installing npm dependencies
cd jam-ui
npm install
## Running the app
cd jam-ui
npm run start
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.

6
jam-ui/cypress.env.json Normal file
View File

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

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

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

View File

@ -0,0 +1,79 @@
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"location": "Denver, CO",
"online": true,
"photo_url": null,
"musician": true,
"gender": "M",
"birth_date": null,
"friend_count": 1,
"liker_count": 0,
"follower_count": 0,
"following_count": 0,
"recording_count": 0,
"session_count": 0,
"biography": "Biography of Test User1",
"favorite_count": 0,
"audio_latency": null,
"upcoming_session_count": 0,
"age": null,
"website": "www.testuser1.com",
"skill_level": 2,
"concert_count": 4,
"studio_session_count": 4,
"virtual_band": true,
"virtual_band_commitment": 2,
"traditional_band": true,
"traditional_band_commitment": 4,
"traditional_band_touring": true,
"paid_sessions": true,
"paid_sessions_hourly_rate": 10000,
"paid_sessions_daily_rate": 200000,
"free_sessions": true,
"cowriting": true,
"cowriting_purpose": 2,
"subscribe_email": true,
"is_a_teacher": false,
"is_a_student": false,
"online_presences": [
{ "id": "e1962204-f652-41b0-84d6-1afd7e9172be", "service_type": "soundcloud", "username": "testuser" },
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b0", "service_type": "reverbnation", "username": "testuser" },
{ "id": "2dd22eef-03ba-4743-b65b-5a194591dc86", "service_type": "bandcamp", "username": "testuser" },
{ "id": "d6ae62b4-e1ce-4cf0-90b7-c64033533261", "service_type": "fandalism", "username": "testuser" },
{ "id": "c6e85453-0fa9-40d0-9754-8f372d6e0ed3", "service_type": "youtube", "username": "testuser" },
{ "id": "480ec1ad-ea1d-4990-9c68-d7f9c0174441", "service_type": "facebook", "username": "testuser" },
{ "id": "232b26d5-c75a-4d65-9013-a07b73c8a7ae", "service_type": "twitter", "username": "testuser" }
],
"performance_samples": [],
"genres": [
{ "genre_id": "asian", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "profile" },
{ "genre_id": "african", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "virtual_band" },
{ "genre_id": "classical", "player_type": "JamRuby::User", "genre_type": "traditional_band" },
{ "genre_id": "blues", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "soft rock", "player_type": "JamRuby::User", "genre_type": "free_sessions" },
{ "genre_id": "celtic", "player_type": "JamRuby::User", "genre_type": "cowriting" },
{ "genre_id": "tv & movie soundtrack", "player_type": "JamRuby::User", "genre_type": "cowriting" }
],
"bands": [],
"instruments": [
{ "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" },
{ "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" },
{ "description": "Ukulele", "proficiency_level": 3, "priority": 11, "instrument_id": "ukulele" },
{ "description": "Voice", "proficiency_level": 3, "priority": 13, "instrument_id": "voice" },
{ "description": "Piano", "proficiency_level": 2, "priority": 10, "instrument_id": "piano" }
],
"is_friend": true,
"is_following": false,
"is_liking": false,
"pending_friend_request": false,
"my_audio_latency": 5,
"internet_score": null
}

View File

@ -0,0 +1,74 @@
[
{
"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
},
{
"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
},
{
"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
}
]

View File

@ -0,0 +1,558 @@
{
"musicians": [
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"online": true,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User1. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"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
},
{
"id": "2",
"first_name": "Test",
"last_name": "User2",
"name": "Test User2",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User2.",
"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
},
{
"id": "3",
"first_name": "Test",
"last_name": "User3",
"name": "Test User3",
"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
},
{
"id": "4",
"first_name": "Test",
"last_name": "User4",
"name": "Test User4",
"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
},
{
"id": "5",
"first_name": "Test",
"last_name": "User5",
"name": "Test User5",
"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
},
{
"id": "6",
"first_name": "Test",
"last_name": "User6",
"name": "Test User6",
"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
},
{
"id": "7",
"first_name": "Test",
"last_name": "User7",
"name": "Test User7",
"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
},
{
"id": "8",
"first_name": "Test",
"last_name": "User8",
"name": "Test User8",
"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
},
{
"id": "9",
"first_name": "Test",
"last_name": "User9",
"name": "Test User9",
"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
},
{
"id": "10",
"first_name": "Test",
"last_name": "User10",
"name": "Test User10",
"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
},
{
"id": "11",
"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
},
{
"id": "12",
"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
},
{
"id": "13",
"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
},
{
"id": "14",
"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
},
{
"id": "15",
"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
},
{
"id": "16",
"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
},
{
"id": "17",
"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
},
{
"id": "18",
"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
},
{
"id": "19",
"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
},
{
"id": "20",
"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
}
],
"page_count": 2,
"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

@ -0,0 +1,81 @@
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"location": "Denver, CO",
"online": true,
"photo_url": null,
"musician": true,
"gender": "M",
"birth_date": null,
"friend_count": 1,
"liker_count": 0,
"follower_count": 0,
"following_count": 0,
"recording_count": 0,
"session_count": 0,
"biography": "Biography of Test User1",
"favorite_count": 0,
"audio_latency": null,
"upcoming_session_count": 0,
"age": null,
"website": "www.testuser1.com",
"skill_level": 2,
"concert_count": 4,
"studio_session_count": 4,
"virtual_band": true,
"virtual_band_commitment": 2,
"traditional_band": true,
"traditional_band_commitment": 4,
"traditional_band_touring": true,
"paid_sessions": true,
"paid_sessions_hourly_rate": 10000,
"paid_sessions_daily_rate": 200000,
"free_sessions": true,
"cowriting": true,
"cowriting_purpose": 2,
"subscribe_email": true,
"is_a_teacher": false,
"is_a_student": false,
"online_presences": [
{ "id": "e1962204-f652-41b0-84d6-1afd7e9172be", "service_type": "soundcloud", "username": "testuser" },
{ "id": "005a7c78-db8b-4f72-a51f-d64d579c22b0", "service_type": "reverbnation", "username": "testuser" },
{ "id": "2dd22eef-03ba-4743-b65b-5a194591dc86", "service_type": "bandcamp", "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" }
],
"bands": [
{
"id": "1",
"name": "The Band",
"admin": true,
"photo_url": "",
"logo_url": "",
"genres": [
{
"id": 1,
"name": "pop"
}
]
}
],
"instruments": [
{ "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1, "instrument_id": "acoustic guitar" },
{ "description": "Keyboard", "proficiency_level": 3, "priority": 8, "instrument_id": "keyboard" }
],
"is_friend": false,
"is_following": false,
"is_liking": false,
"pending_friend_request": false,
"my_audio_latency": 5,
"internet_score": null,
"created_at_timestamp": 1629917088,
"last_active_timestamp": 1629916641
}

View File

@ -0,0 +1,152 @@
[
{
"id": "1",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 1",
"created_at": "2021-09-20T15:15:15.970Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "2",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 2",
"created_at": "2021-09-20T15:14:48.821Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "3",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 3",
"created_at": "2021-09-20T15:13:02.367Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "4",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 4",
"created_at": "2021-09-18T21:39:58.493Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "5",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 5",
"created_at": "2021-09-18T21:37:29.752Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "6",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 6",
"created_at": "2021-09-18T21:37:22.319Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "7",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 7",
"created_at": "2021-09-18T21:37:12.366Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "8",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 8",
"created_at": "2021-09-18T21:23:50.070Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "9",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 9",
"created_at": "2021-09-18T21:23:45.271Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "10",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 10",
"created_at": "2021-09-18T21:23:07.918Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
}
]

View File

@ -0,0 +1,152 @@
[
{
"id": "11",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 11",
"created_at": "2021-09-17T15:15:15.970Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "12",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 12",
"created_at": "2021-09-17T15:14:48.821Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "13",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 13",
"created_at": "2021-09-17T15:13:02.367Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "14",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 14",
"created_at": "2021-09-15T21:39:58.493Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "15",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 15",
"created_at": "2021-09-15T21:37:29.752Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "16",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 16",
"created_at": "2021-09-15T21:37:22.319Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "17",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 17",
"created_at": "2021-09-15T21:37:12.366Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "18",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 18",
"created_at": "2021-09-15T21:23:50.070Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "19",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 19",
"created_at": "2021-09-15T21:23:45.271Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "20",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 20",
"created_at": "2021-09-15T21:23:07.918Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
}
]

View File

@ -0,0 +1,152 @@
[
{
"id": "21",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 21",
"created_at": "2021-09-14T15:15:15.970Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "22",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 22",
"created_at": "2021-09-14T15:14:48.821Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "23",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 23",
"created_at": "2021-09-17T12:13:02.367Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "24",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 24",
"created_at": "2021-09-12T21:39:58.493Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "25",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 25",
"created_at": "2021-09-12T21:37:29.752Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "26",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 26",
"created_at": "2021-09-12T21:37:22.319Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "27",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 27",
"created_at": "2021-09-12T21:37:12.366Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "28",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 28",
"created_at": "2021-09-12T21:23:50.070Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
},
{
"id": "29",
"source_user_id": "2",
"target_user_id": "1",
"message": "Test Message 29",
"created_at": "2021-09-12T21:23:45.271Z",
"source_user": {
"id": "2",
"name": "Test User2"
},
"target_user": {
"id": "1",
"name": "Test User1"
}
},
{
"id": "30",
"source_user_id": "1",
"target_user_id": "2",
"message": "Test Message 30",
"created_at": "2021-09-12T21:23:07.918Z",
"source_user": {
"id": "1",
"name": "Test User1"
},
"target_user": {
"id": "2",
"name": "Test User2"
}
}
]

View File

@ -0,0 +1,367 @@
/// <reference types="cypress" />
describe('Friends page without data', () => {
beforeEach(() => {
cy.stubAuthenticate();
cy.intercept('POST', /\S+\/filter/, {
musicians: []
});
});
it('shows no records alert', () => {
cy.visit('/friends');
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/, { fixture: 'people' }).as('getPeople');
});
describe('listing users', () => {
beforeEach(() => {
cy.visit('/friends');
});
it('lists musician users', () => {
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', 20)
.first()
.contains('Test User1');
});
//TODO: paginate
});
describe('user details side panel', () => {
const showSidePanelContent = () => {
cy.get('[data-testid=profileSidePanel] h4').should('have.text', 'Test User1');
cy.get('[data-testid=profileSidePanel] .modal-body p').within(() => {
cy.contains('Location: Denver, US')
.and('contain', 'Location: Denver, 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
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('The Band')
//online presence
cy.get('[data-testid=online_presences]').contains('Soundcloud');
cy.get('[data-testid=online_presences]').contains('Reverbnation');
});
};
const closeSidePanel = () => {
cy.get('[data-testid=profileSidePanel] .modal-header button.close').click();
};
beforeEach(() => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
cy.visit('/friends');
});
it('shows profile side panel', () => {
//open side panel by clicking name
cy.contains('Test User1').click();
showSidePanelContent();
closeSidePanel();
//open side panel by clicking more button
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=btnMore]')
.click();
showSidePanelContent();
closeSidePanel();
//open side panel by clicking more link
cy.get('[data-testid=peopleListTable] > tbody tr')
.first()
.find('[data-testid=linkMore]')
.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 User1').click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('not.be.disabled')
.click();
cy.get('[data-testid=profileSidePanel]')
.find('[data-testid=connect]')
.should('be.disabled');
cy.contains('Friend request was sent');
});
it('remove friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { 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 disabled for non friends', () => {
cy.get('[data-testid=peopleListTable] > tbody tr')
.eq(1)
.find('[data-testid=message]')
.should('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')
.trigger('mouseover')
.scrollTo('bottom');
cy.get('.modal-body .ScrollbarsCustom')
.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('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('#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('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').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data');
});
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form without filling any form field
cy.wait('@getPeople')
.then(interception => {
assert.isNotNull(interception.response.body, '2nd API call has data');
})
.its('request.body')
.should('deep.equal', {
latency_good: true,
latency_fair: true,
latency_high: true,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
instruments: [],
genres: []
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm()
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')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call has data');
})
.its('request.body')
.should('deep.equal', {
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'],
active_within_days: '1',
joined_within_days: '7'
});
});
});
});

View File

@ -0,0 +1,72 @@
/// <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()
cy.visit('/')
});
it("does not show user dropdown", () => {
cy.contains("Sign in to begin")
cy.get('a.btn').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').trigger('mouseover')
cy.stubUnauthenticate()
cy.get('[data-testid=navbarTopProfileDropdown]').contains('Sign out').click()
cy.get('[data-testid=navbarTopProfileDropdown]').should('not.exist')
cy.contains("Sign in to begin")
})
})
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').trigger('mouseover')
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')
})
})
});

View File

@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@ -0,0 +1,49 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
Cypress.Commands.add('stubAuthenticate', (attrs = {}) => {
const defaultAttrs = {
id: '1',
first_name: 'Peter',
last_name: 'Pan',
name: 'Peter Pan',
photo_url: ''
}
const currentUserAtrs = {...defaultAttrs, ...attrs}
cy.intercept('GET', `${Cypress.env('apiBaseUrl')}/me`, {
statusCode: 200,
body: currentUserAtrs
}).as('getCurrentUser')
});
Cypress.Commands.add('stubUnauthenticate', () => {
cy.intercept('GET', `${Cypress.env('apiBaseUrl')}/me`, {
statusCode: 401,
body: {}
}).as('getUnauthenticateCurrentUser')
});

View File

@ -0,0 +1,23 @@
// ***********************************************************
// 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')

Binary file not shown.

Binary file not shown.

Binary file not shown.

77
jam-ui/gulpfile.js Normal file
View File

@ -0,0 +1,77 @@
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const rtlcss = require('gulp-rtlcss');
const rename = require('gulp-rename');
const sourcemaps = require('gulp-sourcemaps');
const browserSync = require('browser-sync');
const cleanCSS = require('gulp-clean-css');
/*-----------------------------------------------
| SCSS
-----------------------------------------------*/
gulp.task('scss', () =>
gulp
.src('src/assets/scss/*.scss')
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(
sass({
outputStyle: 'expanded'
}).on('error', sass.logError)
)
.pipe(autoprefixer({ cascade: false }))
.pipe(cleanCSS({ compatibility: 'ie9' }))
.pipe(sourcemaps.write('.'))
.pipe(plumber.stop())
.pipe(gulp.dest('public/css'))
.pipe(browserSync.stream())
);
gulp.task('scss:dark', () =>
gulp
.src('src/assets/scss/theme-dark.scss')
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(
sass({
outputStyle: 'expanded'
}).on('error', sass.logError)
)
.pipe(autoprefixer({ cascade: false }))
.pipe(cleanCSS({ compatibility: 'ie9' }))
.pipe(sourcemaps.write('.'))
.pipe(plumber.stop())
.pipe(gulp.dest('public/css'))
.pipe(browserSync.stream())
);
gulp.task('scss:rtl', () =>
gulp
.src('src/assets/scss/*.scss')
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(
sass({
outputStyle: 'expanded'
}).on('error', sass.logError)
)
.pipe(autoprefixer({ cascade: false }))
.pipe(cleanCSS({ compatibility: 'ie9' }))
.pipe(rtlcss()) // Convert to RTL.
.pipe(rename({ suffix: '-rtl' })) // Append "-rtl" to the filename.
.pipe(sourcemaps.write('.'))
.pipe(plumber.stop())
.pipe(gulp.dest('public/css'))
.pipe(browserSync.stream())
);
/*-----------------------------------------------
| Watching
-----------------------------------------------*/
gulp.task('watch', () => {
gulp.watch('src/assets/scss/**/*.scss', gulp.parallel('scss', 'scss:rtl'));
});
gulp.task('default', gulp.parallel('scss', 'scss:rtl', 'watch', 'scss:dark'));

20387
jam-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

106
jam-ui/package.json Normal file
View File

@ -0,0 +1,106 @@
{
"name": "falcon-react",
"version": "2.10.2",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-brands-svg-icons": "^5.14.0",
"@fortawesome/free-regular-svg-icons": "^5.14.0",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/react-fontawesome": "^0.1.11",
"@fullcalendar/bootstrap": "^5.3.1",
"@fullcalendar/core": "^5.3.1",
"@fullcalendar/daygrid": "^5.3.2",
"@fullcalendar/interaction": "^5.3.1",
"@fullcalendar/list": "^5.3.1",
"@fullcalendar/react": "^5.3.1",
"@fullcalendar/timegrid": "^5.3.1",
"@loadable/component": "^5.13.2",
"@reduxjs/toolkit": "^1.6.1",
"attr-accept": "^2.2.2",
"bootstrap": "^4.5.3",
"chance": "^1.1.8",
"chart.js": "^2.9.3",
"classnames": "^2.2.6",
"echarts": "^4.9.0",
"echarts-for-react": "^2.0.16",
"element-resize-event": "^3.0.3",
"emoji-mart": "^3.0.0",
"fuse.js": "^6.4.3",
"google-maps-react": "^2.0.6",
"is_js": "^0.9.0",
"leaflet": "^1.7.1",
"leaflet.markercluster": "^1.4.1",
"leaflet.tilelayer.colorfilter": "^1.2.5",
"lodash": "^4.17.20",
"moment": "^2.28.0",
"plyr": "3.6.2",
"prism-react-renderer": "^0.1.7",
"prism-themes": "^1.4.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-app-polyfill": "^1.0.6",
"react-beautiful-dnd": "^13.0.0",
"react-bootstrap-table-next": "^3.3.5",
"react-bootstrap-table2-paginator": "^2.1.2",
"react-chartjs-2": "^2.10.0",
"react-cookie": "^4.0.3",
"react-countup": "^4.3.3",
"react-datetime": "^2.16.3",
"react-dom": "^16.13.1",
"react-dropzone": "^10.2.2",
"react-es6-progressbar.js": "^1.1.0",
"react-flatpickr": "^3.10.6",
"react-hook-form": "^7.11.1",
"react-image-lightbox": "^5.1.1",
"react-image-video-lightbox": "^2.0.1",
"react-leaflet": "^2.7.0",
"react-live": "^2.2.2",
"react-lottie": "^1.2.3",
"react-quill": "^1.3.5",
"react-rating": "^2.0.5",
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.3",
"react-scroll": "^1.8.1",
"react-scrollbars-custom": "^4.0.25",
"react-select": "^3.1.0",
"react-simple-code-editor": "^0.9.15",
"react-slick": "^0.25.2",
"react-timeago": "^6.2.1",
"react-toastify": "^5.5.0",
"react-typed": "^1.2.0",
"reactstrap": "^8.6.0",
"slick-carousel": "^1.8.1",
"uuid": "^3.4.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"scss": "gulp",
"analyze": "npx source-map-explorer 'build/static/js/*.js'",
"test": "playwright test"
},
"eslintConfig": {
"extends": "react-app"
},
"devDependencies": {
"@playwright/test": "^1.15.2",
"browser-sync": "^2.26.12",
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^6.1.0",
"gulp-clean-css": "^4.3.0",
"gulp-plumber": "^1.2.1",
"gulp-rename": "^1.4.0",
"gulp-rtlcss": "^1.4.1",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.5",
"prettier": "1.17.1",
"swiper": "^6.8.2"
}
}

View File

@ -0,0 +1,57 @@
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
globalSetup: require.resolve('./test/config/global-setup'),
use: {
baseURL: 'http://beta.jamkazam.local:4000',
actionTimeout: 5000,
headless: true,
viewport: { width: 1280, height: 720},
ignoreHTTPSErrors: true,
video: 'on-first-retry',
},
};
export default config;
// const { devices } = require('@playwright/test');
// /** @type {import('@playwright/test').PlaywrightTestConfig} */
// const config = {
// testDir: './test',
// workers: 2,
// retries: 2,
// use: {
// headless: false,
// viewport: { width: 1280, height: 720 },
// launchOptions: {
// slowMo: 1000,
// },
// video:"on",
// },
// projects: [
// {
// name: 'Desktop Chromium',
// use: {
// browserName: 'chromium',
// },
// },
// { name: 'Desktop Safari',
// use: {
// browserName: 'webkit',
// viewport: { width: 1200, height: 750 },
// }
// },
// // Test against mobile viewports.
// { name: 'Mobile Chrome',
// use: devices['Pixel 5'],
// },
// {
// name: 'Mobile Safari',
// use: devices['iPhone 12'],
// },
// ],
// };
// module.exports = config;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
jam-ui/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

21
jam-ui/public/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#2c7be5" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Poppins:100,200,300,400,500,600,700,800,900&display=swap"
/>
<title>JamKazam</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<main class="main" id="main"></main>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"short_name": "Falcon React",
"name": "Falcon React | ReactJS Dashboard & WebApp Template",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#2c7be5",
"background_color": "#edf2f9"
}

17
jam-ui/server.js Normal file
View File

@ -0,0 +1,17 @@
const compression = require('compression');
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(compression());
app.disable('x-powered-by');
app.use(express.static(path.join(__dirname, 'build')));
// need to declare a "catch all" route on your express server
// that captures all page requests and directs them to the client
// the react-router do the route part
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(process.env.PORT || 5000, function() {
console.log(`Frontend start on http://localhost:5000`);
});

18
jam-ui/src/App.js Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Layout from './layouts/JKLayout';
import 'react-toastify/dist/ReactToastify.min.css';
import 'react-datetime/css/react-datetime.css';
import 'react-image-lightbox/style.css';
const App = () => {
return (
<Router basename={process.env.PUBLIC_URL}>
<Layout />
</Router>
);
};
export default App;

134
jam-ui/src/Main.js Normal file
View File

@ -0,0 +1,134 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import AppContext from './context/Context';
import { AuthProvider } from "./context/AuthContext";
import { settings } from './config';
import toggleStylesheet from './helpers/toggleStylesheet';
import { getItemFromStore, setItemToStore, themeColors } from './helpers/utils';
import store from './store/store';
import { Provider } from 'react-redux';
import { CookiesProvider } from 'react-cookie';
const Main = props => {
const [isFluid, setIsFluid] = useState(getItemFromStore('isFluid', settings.isFluid));
const [isRTL, setIsRTL] = useState(getItemFromStore('isRTL', settings.isRTL));
const [isDark, setIsDark] = useState(getItemFromStore('isDark', settings.isDark));
const [isTopNav, setIsTopNav] = useState(getItemFromStore('isTopNav', settings.isTopNav));
const [isCombo, setIsCombo] = useState(getItemFromStore('isCombo', settings.isCombo));
const [isVertical, setIsVertical] = useState(getItemFromStore('isVertical', settings.isVertical));
const [isNavbarVerticalCollapsed, setIsNavbarVerticalCollapsed] = useState(
getItemFromStore('isNavbarVerticalCollapsed', settings.isNavbarVerticalCollapsed)
);
const [currency, setCurrency] = useState(settings.currency);
const [showBurgerMenu, setShowBurgerMenu] = useState(settings.showBurgerMenu);
const [isLoaded, setIsLoaded] = useState(false);
const [isOpenSidePanel, setIsOpenSidePanel] = useState(false);
const [navbarCollapsed, setNavbarCollapsed] = useState(false);
const [navbarStyle, setNavbarStyle] = useState(getItemFromStore('navbarStyle', settings.navbarStyle));
const toggleModal = () => setIsOpenSidePanel(prevIsOpenSidePanel => !prevIsOpenSidePanel);
const value = {
isRTL,
isDark,
isCombo,
isFluid,
setIsRTL,
isTopNav,
currency,
setIsDark,
setIsCombo,
setIsFluid,
isVertical,
toggleModal,
setIsTopNav,
navbarStyle,
setCurrency,
setIsVertical,
showBurgerMenu,
setNavbarStyle,
isOpenSidePanel,
navbarCollapsed,
setShowBurgerMenu,
setIsOpenSidePanel,
setNavbarCollapsed,
isNavbarVerticalCollapsed,
setIsNavbarVerticalCollapsed
};
const setStylesheetMode = mode => {
setIsLoaded(false);
setItemToStore(mode, value[mode]);
toggleStylesheet({ isRTL, isDark }, () => setIsLoaded(true));
};
useEffect(() => {
setStylesheetMode('isFluid');
// eslint-disable-next-line
}, [isFluid]);
useEffect(() => {
setStylesheetMode('isRTL');
// eslint-disable-next-line
}, [isRTL]);
useEffect(() => {
setStylesheetMode('isDark');
// eslint-disable-next-line
}, [isDark]);
useEffect(() => {
setItemToStore('isNavbarVerticalCollapsed', isNavbarVerticalCollapsed);
// eslint-disable-next-line
}, [isNavbarVerticalCollapsed]);
useEffect(() => {
setItemToStore('isTopNav', isTopNav);
// eslint-disable-next-line
}, [isTopNav]);
useEffect(() => {
setItemToStore('isCombo', isCombo);
// eslint-disable-next-line
}, [isCombo]);
useEffect(() => {
setItemToStore('isVertical', isVertical);
// eslint-disable-next-line
}, [isVertical]);
useEffect(() => {
setItemToStore('navbarStyle', navbarStyle);
// eslint-disable-next-line
}, [navbarStyle]);
if (!isLoaded) {
toggleStylesheet({ isRTL, isDark }, () => setIsLoaded(true));
return (
<div
style={{
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
backgroundColor: isDark ? themeColors.dark : themeColors.light
}}
/>
);
}
return <AppContext.Provider value={value}>
<CookiesProvider>
<AuthProvider>
<Provider store={store}>
{props.children}
</Provider>
</AuthProvider>
</CookiesProvider>
</AppContext.Provider>;
};
Main.propTypes = { children: PropTypes.node };
export default Main;

BIN
jam-ui/src/assets/img/chat/1.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
jam-ui/src/assets/img/chat/10.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
jam-ui/src/assets/img/chat/11.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
jam-ui/src/assets/img/chat/12.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

BIN
jam-ui/src/assets/img/chat/2.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

BIN
jam-ui/src/assets/img/chat/3.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
jam-ui/src/assets/img/chat/4.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
jam-ui/src/assets/img/chat/5.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
jam-ui/src/assets/img/chat/6.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

BIN
jam-ui/src/assets/img/chat/7.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
jam-ui/src/assets/img/chat/8.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

BIN
jam-ui/src/assets/img/chat/9.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,18 @@
{
"name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

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