Merge branch 'update-pipeline' into 'master'
Update pipeline and add integration tests See merge request adm3981141/fids!2
This commit is contained in:
commit
98bacab6a9
|
@ -7,4 +7,5 @@ node_modules
|
|||
*.xml
|
||||
notification-domain/
|
||||
TODO.txt
|
||||
*.sh
|
||||
*.sh
|
||||
coverage/
|
131
.gitlab-ci.yml
131
.gitlab-ci.yml
|
@ -28,8 +28,8 @@ preparation:
|
|||
- echo "USER_MANAGER_PROD_IMAGE_NAME=${IMAGE_BASE}/user-manager:prod-${BUILD_ID}" >> context.env
|
||||
- echo "USER_MANAGER_TEST_IMAGE_NAME=${IMAGE_BASE}/user-manager:test-${BUILD_ID}" >> context.env
|
||||
|
||||
- echo "SUBSCRIPTION_PROD_IMAGE_NAME=${IMAGE_BASE}/screens-client:prod-${BUILD_ID}" >> context.env
|
||||
- echo "SUBSCRIPTION_TEST_IMAGE_NAME=${IMAGE_BASE}/screens-client:test-${BUILD_ID}" >> context.env
|
||||
- echo "SUBSCRIPTION_PROD_IMAGE_NAME=${IMAGE_BASE}/subs-manager:prod-${BUILD_ID}" >> context.env
|
||||
- echo "SUBSCRIPTION_TEST_IMAGE_NAME=${IMAGE_BASE}/subs-manager:test-${BUILD_ID}" >> context.env
|
||||
|
||||
- echo "SCREEN_CLIENT_PROD_IMAGE_NAME=${IMAGE_BASE}/screens-client:prod-${BUILD_ID}" >> context.env
|
||||
- echo "SCREEN_CLIENT_TEST_IMAGE_NAME=${IMAGE_BASE}/screens-client:test-${BUILD_ID}" >> context.env
|
||||
|
@ -285,35 +285,64 @@ test-gateway:
|
|||
- job: build-gateway
|
||||
artifacts: true
|
||||
|
||||
test-integration:
|
||||
test-browser-client:
|
||||
stage: test
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
tags:
|
||||
- dev
|
||||
script:
|
||||
- export $(cat context.env | xargs)
|
||||
|
||||
- docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY
|
||||
|
||||
- export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME
|
||||
- export TEST_TARGET=INTEGRATION
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME
|
||||
- export TEST_TARGET=INTEGRATION
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME
|
||||
- export TEST_TARGET=INTEGRATION
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
- export CLIENT_IMAGE=$BROWSER_CLIENT_TEST_IMAGE_NAME
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit
|
||||
- docker cp fids_browser_client:/app/coverage/cobertura-coverage.xml .
|
||||
- docker cp fids_browser_client:/app/junit.xml .
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- cobertura-coverage.xml
|
||||
reports:
|
||||
junit: junit.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: cobertura-coverage.xml
|
||||
needs:
|
||||
- job: test-flights-api
|
||||
- job: test-auth-api
|
||||
- job: preparation
|
||||
- job: build-browser-client
|
||||
artifacts: true
|
||||
|
||||
test-screen-client:
|
||||
stage: test
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
tags:
|
||||
- dev
|
||||
script:
|
||||
- export $(cat context.env | xargs)
|
||||
|
||||
- docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY
|
||||
|
||||
- export CLIENT_IMAGE=$SCREEN_CLIENT_TEST_IMAGE_NAME
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit
|
||||
- docker cp fids_screens_client:/app/coverage/cobertura-coverage.xml .
|
||||
- docker cp fids_screens_client:/app/junit.xml .
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- cobertura-coverage.xml
|
||||
reports:
|
||||
junit: junit.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: cobertura-coverage.xml
|
||||
needs:
|
||||
- job: preparation
|
||||
- job: build-screen-client
|
||||
artifacts: true
|
||||
|
||||
test-browser-integration:
|
||||
|
@ -323,14 +352,39 @@ test-browser-integration:
|
|||
script:
|
||||
- export $(cat context.env | xargs)
|
||||
- docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY
|
||||
|
||||
- export TEST_TARGET=INTEGRATION
|
||||
|
||||
- export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py recreate_db
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py seed_db
|
||||
|
||||
- export API_IMAGE=$SUBSCRIPTION_TEST_IMAGE_NAME
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export CLIENT_IMAGE=$BROWSER_CLIENT_TEST_IMAGE_NAME
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f browser-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit
|
||||
needs:
|
||||
- job: test-integration
|
||||
- job: build-browser-client
|
||||
- job: test-flights-api
|
||||
- job: test-auth-api
|
||||
- job: test-browser-client
|
||||
- job: preparation
|
||||
artifacts: true
|
||||
|
||||
|
@ -341,14 +395,39 @@ test-screen-integration:
|
|||
script:
|
||||
- export $(cat context.env | xargs)
|
||||
- docker login -u $CI_REGISTRY_USER --password $CI_JOB_TOKEN $CI_REGISTRY
|
||||
|
||||
- export TEST_TARGET=INTEGRATION
|
||||
|
||||
- export API_IMAGE=$FLIGHTS_INFO_TEST_IMAGE_NAME
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f flights-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$GATEWAY_TEST_IMAGE_NAME
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f gateway/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export API_IMAGE=$USER_MANAGER_TEST_IMAGE_NAME
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py recreate_db
|
||||
- docker compose -f auth-domain/docker-compose.yml --env-file $ENV_DEV_FILE exec usermanager-api python manage.py seed_db
|
||||
|
||||
- export API_IMAGE=$SUBSCRIPTION_TEST_IMAGE_NAME
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f subscription-domain/docker-compose.yml --env-file $ENV_DEV_FILE up -d
|
||||
|
||||
- export CLIENT_IMAGE=$SCREEN_CLIENT_TEST_IMAGE_NAME
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE down
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE pull
|
||||
- docker compose -f screen-domain/docker-compose.yml --env-file $ENV_DEV_FILE up --abort-on-container-exit
|
||||
needs:
|
||||
- job: test-integration
|
||||
- job: build-screen-client
|
||||
- job: test-flights-api
|
||||
- job: test-auth-api
|
||||
- job: test-screen-client
|
||||
- job: preparation
|
||||
artifacts: true
|
||||
|
||||
|
|
|
@ -7,4 +7,5 @@ services:
|
|||
restart: always
|
||||
environment:
|
||||
- API_HOST=api
|
||||
- TEST_TARGET=${TEST_TARGET}
|
||||
network_mode: host
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
coverageReporters: ["html", "text", "text-summary", "cobertura"],
|
||||
};
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
"@types/react-dom": "^18.0.11",
|
||||
"jest": "^28.0.0",
|
||||
"jest-environment-jsdom": "^28.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^28.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
@ -5703,6 +5705,12 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
|
@ -5809,6 +5817,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
|
@ -6498,6 +6512,16 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
@ -7716,6 +7740,12 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
|
@ -8099,6 +8129,21 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
@ -8499,6 +8544,15 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
|
@ -10331,6 +10385,33 @@
|
|||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/jest-junit": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-junit/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-leak-detector": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz",
|
||||
|
@ -16922,6 +17003,85 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
|
||||
"integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0 <13 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -18575,6 +18735,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
@ -22672,6 +22838,12 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
|
@ -22762,6 +22934,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
|
@ -23243,6 +23421,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
@ -24155,6 +24343,12 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
|
@ -24432,6 +24626,18 @@
|
|||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
@ -24703,6 +24909,12 @@
|
|||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true
|
||||
},
|
||||
"hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
|
@ -26050,6 +26262,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jest-junit": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-leak-detector": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz",
|
||||
|
@ -30698,6 +30930,66 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"superagent": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
|
||||
"integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -31940,6 +32232,12 @@
|
|||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"requires": {}
|
||||
},
|
||||
"xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"dev": true
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "sample-client-users",
|
||||
"name": "browser-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -16,11 +16,9 @@
|
|||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "jest --coverage --collectCoverageFrom=\"./src/**\"",
|
||||
"test:integration": "jest integration",
|
||||
"eject": "react-scripts eject",
|
||||
"docker:build": "docker build -t client-users .",
|
||||
"docker:run": " docker run --rm -it -p 8080:80 client-users"
|
||||
"test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**\" --reporters=default --reporters=jest-junit",
|
||||
"test:integration": "jest integration --testPathPattern=src/tests/integration",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
@ -50,6 +48,8 @@
|
|||
"@types/react-dom": "^18.0.11",
|
||||
"jest": "^28.0.0",
|
||||
"jest-environment-jsdom": "^28.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^28.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
|
|
@ -2,55 +2,55 @@ import { Axios, AxiosError } from "axios";
|
|||
import { Credentials, Token, User, Flight, FlightCreate } from "./Types";
|
||||
|
||||
const instance = new Axios({
|
||||
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
validateStatus: (x) => { return !(x < 200 || x > 204) }
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((request) => {
|
||||
request.data = JSON.stringify(request.data);
|
||||
return request;
|
||||
request.data = JSON.stringify(request.data);
|
||||
return request;
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
return JSON.parse(response.data);
|
||||
},
|
||||
(error) => {
|
||||
const err = error as AxiosError;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
(response) => {
|
||||
return JSON.parse(response.data);
|
||||
},
|
||||
(error) => {
|
||||
const err = error as AxiosError;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
export const createUser = (
|
||||
credentials: Credentials
|
||||
credentials: Credentials
|
||||
): Promise<{ id?: string; message: string }> => {
|
||||
return instance.post("users", credentials);
|
||||
return instance.post("users", credentials);
|
||||
};
|
||||
|
||||
export const fetchUsers = (): Promise<User[]> => {
|
||||
return instance.get("users");
|
||||
return instance.get("users");
|
||||
};
|
||||
|
||||
export const fetchUserById = (id: number): Promise<User> => {
|
||||
return instance.get("users/" + id);
|
||||
return instance.get("users/" + id);
|
||||
};
|
||||
|
||||
export const logIn = (
|
||||
credentials: Credentials
|
||||
credentials: Credentials
|
||||
): Promise<Token & Partial<{ message: string; user_id: number }>> => {
|
||||
return instance.post("auth/login", credentials);
|
||||
return instance.post("auth/login", credentials);
|
||||
};
|
||||
|
||||
export const tokenStatus = (
|
||||
token: string
|
||||
token: string
|
||||
): Promise<User & { message?: string }> => {
|
||||
return instance.get("auth/status", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return instance.get("auth/status", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchZones = (origin: string | null): Promise<Flight[]> => {
|
||||
|
@ -58,10 +58,10 @@ export const fetchZones = (origin: string | null): Promise<Flight[]> => {
|
|||
};
|
||||
|
||||
export const createFlight = (
|
||||
flight_data: FlightCreate,
|
||||
flight_data: FlightCreate,
|
||||
token: string
|
||||
): Promise<Flight> => {
|
||||
return instance.post("flights", flight_data, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return instance.post("flights", flight_data, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
};
|
|
@ -5,18 +5,18 @@ import { Home } from "./components/Home/Home";
|
|||
import { CreateFlight } from "./components/CreateFlight/CreateFlight";
|
||||
import { Button } from "antd";
|
||||
import useAuth, { AuthProvider } from "./useAuth";
|
||||
|
||||
function Router() {
|
||||
|
||||
function Router() {
|
||||
const { user, logout, isAirline } = useAuth();
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="/login" element={<LogIn />} />
|
||||
<Route path="/signup" element={<SignUp />} />
|
||||
<Route path="/home" element={!user ? <LogIn/> :<Home/>} />
|
||||
<Route path="/create-flight" element={!isAirline ? <LogIn/> :<CreateFlight/>} />
|
||||
<Route path="/" element={!user ? <LogIn/> :<Home/>} />
|
||||
<Route path="/home" element={!user ? <LogIn /> : <Home />} />
|
||||
<Route path="/create-flight" element={!isAirline ? <LogIn /> : <CreateFlight />} />
|
||||
<Route path="/" element={!user ? <LogIn /> : <Home />} />
|
||||
</Routes>
|
||||
<div className="LogoutButton">
|
||||
{
|
||||
|
@ -28,16 +28,16 @@ import useAuth, { AuthProvider } from "./useAuth";
|
|||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
return (
|
||||
<AuthProvider>
|
||||
<Router/>
|
||||
<Router />
|
||||
</AuthProvider>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
export interface Credentials {
|
||||
password: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface Token {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export interface TokenData {
|
||||
sub: string;
|
||||
airline: boolean;
|
||||
sub: string;
|
||||
airline: boolean;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
created_date?: Date;
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
created_date?: Date;
|
||||
}
|
||||
|
||||
export interface Zone {
|
||||
id: number;
|
||||
name: string;
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Flight {
|
||||
|
@ -35,9 +35,9 @@ export interface Flight {
|
|||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FlightCreate {
|
||||
export interface FlightCreate {
|
||||
flight_code: string;
|
||||
status: string;
|
||||
origin: string;
|
||||
|
@ -45,4 +45,4 @@ export interface Flight {
|
|||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
}
|
||||
}
|
|
@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react";
|
|||
import { Button } from "antd";
|
||||
|
||||
describe("Button Component Test", () => {
|
||||
test("Display button label and clicked", async () => {
|
||||
const onClick = jest.fn();
|
||||
test("Display button label and clicked", async () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
render(<Button onClick={() => onClick()}>Button</Button>);
|
||||
render(<Button onClick={() => onClick()}>Button</Button>);
|
||||
|
||||
expect(screen.getByText("Button")).toBeVisible();
|
||||
await userEvent.click(screen.getByText("Button"));
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
expect(screen.getByText("Button")).toBeVisible();
|
||||
await userEvent.click(screen.getByText("Button"));
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,9 @@ import "./FlightForm.css";
|
|||
import { createFlight } from "../../Api";
|
||||
|
||||
export const CreateFlight = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const origin = urlParams.get('origin');
|
||||
const navigate = useNavigate();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [flight, setFlight] = useState<Flight>();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [flight, setFlight] = useState<Flight>();
|
||||
|
||||
const [flightData, setFlightData] = useState<FlightCreate>({
|
||||
flight_code: "ABC123",
|
||||
|
@ -19,9 +17,9 @@ export const CreateFlight = () => {
|
|||
departure_time: "2023-10-09 10:00 AM",
|
||||
arrival_time: "2023-10-09 12:00 PM",
|
||||
gate: "A1",
|
||||
});
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
});
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
setError(null);
|
||||
|
@ -40,79 +38,79 @@ export const CreateFlight = () => {
|
|||
.catch((error) => {
|
||||
setError(error as string);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label>
|
||||
Flight Code:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.flight_code}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, flight_code: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Status:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.status}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, status: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Origin:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.origin}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, origin: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Destination:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.destination}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, destination: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Departure Time:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.departure_time}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, departure_time: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Arrival Time:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.arrival_time}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, arrival_time: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Gate:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.gate}
|
||||
onChange={(e) => setFlightData({ ...flightData, gate: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
);
|
||||
<label>
|
||||
Flight Code:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.flight_code}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, flight_code: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Status:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.status}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, status: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Origin:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.origin}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, origin: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Destination:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.destination}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, destination: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Departure Time:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.departure_time}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, departure_time: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Arrival Time:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.arrival_time}
|
||||
onChange={(e) =>
|
||||
setFlightData({ ...flightData, arrival_time: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Gate:
|
||||
<input
|
||||
type="text"
|
||||
value={flightData.gate}
|
||||
onChange={(e) => setFlightData({ ...flightData, gate: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,26 +5,26 @@
|
|||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
label {
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -5,10 +5,10 @@ import "../../../matchMedia.mock";
|
|||
import { Card } from "./Card";
|
||||
|
||||
describe("Card Component Test", () => {
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(<Card name="Belgrano" />);
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(<Card name="Belgrano" />);
|
||||
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("B")).toBeVisible();
|
||||
});
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("B")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,59 +5,59 @@ import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined,
|
|||
import "./Card.css";
|
||||
|
||||
interface FlightProps {
|
||||
flight_code: string;
|
||||
status: string;
|
||||
origin: string;
|
||||
destination: string;
|
||||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
flight_code: string;
|
||||
status: string;
|
||||
origin: string;
|
||||
destination: string;
|
||||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
}
|
||||
|
||||
interface CardProps {
|
||||
flight: FlightProps;
|
||||
flight: FlightProps;
|
||||
}
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const Card: React.FC<CardProps> = ({ flight }) => {
|
||||
return (
|
||||
<div className="flight-card">
|
||||
<Space size={8} align="center">
|
||||
<Avatar size={64} icon={<RightOutlined />} />
|
||||
<div>
|
||||
<Text strong>{flight.flight_code}</Text>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
{flight.origin} <SwapOutlined /> {flight.destination}
|
||||
</Text>
|
||||
</div>
|
||||
return (
|
||||
<div className="flight-card">
|
||||
<Space size={8} align="center">
|
||||
<Avatar size={64} icon={<RightOutlined />} />
|
||||
<div>
|
||||
<Text strong>{flight.flight_code}</Text>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
{flight.origin} <SwapOutlined /> {flight.destination}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
<div className="flight-details">
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Status:</Text>
|
||||
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Departure:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.departure_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Arrival:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.arrival_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Gate:</Text>
|
||||
<Text>{flight.gate}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
<div className="flight-details">
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Status:</Text>
|
||||
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Departure:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.departure_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Arrival:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.arrival_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Gate:</Text>
|
||||
<Text>{flight.gate}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const mockedUsedNavigate = jest.fn();
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useNavigate: () => mockedUsedNavigate,
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useNavigate: () => mockedUsedNavigate,
|
||||
}));
|
||||
|
||||
import "../../matchMedia.mock";
|
||||
|
@ -11,18 +11,18 @@ import { render, screen } from "@testing-library/react";
|
|||
import { Home } from "./Home";
|
||||
|
||||
describe("Home View Test", () => {
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(
|
||||
// <Home
|
||||
// zones={[
|
||||
// { id: 1, name: "Belgrano" },
|
||||
// { id: 2, name: "San Isidro" },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(
|
||||
// <Home
|
||||
// zones={[
|
||||
// { id: 1, name: "Belgrano" },
|
||||
// { id: 2, name: "San Isidro" },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
|
||||
// expect(screen.getByText("Zones")).toBeVisible();
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("San Isidro📍")).toBeVisible();
|
||||
});
|
||||
// expect(screen.getByText("Zones")).toBeVisible();
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("San Isidro📍")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { Card } from "./Card/Card";
|
||||
import { useFetchZones } from "../../hooks/useFetchZones";
|
||||
import { Flight } from "../../Types";
|
||||
|
@ -6,13 +6,13 @@ import { useNavigate } from "react-router";
|
|||
import useAuth from "../../useAuth";
|
||||
|
||||
interface Props {
|
||||
flights?: Flight[];
|
||||
flights?: Flight[];
|
||||
}
|
||||
|
||||
export const Home: React.FC<Props> = (props) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const origin = urlParams.get('origin');
|
||||
const { zones, error } = useFetchZones(origin);
|
||||
const { zones, error } = useFetchZones(origin);
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { loading, isAirline } = useAuth();
|
||||
|
@ -21,16 +21,16 @@ export const Home: React.FC<Props> = (props) => {
|
|||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Box">
|
||||
{isAirline ? <button onClick={() => { console.log("ANA"); navigate("/create-flight") }}>CREATE FLIGHT</button> : <></>}
|
||||
<h2>Flights</h2>
|
||||
<div className="Items">
|
||||
{(props.flights ? props.flights : zones).map((u) => {
|
||||
return <Card key={u.id} flight={u} />;
|
||||
})}
|
||||
{error ? <div className="Disconnected">{error}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="Box">
|
||||
{isAirline ? <button onClick={() => { navigate("/create-flight") }}>CREATE FLIGHT</button> : <></>}
|
||||
<h2>Flights</h2>
|
||||
<div className="Items">
|
||||
{(props.flights ? props.flights : zones).map((u) => {
|
||||
return <Card key={u.id} flight={u} />;
|
||||
})}
|
||||
{error ? <div className="Disconnected">{error}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,42 +4,37 @@ import useAuth from "../../useAuth";
|
|||
|
||||
export const LogIn = () => {
|
||||
const { login, loading, error } = useAuth();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
return (
|
||||
<div className="Box Small">
|
||||
<div className="Section">
|
||||
<img
|
||||
alt="logo"
|
||||
className="Image"
|
||||
src="https://www.seekpng.com/png/full/353-3537757_logo-itba.png"
|
||||
/>
|
||||
<div className="Section">
|
||||
<Input
|
||||
placeholder="User"
|
||||
onChange={(ev) => setEmail(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Password"
|
||||
onChange={(ev) => setPassword(ev.target.value)}
|
||||
/>
|
||||
<Button
|
||||
style={{ width: "100%" }}
|
||||
onClick={async () =>
|
||||
login({email, password})
|
||||
}
|
||||
loading={loading}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
{error ? (
|
||||
<div className="Disconnected">{error}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="Box Small">
|
||||
<div className="Section">
|
||||
<div className="Section">
|
||||
<Input
|
||||
placeholder="User"
|
||||
onChange={(ev) => setEmail(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Password"
|
||||
onChange={(ev) => setPassword(ev.target.value)}
|
||||
/>
|
||||
<Button
|
||||
style={{ width: "100%" }}
|
||||
onClick={async () =>
|
||||
login({ email, password })
|
||||
}
|
||||
loading={loading}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
{error ? (
|
||||
<div className="Disconnected">{error}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,61 +3,56 @@ import { Button, Input } from "antd";
|
|||
import { useCreateUser } from "../../hooks/useCreateUser";
|
||||
|
||||
export const SignUp = () => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [repeatPassword, setRepeatPassword] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [repeatPassword, setRepeatPassword] = useState("");
|
||||
|
||||
const { createUser, isLoading, error } = useCreateUser();
|
||||
const { createUser, isLoading, error } = useCreateUser();
|
||||
|
||||
return (
|
||||
<div className="Box Small">
|
||||
<div className="Section">
|
||||
<img
|
||||
alt="logo"
|
||||
className="Image"
|
||||
src="https://www.seekpng.com/png/full/353-3537757_logo-itba.png"
|
||||
/>
|
||||
<div className="Section">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={(ev) => setEmail(ev.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Username"
|
||||
onChange={(ev) => setUsername(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Password"
|
||||
onChange={(ev) => setPassword(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Repeat password"
|
||||
onChange={(ev) => setRepeatPassword(ev.target.value)}
|
||||
/>
|
||||
<Button
|
||||
style={{ width: "100%" }}
|
||||
onClick={async () =>
|
||||
await createUser({ email, password, username })
|
||||
}
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
email === "" ||
|
||||
username === "" ||
|
||||
password === "" ||
|
||||
password !== repeatPassword
|
||||
}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
{error ? (
|
||||
<div className="Disconnected">{error}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="Box Small">
|
||||
<div className="Section">
|
||||
<div className="Section">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={(ev) => setEmail(ev.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Username"
|
||||
onChange={(ev) => setUsername(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Password"
|
||||
onChange={(ev) => setPassword(ev.target.value)}
|
||||
/>
|
||||
<Input.Password
|
||||
placeholder="Repeat password"
|
||||
onChange={(ev) => setRepeatPassword(ev.target.value)}
|
||||
/>
|
||||
<Button
|
||||
style={{ width: "100%" }}
|
||||
onClick={async () =>
|
||||
await createUser({ email, password, username })
|
||||
}
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
email === "" ||
|
||||
username === "" ||
|
||||
password === "" ||
|
||||
password !== repeatPassword
|
||||
}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
{error ? (
|
||||
<div className="Disconnected">{error}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,11 +4,11 @@ import { User, Flight, FlightCreate } from "../Types";
|
|||
import { createFlight } from "../Api";
|
||||
|
||||
export const useCreateFlight = (flight_data: FlightCreate) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [flight, setFlight] = useState<Flight>();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [flight, setFlight] = useState<Flight>();
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
|
@ -16,14 +16,14 @@ export const useCreateFlight = (flight_data: FlightCreate) => {
|
|||
return;
|
||||
}
|
||||
|
||||
createFlight(flight_data, token)
|
||||
.then((data) => {
|
||||
setFlight(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error as string);
|
||||
});
|
||||
}, []);
|
||||
createFlight(flight_data, token)
|
||||
.then((data) => {
|
||||
setFlight(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error as string);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { flight, error };
|
||||
return { flight, error };
|
||||
};
|
||||
|
|
|
@ -4,28 +4,28 @@ import { createUser as createUserAPI } from "../Api";
|
|||
import useAuth from "../useAuth";
|
||||
|
||||
export const useCreateUser = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { login } = useAuth();
|
||||
|
||||
const createUser = async (credentials: Credentials): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const createUser = async (credentials: Credentials): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const createResponse = await createUserAPI(credentials);
|
||||
const createResponse = await createUserAPI(credentials);
|
||||
|
||||
if (createResponse.id) {
|
||||
login(credentials);
|
||||
} else {
|
||||
setError(createResponse.message);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
if (createResponse.id) {
|
||||
login(credentials);
|
||||
} else {
|
||||
setError(createResponse.message);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { createUser, isLoading, error };
|
||||
return { createUser, isLoading, error };
|
||||
};
|
||||
|
|
|
@ -4,18 +4,18 @@ import { User, Flight } from "../Types";
|
|||
import { fetchZones } from "../Api";
|
||||
|
||||
export const useFetchZones = (origin: string | null) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [zones, setZones] = useState<Flight[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [zones, setZones] = useState<Flight[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
|
||||
fetchZones(origin)
|
||||
.then((data) => {
|
||||
setZones(data);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, []);
|
||||
fetchZones(origin)
|
||||
.then((data) => {
|
||||
setZones(data);
|
||||
})
|
||||
.catch((error) => { });
|
||||
}, []);
|
||||
|
||||
return { zones, error };
|
||||
return { zones, error };
|
||||
};
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
.App {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #eff2f7;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #eff2f7;
|
||||
}
|
||||
|
||||
.Box {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
|
||||
padding: 50px;
|
||||
gap: 30px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
|
||||
padding: 50px;
|
||||
gap: 30px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Small {
|
||||
width: 250px;
|
||||
height: 400px;
|
||||
width: 250px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.Section {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 30px 50px;
|
||||
gap: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 30px 50px;
|
||||
gap: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Image {
|
||||
width: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.Connected {
|
||||
color: green;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.Disconnected {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.FloatingStatus {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 50px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.LogoutButton {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 50px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.Card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Items {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.List {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
const request = require('supertest');
|
||||
const app = 'http://127.0.0.1:5000'
|
||||
|
||||
const authCredentials = {
|
||||
email: 'info@lufthansa.com',
|
||||
password: 'password1234',
|
||||
};
|
||||
|
||||
describe('Flight API Endpoints', () => {
|
||||
let authToken;
|
||||
|
||||
beforeAll(async () => {
|
||||
const loginResponse = await request(app)
|
||||
.post('/auth/login')
|
||||
.send(authCredentials);
|
||||
|
||||
expect(loginResponse.statusCode).toBe(200);
|
||||
expect(loginResponse.body).toHaveProperty('access_token');
|
||||
authToken = loginResponse.body.access_token;
|
||||
});
|
||||
|
||||
it('should create a new flight', async () => {
|
||||
const newFlight = {
|
||||
"flight_code": "ABC127",
|
||||
"status": "En ruta",
|
||||
"origin": "Frankfurt",
|
||||
"destination": "Ciudad A",
|
||||
"departure_time": "2023-10-29 09:57 PM",
|
||||
"arrival_time": "2023-10-30 12:00 PM",
|
||||
"gate": "A2"
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.post('/flights')
|
||||
.send(newFlight)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id');
|
||||
createdFlightId = response.body.id;
|
||||
});
|
||||
|
||||
it('should retrieve the created flight by ID', async () => {
|
||||
const response = await request(app)
|
||||
.get(`/flights/${createdFlightId}`)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id', createdFlightId);
|
||||
});
|
||||
|
||||
it('should update a flight by ID', async () => {
|
||||
const flightUpdate = {
|
||||
"status": "Delayed"
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.patch(`/flights/${createdFlightId}`)
|
||||
.send(flightUpdate)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id');
|
||||
expect(response.body.status).toBe('Delayed');
|
||||
});
|
||||
|
||||
it('should retrieve filtered flights', async () => {
|
||||
const response = await request(app)
|
||||
.get('/flights')
|
||||
.query({ origin: 'Frankfurt' })
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toBeInstanceOf(Array);
|
||||
expect(response.body[0].id).toBe(createdFlightId)
|
||||
});
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
import React, {createContext, ReactNode, useContext, useEffect, useMemo, useState} from "react";
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Credentials, TokenData, User } from "./Types";
|
||||
import { fetchUserById, logIn, tokenStatus } from "./Api";
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
interface AuthContextType {
|
||||
|
||||
interface AuthContextType {
|
||||
user?: User;
|
||||
loading: boolean;
|
||||
isAirline: boolean;
|
||||
|
@ -12,28 +12,28 @@ import jwt_decode from "jwt-decode";
|
|||
login: (credentials: Credentials) => void;
|
||||
signUp: (email: string, name: string, password: string) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>(
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>(
|
||||
{} as AuthContextType
|
||||
);
|
||||
|
||||
export function AuthProvider({
|
||||
);
|
||||
|
||||
export function AuthProvider({
|
||||
children,
|
||||
}: {
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
}): JSX.Element {
|
||||
const [user, setUser] = useState<User>();
|
||||
const [error, setError] = useState<any>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [loadingInitial, setLoadingInitial] = useState<boolean>(true);
|
||||
const [isAirline, setIsAirline] = useState(false);
|
||||
const [isAirline, setIsAirline] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (error) setError(undefined);
|
||||
if (error) setError(undefined);
|
||||
}, [window.location.pathname]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const existingToken = localStorage.getItem("token");
|
||||
if (existingToken) {
|
||||
|
@ -48,68 +48,68 @@ import jwt_decode from "jwt-decode";
|
|||
}
|
||||
|
||||
tokenStatus(existingToken)
|
||||
.then((res) => fetchUserById(res.id)
|
||||
.then((res) => setUser(res))
|
||||
.catch((_error) => {})
|
||||
.finally(() => setLoadingInitial(false))
|
||||
)
|
||||
.catch((_error) => {
|
||||
setLoadingInitial(false)
|
||||
logout()
|
||||
})
|
||||
.then((res) => fetchUserById(res.id)
|
||||
.then((res) => setUser(res))
|
||||
.catch((_error) => { })
|
||||
.finally(() => setLoadingInitial(false))
|
||||
)
|
||||
.catch((_error) => {
|
||||
setLoadingInitial(false)
|
||||
logout()
|
||||
})
|
||||
// .finally(() => setLoadingInitial(false));
|
||||
} else {
|
||||
setLoadingInitial(false)
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
function login(credentials: Credentials) {
|
||||
setLoading(true);
|
||||
const tokens = logIn(credentials)
|
||||
.then((x) => {
|
||||
localStorage.setItem("token", x.access_token);
|
||||
const airline = (jwt_decode(x.access_token) as TokenData).airline;
|
||||
setIsAirline(airline)
|
||||
const user = fetchUserById(x.user_id as number)
|
||||
.then(y => {
|
||||
setUser(y);
|
||||
navigate("/home")
|
||||
.then((x) => {
|
||||
localStorage.setItem("token", x.access_token);
|
||||
const airline = (jwt_decode(x.access_token) as TokenData).airline;
|
||||
setIsAirline(airline)
|
||||
const user = fetchUserById(x.user_id as number)
|
||||
.then(y => {
|
||||
setUser(y);
|
||||
navigate("/home")
|
||||
})
|
||||
.catch((error) => setError(error))
|
||||
.finally(() => setLoading(false));
|
||||
})
|
||||
.catch((error) => setError(error))
|
||||
.finally(() => setLoading(false));
|
||||
})
|
||||
.catch((error) => setError(error))
|
||||
// .finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
function signUp(email: string, name: string, password: string) {}
|
||||
|
||||
|
||||
function signUp(email: string, name: string, password: string) { }
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem("token");
|
||||
setUser(undefined);
|
||||
setUser(undefined);
|
||||
navigate("/login")
|
||||
}
|
||||
|
||||
const memoedValue = useMemo(
|
||||
() => ({
|
||||
user,
|
||||
loading,
|
||||
isAirline,
|
||||
error,
|
||||
login,
|
||||
signUp,
|
||||
logout,
|
||||
}),
|
||||
[user, isAirline, loading, error]
|
||||
() => ({
|
||||
user,
|
||||
loading,
|
||||
isAirline,
|
||||
error,
|
||||
login,
|
||||
signUp,
|
||||
logout,
|
||||
}),
|
||||
[user, isAirline, loading, error]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={memoedValue}>
|
||||
{!loadingInitial && children}
|
||||
</AuthContext.Provider>
|
||||
<AuthContext.Provider value={memoedValue}>
|
||||
{!loadingInitial && children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function useAuth() {
|
||||
}
|
||||
|
||||
export default function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl -X DELETE api:5000/ping
|
||||
curl -X POST api:5000/ping
|
||||
|
||||
|
||||
# npm test
|
||||
echo "NPM TEST"
|
||||
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
|
||||
npm run test:integration
|
||||
else
|
||||
npm run test
|
||||
fi
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from sqlalchemy import Date
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
|
@ -48,9 +49,29 @@ def get_flights_by_origin(db: Session, origin: str):
|
|||
return db.query(Flight).filter(Flight.origin == origin).all()
|
||||
|
||||
|
||||
def get_flights_update(db: Session, origin: str, lastUpdate: str):
|
||||
def get_flights_by_destination(db: Session, destination: str):
|
||||
return db.query(Flight).filter(Flight.destination == destination).all()
|
||||
|
||||
|
||||
def get_flights_update_origin(db: Session, origin: str, lastUpdate: str):
|
||||
return (
|
||||
db.query(Flight)
|
||||
.filter((Flight.origin == origin) & (Flight.last_updated >= lastUpdate))
|
||||
.filter(
|
||||
(Flight.origin == origin)
|
||||
& (Flight.last_updated >= lastUpdate)
|
||||
& (Flight.departure_time.cast(Date) >= func.current_date())
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def get_flights_update_destination(db: Session, destination: str, lastUpdate: str):
|
||||
return (
|
||||
db.query(Flight)
|
||||
.filter(
|
||||
(Flight.destination == destination)
|
||||
& (Flight.last_updated >= lastUpdate)
|
||||
& (Flight.arrival_time.cast(Date) >= func.current_date())
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
|
|
@ -51,13 +51,20 @@ async def update_flight(
|
|||
@router.get("", response_model=list[Flight])
|
||||
def get_flights(
|
||||
origin: Optional[str] = None,
|
||||
destination: Optional[str] = None,
|
||||
lastUpdated: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if origin and lastUpdated:
|
||||
flights = flight_crud.get_flights_update(db, origin, lastUpdated)
|
||||
flights = flight_crud.get_flights_update_origin(db, origin, lastUpdated)
|
||||
elif destination and lastUpdated:
|
||||
flights = flight_crud.get_flights_update_destination(
|
||||
db, destination, lastUpdated
|
||||
)
|
||||
elif origin:
|
||||
flights = flight_crud.get_flights_by_origin(db, origin)
|
||||
flights = flight_crud.get_flights_by_origin(db=db, origin=origin)
|
||||
elif destination:
|
||||
flights = flight_crud.get_flights_by_destination(db=db, destination=destination)
|
||||
else:
|
||||
flights = flight_crud.get_flights(db=db)
|
||||
|
||||
|
|
|
@ -51,10 +51,12 @@ async def update_flight(
|
|||
|
||||
|
||||
@router.get("", response_model=list[Flight])
|
||||
async def get_flights(origin: Optional[str] = None, lastUpdated: Optional[str] = None):
|
||||
async def get_flights(origin: Optional[str] = None, destination: Optional[str] = None, lastUpdated: Optional[str] = None):
|
||||
query = {}
|
||||
if origin:
|
||||
query["origin"] = origin
|
||||
if destination:
|
||||
query["destination"] = destination
|
||||
if lastUpdated:
|
||||
query["lastUpdated"] = lastUpdated
|
||||
(response, status, _) = await request(f"{API_FLIGHTS}", "GET", query=query)
|
||||
|
|
11
run.sh
11
run.sh
|
@ -145,13 +145,13 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
|
|||
fi
|
||||
;;
|
||||
'screen')
|
||||
docker build screen-domain -f screen-domain/Dockerfile.prod --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:prod
|
||||
export CLIENT_IMAGE=$USER/screen-client:prod
|
||||
docker build screen-domain -f screen-domain/Dockerfile.test --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:test
|
||||
export CLIENT_IMAGE=$USER/screen-client:test
|
||||
docker compose -f screen-domain/docker-compose.yml up -d
|
||||
;;
|
||||
'browser')
|
||||
docker build browser-domain -f browser-domain/Dockerfile.prod -t $USER/browser-client:prod
|
||||
export CLIENT_IMAGE=$USER/browser-client:prod
|
||||
docker build browser-domain -f browser-domain/Dockerfile.test -t $USER/browser-client:test
|
||||
export CLIENT_IMAGE=$USER/browser-client:test
|
||||
docker compose -f browser-domain/docker-compose.yml up -d
|
||||
;;
|
||||
'elk')
|
||||
|
@ -183,6 +183,7 @@ else
|
|||
export SUBSCRIPTION_MANAGER=subscription-domain/subscription-manager
|
||||
docker build $SUBSCRIPTION_MANAGER -f $SUBSCRIPTION_MANAGER/Dockerfile.prod -t $USER/subs-manager:prod
|
||||
|
||||
export REACT_APP_ORIGIN=Frankfurt
|
||||
docker build screen-domain -f screen-domain/Dockerfile.prod --build-arg "REACT_APP_ORIGIN=$REACT_APP_ORIGIN" -t $USER/screen-client:prod
|
||||
docker build browser-domain -f browser-domain/Dockerfile.prod -t $USER/browser-client:prod
|
||||
|
||||
|
@ -205,4 +206,4 @@ else
|
|||
docker compose -f screen-domain/docker-compose.yml up -d
|
||||
export CLIENT_IMAGE=$USER/browser-client:prod
|
||||
docker compose -f browser-domain/docker-compose.yml up -d
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
FROM node:17.9.1 AS app
|
||||
WORKDIR /app
|
||||
COPY package.json /app/package.json
|
||||
RUN npm -v && ls -al
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
|
|
|
@ -7,4 +7,5 @@ services:
|
|||
restart: always
|
||||
environment:
|
||||
- API_HOST=api
|
||||
- TEST_TARGET=${TEST_TARGET}
|
||||
network_mode: host
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
coverageReporters: ["html", "text", "text-summary", "cobertura"],
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"react-router": "^6.10.0",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-super-responsive-table": "^5.2.2",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -27,6 +28,8 @@
|
|||
"@types/react-dom": "^18.0.11",
|
||||
"jest": "^28.0.0",
|
||||
"jest-environment-jsdom": "^28.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^28.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
@ -5702,6 +5705,12 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
|
@ -5808,6 +5817,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
|
@ -6497,6 +6512,16 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
@ -7715,6 +7740,12 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
|
@ -8098,6 +8129,21 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
@ -8498,6 +8544,15 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
|
@ -10330,6 +10385,33 @@
|
|||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/jest-junit": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-junit/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-leak-detector": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz",
|
||||
|
@ -15749,6 +15831,19 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/react-super-responsive-table": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-super-responsive-table/-/react-super-responsive-table-5.2.2.tgz",
|
||||
"integrity": "sha512-LDsGq8X3TBn0AMv5gX/0wFeWoFMMmiZXNTD1kIMMWz3WflPMHUZOSHXXlG5EOO2u3WSNcVl7MLIk83maS/Xj0g==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": ">= 15",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -16916,6 +17011,85 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
|
||||
"integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0 <13 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -18569,6 +18743,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
@ -22666,6 +22846,12 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
|
@ -22756,6 +22942,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||
|
@ -23237,6 +23429,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
@ -24149,6 +24351,12 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
|
@ -24426,6 +24634,18 @@
|
|||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
@ -24697,6 +24917,12 @@
|
|||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true
|
||||
},
|
||||
"hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
|
@ -26044,6 +26270,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jest-junit": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-leak-detector": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz",
|
||||
|
@ -29825,6 +30071,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-super-responsive-table": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-super-responsive-table/-/react-super-responsive-table-5.2.2.tgz",
|
||||
"integrity": "sha512-LDsGq8X3TBn0AMv5gX/0wFeWoFMMmiZXNTD1kIMMWz3WflPMHUZOSHXXlG5EOO2u3WSNcVl7MLIk83maS/Xj0g==",
|
||||
"requires": {}
|
||||
},
|
||||
"read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -30687,6 +30939,66 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"superagent": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
|
||||
"integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -31929,6 +32241,12 @@
|
|||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"requires": {}
|
||||
},
|
||||
"xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"dev": true
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "sample-client-users",
|
||||
"name": "screen-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -10,16 +10,15 @@
|
|||
"react-router": "^6.10.0",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-super-responsive-table": "^5.2.2",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "jest --coverage --collectCoverageFrom=\"./src/**\"",
|
||||
"test:integration": "jest integration",
|
||||
"eject": "react-scripts eject",
|
||||
"docker:build": "docker build -t client-users .",
|
||||
"docker:run": " docker run --rm -it -p 8080:80 client-users"
|
||||
"test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**\" --reporters=default --reporters=jest-junit",
|
||||
"test:integration": "jest integration --testPathPattern=src/tests/integration",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
@ -49,6 +48,8 @@
|
|||
"@types/react-dom": "^18.0.11",
|
||||
"jest": "^28.0.0",
|
||||
"jest-environment-jsdom": "^28.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^28.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
|
|
@ -1,36 +1,37 @@
|
|||
import { Axios, AxiosError } from "axios";
|
||||
import { Credentials, User, Flight } from "./Types";
|
||||
import { Flight } from "./Types";
|
||||
|
||||
const instance = new Axios({
|
||||
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
validateStatus: (x) => { return !(x < 200 || x > 204) }
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
return JSON.parse(response.data);
|
||||
},
|
||||
(error) => {
|
||||
const err = error as AxiosError;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
(response) => {
|
||||
return JSON.parse(response.data);
|
||||
},
|
||||
(error) => {
|
||||
const err = error as AxiosError;
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
instance.interceptors.request.use((request) => {
|
||||
request.data = JSON.stringify(request.data);
|
||||
return request;
|
||||
request.data = JSON.stringify(request.data);
|
||||
return request;
|
||||
});
|
||||
|
||||
export const ping = () => {
|
||||
return instance.get("health");
|
||||
return instance.get("health");
|
||||
};
|
||||
|
||||
export const fetchZones = (origin: string | undefined, lastUpdate: string | null): Promise<Flight[]> => {
|
||||
return instance.get("flights" +
|
||||
(origin ? "?origin=" + origin : "") +
|
||||
(lastUpdate ? ( origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : ""))
|
||||
export const fetchZones = (origin: string | undefined, destination: string | undefined, lastUpdate: string | null): Promise<Flight[]> => {
|
||||
return instance.get("flights" +
|
||||
(origin ? "?origin=" + origin : "") +
|
||||
(destination ? "?destination=" + destination : "") +
|
||||
(lastUpdate ? (origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : ""))
|
||||
};
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useIsConnected } from "./hooks/useIsConnected";
|
||||
import { Route, Routes } from "react-router";
|
||||
import { Departure } from "./components/Home/Departure";
|
||||
import { Arrival } from "./components/Home/Arrival";
|
||||
import { Home } from "./components/Home/Home";
|
||||
import { Button } from "antd";
|
||||
import { initDB } from "./db";
|
||||
|
||||
function App() {
|
||||
const connection = useIsConnected();
|
||||
initDB();
|
||||
const connection = useIsConnected();
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="/home" element={<Home />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
<div className="FloatingStatus">{connection}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="/departure" element={<Departure />} />
|
||||
<Route path="/arrival" element={<Arrival />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
<div className="FloatingStatus">{connection}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
export interface Credentials {
|
||||
password: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface Token {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
created_date?: Date;
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
created_date?: Date;
|
||||
}
|
||||
|
||||
export interface Zone {
|
||||
id: number;
|
||||
name: string;
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Flight {
|
||||
|
@ -30,4 +30,4 @@ export interface Flight {
|
|||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
}
|
||||
}
|
|
@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react";
|
|||
import { Button } from "antd";
|
||||
|
||||
describe("Button Component Test", () => {
|
||||
test("Display button label and clicked", async () => {
|
||||
const onClick = jest.fn();
|
||||
test("Display button label and clicked", async () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
render(<Button onClick={() => onClick()}>Button</Button>);
|
||||
render(<Button onClick={() => onClick()}>Button</Button>);
|
||||
|
||||
expect(screen.getByText("Button")).toBeVisible();
|
||||
await userEvent.click(screen.getByText("Button"));
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
expect(screen.getByText("Button")).toBeVisible();
|
||||
await userEvent.click(screen.getByText("Button"));
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Card } from "./Card/Card";
|
||||
import { Flight } from "../../Types";
|
||||
import './Home.css'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table';
|
||||
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
|
||||
import { useFetchDestination } from "../../hooks/useFetchDestination";
|
||||
|
||||
interface Props {
|
||||
flights?: Flight[];
|
||||
}
|
||||
|
||||
export const Arrival: React.FC<Props> = (props) => {
|
||||
let destination = process.env.REACT_APP_ORIGIN;
|
||||
|
||||
const { zones, error } = useFetchDestination(destination);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (zones.length <= 10) {
|
||||
return;
|
||||
}
|
||||
setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [zones]);
|
||||
|
||||
|
||||
return (
|
||||
<div >
|
||||
<h2>Arrival</h2>
|
||||
<div className="Items">
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Code</Th>
|
||||
<Th>Origin</Th>
|
||||
<Th>Time</Th>
|
||||
<Th>Gate</Th>
|
||||
<Th>Status</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{zones.length > 0 && (
|
||||
<>
|
||||
{zones.slice(startIndex, startIndex + 10).map((flight) => (
|
||||
<Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}>
|
||||
<Td>{flight.flight_code}</Td>
|
||||
<Td>{flight.origin}</Td>
|
||||
<Td>{flight.arrival_time}</Td>
|
||||
<Td>{flight.gate}</Td>
|
||||
<Td>{flight.status}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{error ? <div className="Disconnected">{error}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
/* .flight-card {
|
||||
.flight-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
|
@ -9,37 +10,14 @@
|
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.flight-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
} */
|
||||
|
||||
.flight-card {
|
||||
display: flex;
|
||||
flex-direction: column; /* Display as a column instead of a row */
|
||||
justify-content: space-between;
|
||||
align-items: flex-start; /* Align items to the start of the column */
|
||||
padding: 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
|
||||
.flight-details {
|
||||
display: flex;
|
||||
flex-direction: column; /* Display details as a column */
|
||||
margin-top: 16px; /* Add some space between the two rows */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,10 @@ import "../../../matchMedia.mock";
|
|||
import { Card } from "./Card";
|
||||
|
||||
describe("Card Component Test", () => {
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(<Card name="Belgrano" />);
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(<Card name="Belgrano" />);
|
||||
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("B")).toBeVisible();
|
||||
});
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("B")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,101 +1,63 @@
|
|||
// import React from "react";
|
||||
// import { Avatar, Button } from "antd";
|
||||
|
||||
// interface FlightProps {
|
||||
// flight_code: string;
|
||||
// status: string;
|
||||
// origin: string;
|
||||
// destination: string;
|
||||
// departure_time: string;
|
||||
// arrival_time: string;
|
||||
// gate: string;
|
||||
// }
|
||||
|
||||
// interface CardProps {
|
||||
// flight: FlightProps;
|
||||
// }
|
||||
|
||||
// export const Card: React.FC<CardProps> = ({
|
||||
// flight: { flight_code, status, origin, destination, departure_time, arrival_time, gate },
|
||||
// }) => {
|
||||
// console.log(flight_code)
|
||||
// return (
|
||||
// <div className="Card">
|
||||
// <Avatar size="large">{flight_code.slice(0, 1).toUpperCase()}</Avatar>
|
||||
// <div>
|
||||
// <div>Name: {flight_code}</div>
|
||||
// <div>Status: {status}</div>
|
||||
// <div>Origin: {origin}</div>
|
||||
// <div>Destination: {destination}</div>
|
||||
// <div>Departure Time: {departure_time}</div>
|
||||
// <div>Arrival Time: {arrival_time}</div>
|
||||
// <div>Gate: {gate}</div>
|
||||
// </div>
|
||||
// 📍
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
import React from "react";
|
||||
import { Avatar, Space, Typography, Tag } from "antd";
|
||||
import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons";
|
||||
|
||||
import "./Card.css"; // Import a CSS file for styling, you can create this file with your styles
|
||||
import "./Card.css";
|
||||
|
||||
interface FlightProps {
|
||||
flight_code: string;
|
||||
status: string;
|
||||
origin: string;
|
||||
destination: string;
|
||||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
flight_code: string;
|
||||
status: string;
|
||||
origin: string;
|
||||
destination: string;
|
||||
departure_time: string;
|
||||
arrival_time: string;
|
||||
gate: string;
|
||||
}
|
||||
|
||||
interface CardProps {
|
||||
flight: FlightProps;
|
||||
flight: FlightProps;
|
||||
}
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const Card: React.FC<CardProps> = ({ flight }) => {
|
||||
return (
|
||||
<div className="flight-card">
|
||||
<Space size={8} align="center">
|
||||
<Avatar size={64} icon={<RightOutlined />} />
|
||||
<div>
|
||||
<Text strong>{flight.flight_code}</Text>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
{flight.origin} <SwapOutlined /> {flight.destination}
|
||||
</Text>
|
||||
</div>
|
||||
return (
|
||||
<div className="flight-card">
|
||||
<Space size={8} align="center">
|
||||
<Avatar size={64} icon={<RightOutlined />} />
|
||||
<div>
|
||||
<Text strong>{flight.flight_code}</Text>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
{flight.origin} <SwapOutlined /> {flight.destination}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
<div className="flight-details">
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Status:</Text>
|
||||
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Departure:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.departure_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Arrival:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.arrival_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Gate:</Text>
|
||||
<Text>{flight.gate}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
<div className="flight-details">
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Status:</Text>
|
||||
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Departure:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.departure_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Arrival:</Text>
|
||||
<Space size={2} align="baseline">
|
||||
<CalendarOutlined />
|
||||
{flight.arrival_time}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space size={8} direction="vertical">
|
||||
<Text strong>Gate:</Text>
|
||||
<Text>{flight.gate}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Card } from "./Card/Card";
|
||||
import { useFetchOrigin } from "../../hooks/useFetchOrigin";
|
||||
import { Flight } from "../../Types";
|
||||
import './Home.css'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table';
|
||||
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
|
||||
|
||||
interface Props {
|
||||
flights?: Flight[];
|
||||
}
|
||||
|
||||
export const Departure: React.FC<Props> = (props) => {
|
||||
let origin = process.env.REACT_APP_ORIGIN;
|
||||
|
||||
const { zones, error } = useFetchOrigin(origin);
|
||||
const [startIndex, setStartIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (zones.length <= 10) {
|
||||
return;
|
||||
}
|
||||
setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [zones]);
|
||||
|
||||
|
||||
return (
|
||||
<div >
|
||||
<h2>Departure</h2>
|
||||
<div className="Items">
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Code</Th>
|
||||
<Th>Time</Th>
|
||||
<Th>Destination</Th>
|
||||
<Th>Gate</Th>
|
||||
<Th>Status</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{zones.length > 0 && (
|
||||
<>
|
||||
{zones.slice(startIndex, startIndex + 10).map((flight) => (
|
||||
<Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}>
|
||||
<Td>{flight.flight_code}</Td>
|
||||
<Td>{flight.departure_time}</Td>
|
||||
<Td>{flight.destination}</Td>
|
||||
<Td>{flight.gate}</Td>
|
||||
<Td>{flight.status}</Td>
|
||||
</Tr>
|
||||
// );
|
||||
))}
|
||||
{startIndex + 10 >= zones.length && (
|
||||
<>
|
||||
{Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td></Td>
|
||||
<Td></Td>
|
||||
<Td></Td>
|
||||
<Td></Td>
|
||||
<Td></Td>
|
||||
</Tr>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{error ? <div className="Disconnected">{error}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
border-collapse: collapse;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.delayed-flight {
|
||||
background-color: #ffcccc;
|
||||
color: #ff0000;
|
||||
}
|
|
@ -1,28 +1,28 @@
|
|||
const mockedUsedNavigate = jest.fn();
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useNavigate: () => mockedUsedNavigate,
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useNavigate: () => mockedUsedNavigate,
|
||||
}));
|
||||
|
||||
import "../../matchMedia.mock";
|
||||
import "@testing-library/jest-dom";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { Home } from "./Home";
|
||||
// import { Home } from "./Departure";
|
||||
|
||||
describe("Home View Test", () => {
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(
|
||||
// <Home
|
||||
// zones={[
|
||||
// { id: 1, name: "Belgrano" },
|
||||
// { id: 2, name: "San Isidro" },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
test("Display initial, name and icon", async () => {
|
||||
// render(
|
||||
// <Home
|
||||
// zones={[
|
||||
// { id: 1, name: "Belgrano" },
|
||||
// { id: 2, name: "San Isidro" },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
|
||||
// expect(screen.getByText("Zones")).toBeVisible();
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("San Isidro📍")).toBeVisible();
|
||||
});
|
||||
// expect(screen.getByText("Zones")).toBeVisible();
|
||||
// expect(screen.getByText("Belgrano📍")).toBeVisible();
|
||||
// expect(screen.getByText("San Isidro📍")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
import React from "react";
|
||||
import { Card } from "./Card/Card";
|
||||
import { useFetchZones } from "../../hooks/useFetchZones";
|
||||
import { Flight } from "../../Types";
|
||||
import './Home.css'
|
||||
import { useNavigate } from "react-router";
|
||||
import './Page.css'
|
||||
|
||||
interface Props {
|
||||
flights?: Flight[];
|
||||
flights?: Flight[];
|
||||
}
|
||||
|
||||
export const Home: React.FC<Props> = (props) => {
|
||||
// const urlParams = new URLSearchParams(window.location.search);
|
||||
// const origin = urlParams.get('origin');
|
||||
// const { zones, error } = useFetchZones(origin);
|
||||
const { zones, error } = useFetchZones();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="Box">
|
||||
<h2>Flights</h2>
|
||||
<div className="Items">
|
||||
{(props.flights ? props.flights : zones).map((u) => {
|
||||
return <Card key={u.id} flight={u} />;
|
||||
})}
|
||||
{error ? <div className="Disconnected">{error}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const submitHandler = (path: string) => {
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => submitHandler("/departure")}>Departure</button>
|
||||
<button onClick={() => submitHandler("/arrival")}>Arrival</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
|
@ -3,123 +3,90 @@ let db: IDBDatabase;
|
|||
let version = 1;
|
||||
|
||||
export enum Stores {
|
||||
Flight = 'flights',
|
||||
Departure = 'departure',
|
||||
Arrival = 'arrival'
|
||||
}
|
||||
|
||||
interface EventTarget {
|
||||
result: any
|
||||
}
|
||||
export const initDB = (): Promise<boolean | IDBDatabase> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB');
|
||||
|
||||
export const initDB = (): Promise<boolean|IDBDatabase> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB');
|
||||
request.onupgradeneeded = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
|
||||
request.onupgradeneeded = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
if (!db.objectStoreNames.contains(Stores.Arrival)) {
|
||||
db.createObjectStore(Stores.Arrival, { keyPath: 'id' });
|
||||
}
|
||||
if (!db.objectStoreNames.contains(Stores.Departure)) {
|
||||
db.createObjectStore(Stores.Departure, { keyPath: 'id' });
|
||||
}
|
||||
};
|
||||
|
||||
if (!db.objectStoreNames.contains(Stores.Flight)) {
|
||||
db.createObjectStore(Stores.Flight, { keyPath: 'id' });
|
||||
}
|
||||
};
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
version = db.version;
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
version = db.version;
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
request.onerror = (e) => {
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
request.onerror = (e) => {
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const addData = <T>(storeName: string, data: T): Promise<T|string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB', version);
|
||||
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
store.add(data);
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
const error = request.error?.message
|
||||
if (error) {
|
||||
resolve(error);
|
||||
} else {
|
||||
resolve('Unknown error');
|
||||
}
|
||||
};
|
||||
});
|
||||
export const addData = <T>(storeName: string, data: T): Promise<T | string | null> => {
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
store.add(data);
|
||||
resolve(data);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteData = (storeName: string, key: number): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB', version);
|
||||
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.delete(key);
|
||||
res.onsuccess = () => {
|
||||
resolve(true);
|
||||
};
|
||||
res.onerror = () => {
|
||||
resolve(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.delete(key);
|
||||
console.log("removing" + key)
|
||||
res.onsuccess = () => {
|
||||
console.log("success")
|
||||
resolve(true);
|
||||
};
|
||||
res.onerror = () => {
|
||||
console.log("error")
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const updateData = <T>(storeName: string, key: number, data: T): Promise<T|string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB', version);
|
||||
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
db = req.result;
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.get(key);
|
||||
res.onsuccess = () => {
|
||||
const newData = { ...res.result, ...data };
|
||||
store.put(newData);
|
||||
resolve(newData);
|
||||
};
|
||||
res.onerror = () => {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
export const updateData = <T>(storeName: string, key: number, data: T): Promise<T | string | null> => {
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.get(key);
|
||||
res.onsuccess = () => {
|
||||
const newData = { ...res.result, ...data };
|
||||
store.put(newData);
|
||||
resolve(newData);
|
||||
};
|
||||
res.onerror = () => {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getStoreData = <T>(storeName: Stores): Promise<T[]|null> => {
|
||||
return new Promise((resolve) => {
|
||||
request = indexedDB.open('myDB');
|
||||
|
||||
request.onsuccess = (e) => {
|
||||
let req = (e.target as IDBOpenDBRequest)
|
||||
if (!req.result) {
|
||||
resolve(null);
|
||||
}
|
||||
db = req.result;
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.getAll();
|
||||
res.onsuccess = () => {
|
||||
resolve(res.result);
|
||||
};
|
||||
};
|
||||
});
|
||||
export const getStoreData = <T>(storeName: Stores): Promise<T[] | null> => {
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
const res = store.getAll();
|
||||
res.onsuccess = () => {
|
||||
resolve(res.result);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export {};
|
||||
export { };
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { Flight } from "../Types";
|
||||
import { fetchZones } from "../Api";
|
||||
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
|
||||
|
||||
export const useFetchDestination = (destination: string | undefined) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [zones, setZones] = useState<Flight[]>([]);
|
||||
|
||||
const isToday = (someDate: Date) => {
|
||||
let today = new Date();
|
||||
return someDate.getDate() == today.getDate() &&
|
||||
someDate.getMonth() == today.getMonth() &&
|
||||
someDate.getFullYear() == today.getFullYear()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
let today = localStorage.getItem('date')
|
||||
|
||||
if (today && !isToday(new Date(today))) {
|
||||
getStoreData<Flight>(Stores.Arrival)
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
data.map((u) => {
|
||||
deleteData(Stores.Arrival, u.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
localStorage.setItem('date', new Date().toString())
|
||||
}
|
||||
|
||||
}, 36000)
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
localStorage.setItem('date', new Date().toString())
|
||||
|
||||
initDB().then((x) => {
|
||||
console.log(x)
|
||||
getStoreData<Flight>(Stores.Arrival)
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
if (data && data.length > 0) {
|
||||
data.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
|
||||
setZones(data)
|
||||
} else {
|
||||
fetchZones(undefined, destination, null)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
data.map((u) => {
|
||||
if (u.status != 'Deleted') {
|
||||
addData(Stores.Arrival, u)
|
||||
toAdd.push(u)
|
||||
}
|
||||
})
|
||||
toAdd.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
|
||||
setZones(toAdd);
|
||||
})
|
||||
.catch((error) => { });
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
}, [origin]);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
let lastUpdate = localStorage.getItem('lastUpdated')
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
fetchZones(undefined, destination, lastUpdate)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
let toRemove: Flight[] = []
|
||||
|
||||
zones.forEach((c, i) => {
|
||||
let index = data.findIndex(x => x.id === c.id)
|
||||
if (index >= 0) {
|
||||
console.log(data[index].departure_time + 'nuevo')
|
||||
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
|
||||
console.log("sacamos")
|
||||
toRemove.push(data[index])
|
||||
deleteData(Stores.Arrival, c.id)
|
||||
} else {
|
||||
toAdd.push(data[index]);
|
||||
updateData(Stores.Arrival, c.id, data[index])
|
||||
}
|
||||
} else {
|
||||
console.log(new Date(c.departure_time))
|
||||
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
|
||||
console.log("sacamos?")
|
||||
toRemove.push(c);
|
||||
deleteData(Stores.Arrival, c.id)
|
||||
} else {
|
||||
toAdd.push(c);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let filtered = data.filter(o =>
|
||||
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
|
||||
)
|
||||
const newArray = toAdd.concat(filtered);
|
||||
console.log(filtered)
|
||||
console.log(newArray)
|
||||
filtered.forEach(c => {
|
||||
addData(Stores.Arrival, c)
|
||||
})
|
||||
|
||||
newArray.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
|
||||
setZones(newArray);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!!error.isAxiosError && !error.response) {
|
||||
//
|
||||
}
|
||||
});
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [origin, zones])
|
||||
|
||||
return { zones, error };
|
||||
};
|
|
@ -0,0 +1,135 @@
|
|||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { Flight } from "../Types";
|
||||
import { fetchZones } from "../Api";
|
||||
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
|
||||
|
||||
export const useFetchOrigin = (origin: string | undefined) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [zones, setZones] = useState<Flight[]>([]);
|
||||
|
||||
const isToday = (someDate: Date) => {
|
||||
let today = new Date();
|
||||
return someDate.getDate() == today.getDate() &&
|
||||
someDate.getMonth() == today.getMonth() &&
|
||||
someDate.getFullYear() == today.getFullYear()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
let today = localStorage.getItem('date')
|
||||
|
||||
if (today && !isToday(new Date(today))) {
|
||||
getStoreData<Flight>(Stores.Departure)
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
data.map((u) => {
|
||||
deleteData(Stores.Departure, u.id)
|
||||
})
|
||||
}
|
||||
})
|
||||
localStorage.setItem('date', new Date().toString())
|
||||
}
|
||||
|
||||
}, 36000)
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
localStorage.setItem('date', new Date().toString())
|
||||
|
||||
initDB().then((x) => {
|
||||
console.log(x)
|
||||
getStoreData<Flight>(Stores.Departure)
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
if (data && data.length > 0) {
|
||||
data.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
|
||||
setZones(data)
|
||||
} else {
|
||||
fetchZones(origin, undefined, null)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
data.map((u) => {
|
||||
if (u.status != 'Deleted') {
|
||||
addData(Stores.Departure, u)
|
||||
toAdd.push(u)
|
||||
}
|
||||
})
|
||||
toAdd.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
|
||||
setZones(toAdd);
|
||||
})
|
||||
.catch((error) => { });
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
}, [origin]);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
let lastUpdate = localStorage.getItem('lastUpdated')
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
fetchZones(origin, undefined, lastUpdate)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
let toRemove: Flight[] = []
|
||||
|
||||
zones.forEach((c, i) => {
|
||||
let index = data.findIndex(x => x.id === c.id)
|
||||
if (index >= 0) {
|
||||
console.log(data[index].departure_time + 'nuevo')
|
||||
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
|
||||
console.log("sacamos")
|
||||
toRemove.push(data[index])
|
||||
deleteData(Stores.Departure, c.id)
|
||||
} else {
|
||||
toAdd.push(data[index]);
|
||||
updateData(Stores.Departure, c.id, data[index])
|
||||
}
|
||||
} else {
|
||||
console.log(new Date(c.departure_time))
|
||||
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
|
||||
console.log("sacamos?")
|
||||
toRemove.push(c);
|
||||
deleteData(Stores.Departure, c.id)
|
||||
} else {
|
||||
toAdd.push(c);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let filtered = data.filter(o =>
|
||||
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
|
||||
)
|
||||
const newArray = toAdd.concat(filtered);
|
||||
console.log(filtered)
|
||||
console.log(newArray)
|
||||
filtered.forEach(c => {
|
||||
addData(Stores.Departure, c)
|
||||
})
|
||||
|
||||
newArray.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
|
||||
setZones(newArray);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!!error.isAxiosError && !error.response) {
|
||||
//
|
||||
}
|
||||
});
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [origin, zones])
|
||||
|
||||
return { zones, error };
|
||||
};
|
|
@ -1,90 +0,0 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { User, Flight } from "../Types";
|
||||
import { fetchZones } from "../Api";
|
||||
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
|
||||
|
||||
export const useFetchZones = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [zones, setZones] = useState<Flight[]>([]);
|
||||
|
||||
let origin = process.env.REACT_APP_ORIGIN;
|
||||
|
||||
useEffect(() => {
|
||||
setError(null);
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
getStoreData<Flight>(Stores.Flight)
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
if (data && data.length > 0) {
|
||||
setZones(data)
|
||||
} else {
|
||||
fetchZones(origin, null)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
data.map((u) => {
|
||||
if (u.status != 'Deleted') {
|
||||
addData(Stores.Flight, u)
|
||||
toAdd.push(u)
|
||||
}
|
||||
})
|
||||
setZones(toAdd);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}
|
||||
})
|
||||
|
||||
}, [origin]);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
let lastUpdate = localStorage.getItem('lastUpdated')
|
||||
let newUpdate = new Date().toISOString()
|
||||
|
||||
fetchZones(origin, lastUpdate)
|
||||
.then((data) => {
|
||||
localStorage.setItem('lastUpdated', newUpdate)
|
||||
let toAdd: Flight[] = []
|
||||
let toRemove: Flight[] = []
|
||||
|
||||
zones.forEach((c, i) => {
|
||||
let index = data.findIndex(x => x.id === c.id)
|
||||
if (index >= 0) {
|
||||
console.log(data[index].status)
|
||||
if (data[index].status == 'Deleted') {
|
||||
console.log("sacamos")
|
||||
toRemove.push(data[index])
|
||||
deleteData(Stores.Flight, c.id)
|
||||
} else {
|
||||
toAdd.push(data[index]);
|
||||
updateData(Stores.Flight, c.id, data[index])
|
||||
}
|
||||
} else {
|
||||
if (c.status == 'Deleted') {
|
||||
toRemove.push(c);
|
||||
} else {
|
||||
toAdd.push(c);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(toAdd)
|
||||
console.log(toRemove)
|
||||
let filtered = data.filter(o => !toAdd.some(b => { return o.id === b.id}) && !toRemove.some(b => { return o.id === b.id}))
|
||||
const newArray = toAdd.concat(filtered);
|
||||
filtered.forEach(c => {
|
||||
addData(Stores.Flight, c)
|
||||
})
|
||||
|
||||
setZones(newArray);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, 5000)
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [origin, zones])
|
||||
|
||||
return { zones, error };
|
||||
};
|
|
@ -3,25 +3,29 @@ import { useState } from "react";
|
|||
import { ping } from "../Api";
|
||||
|
||||
export const useIsConnected = () => {
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [connected, setConnected] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
ping()
|
||||
.then(() => {
|
||||
setConnected(true);
|
||||
})
|
||||
.catch(() => {
|
||||
setConnected(false);
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
ping()
|
||||
.then(() => {
|
||||
setConnected(true);
|
||||
})
|
||||
.catch(() => {
|
||||
setConnected(false);
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connected ? (
|
||||
<p className="Connected">Connected</p>
|
||||
) : (
|
||||
<p className="Disconnected">Disconnected</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connected ? (
|
||||
<p className="Connected">Connected</p>
|
||||
) : (
|
||||
<p className="Disconnected">Disconnected</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
.App {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #eff2f7;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #eff2f7;
|
||||
}
|
||||
|
||||
.Box {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
|
||||
padding: 50px;
|
||||
gap: 30px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
|
||||
padding: 50px;
|
||||
gap: 30px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Small {
|
||||
width: 250px;
|
||||
height: 400px;
|
||||
width: 250px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.Section {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 30px 50px;
|
||||
gap: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 30px 50px;
|
||||
gap: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Image {
|
||||
width: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.Connected {
|
||||
color: green;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.Disconnected {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.FloatingStatus {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 50px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.LogoutButton {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 50px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.Card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Items {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.List {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
const request = require('supertest');
|
||||
const app = 'http://127.0.0.1:5000'
|
||||
|
||||
const authCredentials = {
|
||||
email: 'info@lufthansa.com',
|
||||
password: 'password1234',
|
||||
};
|
||||
|
||||
describe('Flight API Endpoints', () => {
|
||||
let authToken;
|
||||
|
||||
beforeAll(async () => {
|
||||
const loginResponse = await request(app)
|
||||
.post('/auth/login')
|
||||
.send(authCredentials);
|
||||
|
||||
expect(loginResponse.statusCode).toBe(200);
|
||||
expect(loginResponse.body).toHaveProperty('access_token');
|
||||
authToken = loginResponse.body.access_token;
|
||||
});
|
||||
|
||||
it('should create a new flight', async () => {
|
||||
const newFlight = {
|
||||
"flight_code": "ABC127",
|
||||
"status": "En ruta",
|
||||
"origin": "Frankfurt",
|
||||
"destination": "Ciudad A",
|
||||
"departure_time": "2023-10-29 09:57 PM",
|
||||
"arrival_time": "2023-10-30 12:00 PM",
|
||||
"gate": "A2"
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.post('/flights')
|
||||
.send(newFlight)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id');
|
||||
createdFlightId = response.body.id;
|
||||
});
|
||||
|
||||
it('should retrieve the created flight by ID', async () => {
|
||||
const response = await request(app)
|
||||
.get(`/flights/${createdFlightId}`)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id', createdFlightId);
|
||||
});
|
||||
|
||||
it('should update a flight by ID', async () => {
|
||||
const flightUpdate = {
|
||||
"status": "Delayed"
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.patch(`/flights/${createdFlightId}`)
|
||||
.send(flightUpdate)
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toHaveProperty('id');
|
||||
expect(response.body.status).toBe('Delayed');
|
||||
});
|
||||
|
||||
it('should retrieve filtered flights', async () => {
|
||||
const response = await request(app)
|
||||
.get('/flights')
|
||||
.query({ origin: 'Frankfurt' })
|
||||
.set('Authorization', `Bearer ${authToken}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toBeInstanceOf(Array);
|
||||
expect(response.body[0].id).toBe(createdFlightId)
|
||||
});
|
||||
});
|
|
@ -1,8 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl -X DELETE api:5000/ping
|
||||
curl -X POST api:5000/ping
|
||||
|
||||
|
||||
# npm test
|
||||
echo "NPM TEST"
|
||||
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
|
||||
npm run test:integration
|
||||
else
|
||||
npm run test
|
||||
fi
|
||||
|
|
Loading…
Reference in New Issue