diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e757a909259f83ab7a49cbed5dac6b15ab115061..8dd0ebc17218f6c8273da344f9dec0ce864912a8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ image: node:16 stages: # - setup - test + - deploy #variables: # npm_config_cache: "$CI_PROJECT_DIR/.npm" @@ -37,6 +38,21 @@ test: - npm run lint - npm run test:unit +deploy-backend: + image: ubuntu:latest + stage: deploy + script: + - chmod og= $ID_RSA + - apt update + - apt install --assume-yes rsync + - apt install --assume-yes openssh-client + - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVERUSER@$SERVERIP "sudo /bin/systemctl restart cleanfront.service" + - rsync --archive --rsync-path=/usr/bin/rsync --delete --exclude='.git' --exclude='node_modules' -e "ssh -i $ID_RSA -o StrictHostKeyChecking=no -l $SERVERUSER -p 22" . $SERVERIP:./app/frontend + - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVERUSER@$SERVERIP "cd app/frontend;npm install" + - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVERUSER@$SERVERIP "sudo /bin/systemctl restart frontend.service" + only: + - main + #unit_test: # stage: test # script: diff --git a/package-lock.json b/package-lock.json index 894267d8427354bf139f24226b19df82101d6cf5..92415a10cf3e721007d669673ea0ebbbf5827a42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "roboto-fontface": "*", "sockjs-client": "^1.6.0", "stompjs": "^2.3.3", + "tw-elements": "^1.0.0-alpha12", "vue": "^3.2.13", "vue-router": "^4.0.3", "vuelidate": "^0.7.7", @@ -2382,7 +2383,6 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -2394,7 +2394,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -2402,7 +2401,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2417,6 +2415,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "dev": true, @@ -3874,7 +3881,6 @@ }, "node_modules/acorn-node": { "version": "1.8.2", - "dev": true, "license": "Apache-2.0", "dependencies": { "acorn": "^7.0.0", @@ -3884,7 +3890,6 @@ }, "node_modules/acorn-node/node_modules/acorn": { "version": "7.4.1", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3895,7 +3900,6 @@ }, "node_modules/acorn-node/node_modules/acorn-walk": { "version": "7.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4046,7 +4050,6 @@ }, "node_modules/anymatch": { "version": "3.1.2", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -4077,7 +4080,6 @@ }, "node_modules/arg": { "version": "5.0.1", - "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -4450,7 +4452,6 @@ }, "node_modules/binary-extensions": { "version": "2.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4539,7 +4540,6 @@ }, "node_modules/braces": { "version": "3.0.2", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.0.1" @@ -4680,7 +4680,6 @@ }, "node_modules/camelcase-css": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4749,9 +4748,42 @@ "node": ">=6" } }, + "node_modules/chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "dependencies": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "node_modules/chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "dependencies": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "node_modules/chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/chartjs-plugin-datalabels": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz", + "integrity": "sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==", + "peerDependencies": { + "chart.js": ">= 2.7.0 < 3" + } + }, "node_modules/chokidar": { "version": "3.5.3", - "dev": true, "funding": [ { "type": "individual", @@ -4777,7 +4809,6 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4997,7 +5028,6 @@ }, "node_modules/color-convert": { "version": "1.9.3", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -5005,7 +5035,6 @@ }, "node_modules/color-name": { "version": "1.1.3", - "dev": true, "license": "MIT" }, "node_modules/colord": { @@ -5512,7 +5541,6 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -5629,6 +5657,11 @@ "version": "2.6.20", "license": "MIT" }, + "node_modules/custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==" + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -5851,7 +5884,6 @@ }, "node_modules/defined": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/delayed-stream": { @@ -5875,6 +5907,14 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-autofill": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/detect-autofill/-/detect-autofill-1.1.4.tgz", + "integrity": "sha512-utCBQwCR/beSnADQmBC7C4tTueBBkYCl6WSpfGUkYKO/+MzPxqYGj6G4MvHzcKmH1gCTK+VunX2vaagvkRXPvA==", + "dependencies": { + "custom-event-polyfill": "^1.0.7" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "dev": true, @@ -5890,7 +5930,6 @@ }, "node_modules/detective": { "version": "5.2.0", - "dev": true, "license": "MIT", "dependencies": { "acorn-node": "^1.6.1", @@ -5906,7 +5945,6 @@ }, "node_modules/didyoumean": { "version": "1.2.2", - "dev": true, "license": "Apache-2.0" }, "node_modules/diff-sequences": { @@ -5930,7 +5968,6 @@ }, "node_modules/dlv": { "version": "1.1.3", - "dev": true, "license": "MIT" }, "node_modules/dns-equal": { @@ -7061,7 +7098,6 @@ }, "node_modules/fast-glob": { "version": "3.2.11", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7076,7 +7112,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -7097,7 +7132,6 @@ }, "node_modules/fastq": { "version": "1.13.0", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -7145,7 +7179,6 @@ }, "node_modules/fill-range": { "version": "7.0.1", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -7314,7 +7347,6 @@ }, "node_modules/function-bind": { "version": "1.1.1", - "dev": true, "license": "MIT" }, "node_modules/functional-red-black-tree": { @@ -7391,7 +7423,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7458,7 +7489,6 @@ }, "node_modules/has": { "version": "1.0.3", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.1" @@ -7872,7 +7902,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -7904,7 +7933,6 @@ }, "node_modules/is-core-module": { "version": "2.9.0", - "dev": true, "license": "MIT", "dependencies": { "has": "^1.0.3" @@ -7937,7 +7965,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7969,7 +7996,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -7988,7 +8014,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10336,7 +10361,6 @@ }, "node_modules/lilconfig": { "version": "2.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10728,7 +10752,6 @@ }, "node_modules/merge2": { "version": "1.4.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -10744,7 +10767,6 @@ }, "node_modules/micromatch": { "version": "4.0.5", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.2", @@ -10877,7 +10899,6 @@ }, "node_modules/minimist": { "version": "1.2.6", - "dev": true, "license": "MIT" }, "node_modules/minipass": { @@ -10907,6 +10928,14 @@ "dev": true, "license": "MIT" }, + "node_modules/moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", + "engines": { + "node": "*" + } + }, "node_modules/mrmime": { "version": "1.0.0", "dev": true, @@ -11096,7 +11125,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11158,7 +11186,6 @@ }, "node_modules/object-hash": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -11520,7 +11547,6 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "dev": true, "license": "MIT" }, "node_modules/path-to-regexp": { @@ -11536,13 +11562,17 @@ "node": ">=8" } }, + "node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "node_modules/picocolors": { "version": "1.0.0", "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -11570,6 +11600,16 @@ "node": ">=8" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/portfinder": { "version": "1.0.28", "dev": true, @@ -11702,7 +11742,6 @@ }, "node_modules/postcss-js": { "version": "4.0.0", - "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -11720,7 +11759,6 @@ }, "node_modules/postcss-load-config": { "version": "3.1.4", - "dev": true, "license": "MIT", "dependencies": { "lilconfig": "^2.0.5", @@ -11930,7 +11968,6 @@ }, "node_modules/postcss-nested": { "version": "5.0.6", - "dev": true, "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.6" @@ -12117,7 +12154,6 @@ }, "node_modules/postcss-selector-parser": { "version": "6.0.10", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -12158,7 +12194,6 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", - "dev": true, "license": "MIT" }, "node_modules/prelude-ls": { @@ -12350,7 +12385,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "dev": true, "funding": [ { "type": "github", @@ -12369,7 +12403,6 @@ }, "node_modules/quick-lru": { "version": "5.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -12482,7 +12515,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -12612,7 +12644,6 @@ }, "node_modules/resolve": { "version": "1.22.0", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.8.1", @@ -12680,7 +12711,6 @@ }, "node_modules/reusify": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -12707,7 +12737,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "dev": true, "funding": [ { "type": "github", @@ -13410,7 +13439,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13493,7 +13521,6 @@ }, "node_modules/tailwindcss": { "version": "3.0.24", - "dev": true, "license": "MIT", "dependencies": { "arg": "^5.0.1", @@ -13531,7 +13558,6 @@ }, "node_modules/tailwindcss/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/tapable": { @@ -13759,7 +13785,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -13848,6 +13873,21 @@ "dev": true, "license": "0BSD" }, + "node_modules/tw-elements": { + "version": "1.0.0-alpha12", + "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-alpha12.tgz", + "integrity": "sha512-AQHwXIC4kw4T0NPKAi0vD1bkHSiA4RhdXqqnXDWEHKsrN02njc/ITB3YZ92s2f2jt+wBPI2UOpeUsWL5xiMVeQ==", + "dependencies": { + "@popperjs/core": "^2.6.0", + "chart.js": "^2.9.4", + "chartjs-plugin-datalabels": "^0.7.0", + "deepmerge": "^4.2.2", + "detect-autofill": "^1.1.3", + "perfect-scrollbar": "^1.5.0", + "popper.js": "^1.16.1", + "tailwindcss": "^3.0.7" + } + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -13993,7 +14033,6 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/utila": { @@ -14956,7 +14995,6 @@ }, "node_modules/xtend": { "version": "4.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -14986,7 +15024,6 @@ }, "node_modules/yaml": { "version": "1.10.2", - "dev": true, "license": "ISC", "engines": { "node": ">= 6" @@ -16544,19 +16581,16 @@ }, "@nodelib/fs.scandir": { "version": "2.1.5", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true + "version": "2.0.5" }, "@nodelib/fs.walk": { "version": "1.2.8", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -16566,6 +16600,11 @@ "version": "1.0.0-next.21", "dev": true }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" + }, "@sideway/address": { "version": "4.1.4", "dev": true, @@ -17616,7 +17655,6 @@ }, "acorn-node": { "version": "1.8.2", - "dev": true, "requires": { "acorn": "^7.0.0", "acorn-walk": "^7.0.0", @@ -17624,12 +17662,10 @@ }, "dependencies": { "acorn": { - "version": "7.4.1", - "dev": true + "version": "7.4.1" }, "acorn-walk": { - "version": "7.2.0", - "dev": true + "version": "7.2.0" } } }, @@ -17718,7 +17754,6 @@ }, "anymatch": { "version": "3.1.2", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -17729,8 +17764,7 @@ "dev": true }, "arg": { - "version": "5.0.1", - "dev": true + "version": "5.0.1" }, "argparse": { "version": "1.0.10", @@ -17962,8 +17996,7 @@ "dev": true }, "binary-extensions": { - "version": "2.2.0", - "dev": true + "version": "2.2.0" }, "bl": { "version": "4.1.0", @@ -18035,7 +18068,6 @@ }, "braces": { "version": "3.0.2", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -18112,8 +18144,7 @@ "dev": true }, "camelcase-css": { - "version": "2.0.1", - "dev": true + "version": "2.0.1" }, "caniuse-api": { "version": "3.0.0", @@ -18150,9 +18181,40 @@ "version": "0.2.0", "dev": true }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chartjs-plugin-datalabels": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz", + "integrity": "sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==", + "requires": {} + }, "chokidar": { "version": "3.5.3", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -18166,7 +18228,6 @@ "dependencies": { "glob-parent": { "version": "5.1.2", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -18307,14 +18368,12 @@ }, "color-convert": { "version": "1.9.3", - "dev": true, "requires": { "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.3", - "dev": true + "version": "1.1.3" }, "colord": { "version": "2.9.2", @@ -18631,8 +18690,7 @@ "dev": true }, "cssesc": { - "version": "3.0.0", - "dev": true + "version": "3.0.0" }, "cssnano": { "version": "5.1.7", @@ -18709,6 +18767,11 @@ "csstype": { "version": "2.6.20" }, + "custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==" + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -18844,8 +18907,7 @@ } }, "defined": { - "version": "1.0.0", - "dev": true + "version": "1.0.0" }, "delayed-stream": { "version": "1.0.0", @@ -18859,6 +18921,14 @@ "version": "1.0.4", "dev": true }, + "detect-autofill": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/detect-autofill/-/detect-autofill-1.1.4.tgz", + "integrity": "sha512-utCBQwCR/beSnADQmBC7C4tTueBBkYCl6WSpfGUkYKO/+MzPxqYGj6G4MvHzcKmH1gCTK+VunX2vaagvkRXPvA==", + "requires": { + "custom-event-polyfill": "^1.0.7" + } + }, "detect-newline": { "version": "3.1.0", "dev": true @@ -18869,7 +18939,6 @@ }, "detective": { "version": "5.2.0", - "dev": true, "requires": { "acorn-node": "^1.6.1", "defined": "^1.0.0", @@ -18877,8 +18946,7 @@ } }, "didyoumean": { - "version": "1.2.2", - "dev": true + "version": "1.2.2" }, "diff-sequences": { "version": "27.5.1", @@ -18892,8 +18960,7 @@ } }, "dlv": { - "version": "1.1.3", - "dev": true + "version": "1.1.3" }, "dns-equal": { "version": "1.0.0", @@ -19623,7 +19690,6 @@ }, "fast-glob": { "version": "3.2.11", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -19634,7 +19700,6 @@ "dependencies": { "glob-parent": { "version": "5.1.2", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -19651,7 +19716,6 @@ }, "fastq": { "version": "1.13.0", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -19685,7 +19749,6 @@ }, "fill-range": { "version": "7.0.1", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -19788,8 +19851,7 @@ "dev": true }, "function-bind": { - "version": "1.1.1", - "dev": true + "version": "1.1.1" }, "functional-red-black-tree": { "version": "1.0.1", @@ -19837,7 +19899,6 @@ }, "glob-parent": { "version": "6.0.2", - "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -19879,7 +19940,6 @@ }, "has": { "version": "1.0.3", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -20130,7 +20190,6 @@ }, "is-binary-path": { "version": "2.1.0", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -20154,7 +20213,6 @@ }, "is-core-module": { "version": "2.9.0", - "dev": true, "requires": { "has": "^1.0.3" } @@ -20168,8 +20226,7 @@ "dev": true }, "is-extglob": { - "version": "2.1.1", - "dev": true + "version": "2.1.1" }, "is-file-esm": { "version": "1.0.0", @@ -20188,7 +20245,6 @@ }, "is-glob": { "version": "4.0.3", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -20198,8 +20254,7 @@ "dev": true }, "is-number": { - "version": "7.0.0", - "dev": true + "version": "7.0.0" }, "is-plain-obj": { "version": "3.0.0", @@ -21694,8 +21749,7 @@ } }, "lilconfig": { - "version": "2.0.5", - "dev": true + "version": "2.0.5" }, "lines-and-columns": { "version": "1.2.4", @@ -21956,8 +22010,7 @@ "dev": true }, "merge2": { - "version": "1.4.1", - "dev": true + "version": "1.4.1" }, "methods": { "version": "1.1.2", @@ -21965,7 +22018,6 @@ }, "micromatch": { "version": "4.0.5", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -22042,8 +22094,7 @@ } }, "minimist": { - "version": "1.2.6", - "dev": true + "version": "1.2.6" }, "minipass": { "version": "3.1.6", @@ -22063,6 +22114,11 @@ "version": "2.2.2", "dev": true }, + "moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" + }, "mrmime": { "version": "1.0.0", "dev": true @@ -22192,8 +22248,7 @@ } }, "normalize-path": { - "version": "3.0.0", - "dev": true + "version": "3.0.0" }, "normalize-range": { "version": "0.1.2", @@ -22226,8 +22281,7 @@ "dev": true }, "object-hash": { - "version": "3.0.0", - "dev": true + "version": "3.0.0" }, "object-keys": { "version": "1.1.1", @@ -22456,8 +22510,7 @@ "dev": true }, "path-parse": { - "version": "1.0.7", - "dev": true + "version": "1.0.7" }, "path-to-regexp": { "version": "0.1.7", @@ -22467,12 +22520,16 @@ "version": "4.0.0", "dev": true }, + "perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "picocolors": { "version": "1.0.0" }, "picomatch": { - "version": "2.3.1", - "dev": true + "version": "2.3.1" }, "pirates": { "version": "4.0.5", @@ -22485,6 +22542,11 @@ "find-up": "^4.0.0" } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "portfinder": { "version": "1.0.28", "dev": true, @@ -22558,14 +22620,12 @@ }, "postcss-js": { "version": "4.0.0", - "dev": true, "requires": { "camelcase-css": "^2.0.1" } }, "postcss-load-config": { "version": "3.1.4", - "dev": true, "requires": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" @@ -22669,7 +22729,6 @@ }, "postcss-nested": { "version": "5.0.6", - "dev": true, "requires": { "postcss-selector-parser": "^6.0.6" } @@ -22762,7 +22821,6 @@ }, "postcss-selector-parser": { "version": "6.0.10", - "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -22784,8 +22842,7 @@ } }, "postcss-value-parser": { - "version": "4.2.0", - "dev": true + "version": "4.2.0" }, "prelude-ls": { "version": "1.1.2", @@ -22907,12 +22964,10 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "queue-microtask": { - "version": "1.2.3", - "dev": true + "version": "1.2.3" }, "quick-lru": { - "version": "5.1.1", - "dev": true + "version": "5.1.1" }, "randombytes": { "version": "2.1.0", @@ -22987,7 +23042,6 @@ }, "readdirp": { "version": "3.6.0", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -23075,7 +23129,6 @@ }, "resolve": { "version": "1.22.0", - "dev": true, "requires": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", @@ -23114,8 +23167,7 @@ "dev": true }, "reusify": { - "version": "1.0.4", - "dev": true + "version": "1.0.4" }, "rimraf": { "version": "3.0.2", @@ -23129,7 +23181,6 @@ }, "run-parallel": { "version": "1.2.0", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -23615,8 +23666,7 @@ } }, "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true + "version": "1.0.0" }, "svg-tags": { "version": "1.0.0", @@ -23674,7 +23724,6 @@ }, "tailwindcss": { "version": "3.0.24", - "dev": true, "requires": { "arg": "^5.0.1", "chokidar": "^3.5.3", @@ -23700,8 +23749,7 @@ }, "dependencies": { "color-name": { - "version": "1.1.4", - "dev": true + "version": "1.1.4" } } }, @@ -23839,7 +23887,6 @@ }, "to-regex-range": { "version": "5.0.1", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -23898,6 +23945,21 @@ "version": "2.3.1", "dev": true }, + "tw-elements": { + "version": "1.0.0-alpha12", + "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-alpha12.tgz", + "integrity": "sha512-AQHwXIC4kw4T0NPKAi0vD1bkHSiA4RhdXqqnXDWEHKsrN02njc/ITB3YZ92s2f2jt+wBPI2UOpeUsWL5xiMVeQ==", + "requires": { + "@popperjs/core": "^2.6.0", + "chart.js": "^2.9.4", + "chartjs-plugin-datalabels": "^0.7.0", + "deepmerge": "^4.2.2", + "detect-autofill": "^1.1.3", + "perfect-scrollbar": "^1.5.0", + "popper.js": "^1.16.1", + "tailwindcss": "^3.0.7" + } + }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -23992,8 +24054,7 @@ } }, "util-deprecate": { - "version": "1.0.2", - "dev": true + "version": "1.0.2" }, "utila": { "version": "0.4.0", @@ -24626,8 +24687,7 @@ "dev": true }, "xtend": { - "version": "4.0.2", - "dev": true + "version": "4.0.2" }, "y18n": { "version": "5.0.8", @@ -24644,8 +24704,7 @@ "dev": true }, "yaml": { - "version": "1.10.2", - "dev": true + "version": "1.10.2" }, "yargs": { "version": "16.2.0", diff --git a/package.json b/package.json index e02ef530fc185de6dac101e77ea5d312fc4374b0..82bef75d775f37cffd5e8bda14ea138904251888 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "roboto-fontface": "*", "sockjs-client": "^1.6.0", "stompjs": "^2.3.3", + "tw-elements": "^1.0.0-alpha12", "vue": "^3.2.13", "vue-router": "^4.0.3", "vuelidate": "^0.7.7", diff --git a/src/components/CommunityComponents/CommunityHome.vue b/src/components/CommunityComponents/CommunityHome.vue index 477ba7fbbf0b314242af6e2762be65c67b040ee2..2e9d9eb9a3f938291c63e62809b2007a8154f149 100644 --- a/src/components/CommunityComponents/CommunityHome.vue +++ b/src/components/CommunityComponents/CommunityHome.vue @@ -34,7 +34,12 @@ <div class="grid grid-flow-row-dense grid-cols-2 md:grid-cols-4 lg:grid-cols-5 w-full place-items-center" > - <ItemCard v-for="item in searchedItems" :key="item" :item="item" /> + <ItemCard + v-for="item in searchedItems" + :key="item" + :item="item" + @click="goToItemInfoPage(item.listingID)" + /> </div> </div> </section> @@ -43,7 +48,11 @@ <script> import CommunityHeader from "@/components/CommunityComponents/CommunityHeader.vue"; import ItemCard from "@/components/ItemComponents/ItemCard"; -import { GetCommunity, GetListingsInCommunity } from "@/utils/apiutil"; +import { + GetCommunity, + GetListingsInCommunity, + getItemPictures, +} from "@/utils/apiutil"; export default { name: "SearchItemListComponent", @@ -75,6 +84,7 @@ export default { return { items: [], item: { + listingID: 0, img: "", address: "", title: "", @@ -95,6 +105,19 @@ export default { this.communityID = await this.$router.currentRoute.value.params .communityID; this.items = await GetListingsInCommunity(this.communityID); + for (var i = 0; i < this.items.length; i++) { + let images = await getItemPictures(this.items[i].listingID); + if (images.length > 0) { + this.items[i].img = images[0].picture; + } + } + }, + goToItemInfoPage(item) { + this.$router.push("/itempage/" + item); + }, + getItemPictures: async function (itemid) { + let res = await getItemPictures(itemid); + return res; }, }, beforeMount() { diff --git a/src/components/FormComponents/NewPasswordForm.vue b/src/components/FormComponents/NewPasswordForm.vue index f25e934c1e892c525994266892deeb6d26727aa2..f9a9c779db23ef5d16e6871d1edac10fd7b7e55b 100644 --- a/src/components/FormComponents/NewPasswordForm.vue +++ b/src/components/FormComponents/NewPasswordForm.vue @@ -2,11 +2,11 @@ <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > - <div - class="text-2xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" + <h3 + class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" > Endre passord - </div> + </h3> <div id="firstPasswordField" @@ -138,7 +138,7 @@ export default { const newPasswordResponse = await doNewPassword(newPassword); - if(newPasswordResponse != null) { + if (newPasswordResponse != null) { console.log("New password set"); this.$store.commit("saveToken", newPasswordResponse); await this.$router.push("/"); diff --git a/src/components/FormComponents/ResetPasswordForm.vue b/src/components/FormComponents/ResetPasswordForm.vue index 61a48774e9d97658981eeadf44659e2d9e73b86a..a4c2d80e1533e2c7be82804041bd49208b4ab6be 100644 --- a/src/components/FormComponents/ResetPasswordForm.vue +++ b/src/components/FormComponents/ResetPasswordForm.vue @@ -2,11 +2,11 @@ <div class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > - <div - class="text-2xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" + <h3 + class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" > Glemt passordet ditt? - </div> + </h3> <div id="emailField" diff --git a/src/components/ItemComponents/ItemCard.vue b/src/components/ItemComponents/ItemCard.vue index 089dc01a8043d3409d6806537e853cf95017cc28..ac79861f7bf9eb7bbed3592b4420d440bc98e587 100644 --- a/src/components/ItemComponents/ItemCard.vue +++ b/src/components/ItemComponents/ItemCard.vue @@ -1,6 +1,8 @@ <template> <div class="mt-5"> - <div class="w-4/5 rounded bg-gray-200"> + <div + class="w-4/5 rounded bg-gray-200 h-full overflow-hidden display:inline-block correct-size" + > <img class="w-full" :src="item.img || require('../../assets/default-product.png')" diff --git a/src/components/ItemComponents/NewItemForm.vue b/src/components/ItemComponents/NewItemForm.vue index 14208374965626387442ef8e888e4c2cda8195ca..f9f5f06e0e6a8fc5e610ee8cfc326a276d111e18 100644 --- a/src/components/ItemComponents/NewItemForm.vue +++ b/src/components/ItemComponents/NewItemForm.vue @@ -3,11 +3,11 @@ class="md:ring-1 ring-gray-300 rounded-xl overflow-hidden mx-auto mb-auto max-w-md w-full p-4" > <!-- Component heading --> - <div - class="text-xl md:text-2xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-10" + <h3 + class="text-xl font-medium text-center text-gray-600 dark:text-gray-200 mt-4 mb-8" > Opprett ny utleie - </div> + </h3> <!-- Title --> <div class="mb-6" :class="{ error: v$.item.title.$errors.length }"> diff --git a/src/components/RentingComponents/ImageCarousel.vue b/src/components/RentingComponents/ImageCarousel.vue new file mode 100644 index 0000000000000000000000000000000000000000..aef0e652e6609273c2505025287c152ece3d10d4 --- /dev/null +++ b/src/components/RentingComponents/ImageCarousel.vue @@ -0,0 +1,78 @@ +<template> + <div + id="carouselIndicators" + class="carousel slide relative" + data-bs-ride="carousel" + > + <div + class="carousel-indicators absolute right-0 bottom-0 left-0 flex justify-center p-0 mb-4" + > + <button + v-for="(image, i) in images" + :key="i" + type="button" + data-bs-target="#carouselIndicators" + :data-bs-slide-to="i" + :class="{ active: i === 0 }" + :aria-current="i === 0 ? 'true' : 'false'" + :aria-label="'Slide ' + (i + 1)" + ></button> + </div> + <div class="carousel-inner relative w-full overflow-hidden"> + <div + v-for="(image, i) in images" + :key="i" + :class="'carousel-item float-left w-full' + (i == 0 ? ' active' : '')" + > + <img :src="image.src" class="block w-full" :alt="image.alt" /> + </div> + </div> + <button + class="carousel-control-prev absolute top-0 bottom-0 flex items-center justify-center p-0 text-center border-0 hover:outline-none hover:no-underline focus:outline-none focus:no-underline left-0" + type="button" + data-bs-target="#carouselIndicators" + data-bs-slide="prev" + > + <span + class="carousel-control-prev-icon inline-block bg-no-repeat" + aria-hidden="true" + ></span> + <span class="visually-hidden">Previous</span> + </button> + <button + class="carousel-control-next absolute top-0 bottom-0 flex items-center justify-center p-0 text-center border-0 hover:outline-none hover:no-underline focus:outline-none focus:no-underline right-0" + type="button" + data-bs-target="#carouselIndicators" + data-bs-slide="next" + > + <span + class="carousel-control-next-icon inline-block bg-no-repeat" + aria-hidden="true" + ></span> + <span class="visually-hidden">Next</span> + </button> + </div> +</template> + +<script> +/* +{ + src: "imageURL", + alt: "IMAGE ALT TEXT" +} +*/ +import "tw-elements"; +export default { + props: { + images: { + type: Array, + required: true, + }, + }, + data() { + return {}; + }, +}; +</script> + +<style scoped></style> diff --git a/src/components/RentingComponents/ItemInfo.vue b/src/components/RentingComponents/ItemInfo.vue new file mode 100644 index 0000000000000000000000000000000000000000..59308822724d89fc7749b0e1ed88afde4b316cea --- /dev/null +++ b/src/components/RentingComponents/ItemInfo.vue @@ -0,0 +1,219 @@ +<template> + <div> + <new-rent v-if="confirm" :newRentBox="pushItem"> </new-rent> + </div> + <div v-if="!confirm"> + <div> + <div + v-bind:class="{ + 'grid grid-flow-row-dense grid-cols-2 md:grid-cols-4 h-[600px] w-auto lg:grid-cols-5 place-items-center': + noPicture, + }" + > + <ImageCarousel :images="pictures"></ImageCarousel> + </div> + </div> + <!-- Product info --> + <div + class="max-w-2xl mx-auto pt-10 pb-16 px-4 sm:px-6 lg:max-w-7xl lg:pt-16 lg:pb-24 lg:px-8 lg:grid lg:grid-cols-3 lg:grid-rows-[auto,auto,1fr] lg:gap-x-8" + > + <div class="lg:col-span-2 lg:border-r lg:border-gray-200 lg:pr-8"> + <h1 + class="text-2xl font-extrabold tracking-tight text-gray-900 sm:text-3xl" + > + {{ item.title }} + </h1> + </div> + <!-- TODO make this component render elements differently depending on screen size --> + <div + class="py-10 lg:pt-6 lg:pb-16 lg:col-start-1 lg:col-span-2 lg:border-r lg:border-gray-200 lg:pr-8" + > + <!-- Description and details --> + <div> + <h3 class="text-base font-semibold text-gray-900">Pris per dag</h3> + + <div class="space-y-6"> + <p class="text-2xl font-medium text-gray-900"> + {{ item.pricePerDay }} kr + </p> + </div> + </div> + <div> + <div class="mt-4 space-y-6"> + <p class="text-sm text-gray-600">{{ item.description }}</p> + </div> + </div> + <div> + <div class="mt-4 space-y-6"> + <p class="text-base font-semibold text-gray-900"> + {{ item.address }} + </p> + </div> + </div> + <div class="mt-2"> + <UserListItemCard :user="userForId"></UserListItemCard> + </div> + <div class="mt-4"> + <h3 class="text-base font-base text-gray-900">Tidspunkter</h3> + + <div> + <p class="text-sm text-gray-900"> + <DatepickerRange + @value="setDate" + :messageOnDisplay="dateMessage" + ></DatepickerRange> + </p> + </div> + </div> + <div class="mt-2 md:col-span-1"> + <div class="mt-2 space-y-2"> + <p class="text-xl font-semibold text-gray-900"> + Total pris: {{ totPrice }} kr + </p> + <button + class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-gray-500 rounded-md focus:outline-none focus:ring focus:ring-opacity-80" + v-bind:class="{ colorChange: allowForRent }" + @click="sendToConfirm" + > + <!-- This button should send you to the rent page --> + Rent now + </button> + </div> + </div> + </div> + </div> + </div> +</template> + +<script> +import NewRent from "@/components/RentingComponents/NewRent.vue"; +import { getItem, getItemPictures, getUser } from "@/utils/apiutil"; +import ImageCarousel from "@/components/RentingComponents/ImageCarousel.vue"; +import UserListItemCard from "@/components/UserProfileComponents/UserListItemCard.vue"; +import DatepickerRange from "@/components/TimepickerComponents/DatepickerRange/DatepickerRange.vue"; + +export default { + name: "ItemInfo", + data() { + return { + confirm: false, + item: { + listingID: 0, + title: "", + description: "", + pricePerDay: 0, + price: this.totPrice, + address: "", + userID: 0, + categoryNames: [], + communityIDs: [], + }, + pushItem: { + listingID: 157, + title: "Heii", + price: 56, + fromTime: "", + toTime: "", + }, + images: [ + { + listingID: 0, + picture: "", + }, + ], + pictures: [], + noPicture: true, + userForId: Object, + rentingStartDate: null, + rentingEndDate: null, + totPrice: 0, + dateMessage: "Venligst velg dato for leieperioden", + allowForRent: false, + }; + }, + components: { + ImageCarousel, + UserListItemCard, + DatepickerRange, + NewRent, + }, + methods: { + sendToConfirm() { + if (this.allowForRent) { + this.confirm = true; + this.createPushItem(); + } + }, + createPushItem() { + this.pushItem.listingID = this.item.listingID; + this.pushItem.fromTime = this.rentingStartDate; + this.pushItem.toTime = this.rentingEndDate; + this.pushItem.title = this.item.title; + this.pushItem.price = this.totPrice; + }, + async getItem() { + let id = this.$router.currentRoute.value.params.id; + this.item = await getItem(id); + this.item.listingID = id; + this.totPrice = this.item.pricePerDay; + }, + async getItemPictures() { + let id = this.$router.currentRoute.value.params.id; + this.images = await getItemPictures(id); + + if (this.images.length < 1) { + let noImage = { + src: require("@/assets/default-product.png"), + alt: "No image found", + }; + this.pictures.push(noImage); + } else { + this.noPicture = false; + for (let i = 0; i < this.images.length; i++) { + let oneImage = { + src: this.images[i].picture, + //How do i make this accurate to the image? + alt: "An image", + }; + this.pictures.push(oneImage); + } + } + //TODO fixs so each image get a correct alt text. + }, + async getUser(userId) { + this.userForId = await getUser(userId); + }, + setDate(dateOfsomthing) { + if (dateOfsomthing.startDate == null || dateOfsomthing.endDate == null) { + this.totPrice = this.item.pricePerDay; + this.allowForRent = false; + } else { + this.rentingStartDate = dateOfsomthing.startDate; + this.rentingEndDate = dateOfsomthing.endDate; + this.calculateTotPrice(); + this.allowForRent = true; + } + }, + calculateTotPrice() { + let amountOfDays = this.rentingEndDate - this.rentingStartDate; + amountOfDays = amountOfDays / 86400000; + this.totPrice = this.item.pricePerDay * amountOfDays; + }, + }, + async beforeMount() { + await this.getItemPictures(); + await this.getItem(); + await this.getUser(this.item.userID); + }, +}; +</script> + +<style> +.colorChange { + background-color: #004aad; +} + +.colorChange:hover { + background-color: #306ec1; +} +</style> diff --git a/src/components/RentingComponents/NewRent.vue b/src/components/RentingComponents/NewRent.vue new file mode 100644 index 0000000000000000000000000000000000000000..32073aa24a66328935b56876748e554e8dc00925 --- /dev/null +++ b/src/components/RentingComponents/NewRent.vue @@ -0,0 +1,219 @@ +<template> + <div id="confirmationBox"> + <div id="rentTitle"> + <h1> + {{ title }} + </h1> + </div> + + <div id="fromTime"> + <p>Fra: {{ fromTimeString }}</p> + </div> + <div id="toTime"> + <p>Til: {{ toTimeString }}</p> + </div> + <div id="price"> + <p>Totaltpris: {{ price }} kr</p> + </div> + <div id="message"> + <label + class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-400" + id="descriptionLabel" + >Skriv en melding til utleier:</label + > + <textarea + id="description" + rows="4" + v-model="message" + class="block p-2.5 w-full text-sm text-gray-900 bg-gray-200 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + required + ></textarea> + </div> + <button id="cancelButton" @click="cancelRent" class="text-primary-medium"> + Tilbake + </button> + <div id="confirmButton"> + <colored-button @click="sendRent" :text="'Send'"></colored-button> + </div> + </div> + <div> + <notification-modal + @click="routeToHome" + :visible="confirmed" + :title="'Vellykket'" + :message="'Forespørsel sendt!'" + > + </notification-modal> + </div> +</template> + +<script> +import ColoredButton from "@/components/BaseComponents/ColoredButton.vue"; +import { postNewRent } from "@/utils/apiutil"; +import NotificationModal from "@/components/BaseComponents/NotificationModal.vue"; +export default { + name: "NewRent", + components: { + ColoredButton, + NotificationModal, + }, + data() { + return { + confirmed: false, + title: this.newRentBox.title, + fromTime: this.newRentBox.fromTime, + fromTimeString: "", + toTime: this.newRentBox.toTime, + toTimeString: "", + fromTimeMilliseconds: new Date(this.newRentBox.fromTime).valueOf(), + toTimeMilliseconds: new Date(this.newRentBox.toTime).valueOf(), + message: "", + price: this.newRentBox.price, + }; + }, + props: { + newRentBox: { + renterId: Number, + title: String, + description: String, + fromTime: Date, + toTime: Date, + listingID: Number, + isAccepted: Boolean, + price: Number, + }, + }, + created() { + this.fromTimeString = this.convertDates(this.fromTime); + this.toTimeString = this.convertDates(this.toTime); + }, + + methods: { + //Converts Date-object to a more readable string + convertDates(date) { + //Copies the chosen date + const dateCopy = new Date(date); + //Gets the day part of date (1-31) + const dateDate = dateCopy.getDate(); + //Gets the month of the date (1-12) + const dateMonth = dateCopy.getMonth(); + let monthString = ""; + //Gives the month the proper name + switch (dateMonth) { + case 1: + monthString = "Januar"; + break; + case 2: + monthString = "Februar"; + break; + case 3: + monthString = "Mars"; + break; + case 4: + monthString = "April"; + break; + case 5: + monthString = "Mai"; + break; + case 6: + monthString = "Juni"; + break; + case 7: + monthString = "Juli"; + break; + case 8: + monthString = "August"; + break; + case 9: + monthString = "September"; + break; + case 10: + monthString = "Oktober"; + break; + case 11: + monthString = "November"; + break; + case 12: + monthString = "Desember"; + break; + default: + monthString = "Noe feil"; + break; + } + //Gets the year of the date + const dateYear = dateCopy.getFullYear(); + return dateDate + ". " + monthString + " " + dateYear; + }, + cancelRent() { + this.$router.go(0); + }, + routeToHome() { + this.$router.push("/"); + }, + sendRent: async function () { + const rent = { + renterId: 0, + message: this.message, + listingId: this.newRentBox.listingID, + isAccepted: false, + toTime: this.toTimeMilliseconds, + fromTime: this.fromTimeMilliseconds, + }; + + await postNewRent(rent); + this.confirmed = true; + }, + }, +}; +</script> + +<style scoped> +#confirmationBox { + border: 1px solid silver; + margin-top: 60fr; + padding: 10%; + width: 80%; + margin: auto; + display: grid; + grid-template-columns: 1fr 3fr; + grid-template-rows: 0.5fr 1.5fr 0.7fr 0.7fr 0.7fr 3fr 1fr; + /* max-height: 100%; */ + margin-top: 10%; +} +#cancelButton { + grid-column: 1/2; + grid-row: 1/2; +} +#rentTitle { + margin-top: 10%; + text-align: center; + grid-column: 1/3; + grid-row: 2/3; + font-weight: bold; +} +#fromTime { + grid-row: 3/4; + grid-column: 1/3; + display: block; +} +#toTime { + grid-row: 4/5; + grid-column: 1/3; + display: block; +} +#price { + grid-row: 5/6; + grid-column: 1/3; +} +#message { + grid-column: 1/3; + grid-row: 6/7; +} +#confirmButton { + grid-column: 1/3; + grid-row: 7/8; + align-content: center; + margin: auto; + margin-top: 10px; +} +</style> diff --git a/src/components/TimepickerComponents/DatepickerRange/CalendarComponent.vue b/src/components/TimepickerComponents/DatepickerRange/CalendarComponent.vue new file mode 100644 index 0000000000000000000000000000000000000000..5279dc8265d9273154150ff7398a40912bcae196 --- /dev/null +++ b/src/components/TimepickerComponents/DatepickerRange/CalendarComponent.vue @@ -0,0 +1,313 @@ +<template> + <div class="calendar"> + <div + class="grid grid-cols-7 py-2 mt-0.5 border-b border-black border-opacity-10 dark:border-litepie-secondary-700 dark:border-opacity-100" + > + <div class="months" v-for="day in days" :key="day">{{ day }}</div> + </div> + <div class="grid grid-cols-7 gap-y-0.5 my-1"> + <div + class="days blocked" + v-for="index in daysBeforeStartOfMonth" + :key="index" + > + <button> + {{ daysInMonthBeforeMonth - (daysBeforeStartOfMonth - index) }} + </button> + </div> + <div + class="days relative" + v-for="index in daysInMonth" + :key="index" + :class="{ + disabled: this.isBlocked(index), + selected: this.isDayStartDate(index) && !this.endDate, + start: this.isDayStartDate(index), + end: this.isDayEndDate(index), + error: !!( + this.isBlocked(index) && this.isDayBetweenStartAndEndDate(index) + ), + between: this.isDayBetweenStartAndEndDate(index), + }" + > + <span class="overlay absolute inset-0"></span> + <button v-on:Click="selectDate(index)">{{ index }}</button> + </div> + <div + class="days blocked" + v-for="index in daysRemainingInLastWeek" + :key="index" + > + <button>{{ index }}</button> + </div> + </div> + <p v-if="doesDaysSelectedContainBlockedDays()" class="text-red-500"> + {{ errorMessage }} + </p> + </div> +</template> + +<script> +export default { + props: { + month: { + type: Date, + required: true, + }, + blockedDaysRange: { + type: Array, + default: () => [], + }, + start: { + type: Date, + default: () => null, + }, + end: { + type: Date, + default: () => null, + }, + errorMessage: { + type: String, + default: () => "You cannot select a blocked day", + }, + }, + data() { + return { + days: ["Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"], + }; + }, + computed: { + monthDate() { + return new Date(this.month); + }, + startDate() { + return this.start ? new Date(this.start) : null; + }, + endDate() { + return this.end ? new Date(this.end) : null; + }, + blockedDays() { + const blockedDays = []; + this.blockedDaysRange.forEach((blockedDay) => { + if (blockedDay.length === 2) { + const start = new Date(blockedDay[0]); + const end = new Date(blockedDay[1]); + + // Check if the start or end dates range ends in the current month or after the current month + if ( + start.getMonth() <= this.monthDate.getMonth() && + end.getMonth() >= this.monthDate.getMonth() + ) { + if ( + start.getMonth() < this.monthDate.getMonth() && + end.getMonth() > this.monthDate.getMonth() + ) { + // Add all days of month to the list + for (let i = 1; i <= this.daysInMonth; i++) { + blockedDays.push(i); + } + } else if (start.getMonth() === this.monthDate.getMonth()) { + // Add all days of start month to the list + for (let i = start.getDate(); i <= this.daysInMonth; i++) { + blockedDays.push(i); + } + } else if (end.getMonth() === this.monthDate.getMonth()) { + // Add all days of end month to the list + for (let i = 1; i <= end.getDate(); i++) { + blockedDays.push(i); + } + } + } + } else { + // check that day is in this month + const day = new Date(blockedDay[0]); + if (day.getMonth() !== this.monthDate.getMonth()) return; + blockedDays.push(day.getDate()); + } + }); + return blockedDays; + }, + daysInMonth() { + return new Date( + this.monthDate.getFullYear(), + this.monthDate.getMonth() + 1, + 0 + ).getDate(); + }, + daysRemainingInLastWeek() { + return ( + 7 - + new Date( + new Date(this.month).getFullYear(), + new Date(this.month).getMonth() + 1, + 0 + ).getDay() + ); + }, + daysInMonthBeforeMonth() { + let daysInMonth = new Date( + new Date(this.month).getFullYear(), + new Date(this.month).getMonth(), + 0 + ).getDate(); + return daysInMonth; + }, + daysBeforeStartOfMonth() { + let firstDayOfMonth = new Date( + new Date(this.month).getFullYear(), + new Date(this.month).getMonth(), + 1 + ); + let dayOfWeek = firstDayOfMonth.getDay(); + let daysBefore = dayOfWeek === 0 ? 6 : dayOfWeek - 1; + return daysBefore; + }, + }, + methods: { + isDateInMonth(date) { + if (!date) return null; + return ( + date.getFullYear() === this.monthDate.getFullYear() && + date.getMonth() === this.monthDate.getMonth() + ); + }, + isDayStartDate(day) { + // Check that start day is in month + if (!this.isDateInMonth(this.startDate)) return false; + return this.startDate && this.startDate.getDate() === day; + }, + isDayEndDate(day) { + // Check that end day is in month + if (!this.isDateInMonth(this.endDate)) return false; + return this.endDate && this.endDate.getDate() === day; + }, + selectDate(day) { + const date = new Date( + this.monthDate.getFullYear(), + this.monthDate.getMonth(), + day + ); + this.$emit("selectDate", date); + }, + isBlocked(day) { + return this.blockedDays.includes(day); + }, + isDayBlocked(day) { + return this.blockedDays.includes(day); + }, + isDayBetweenBlocked(day) { + return this.blockedDaysRange.some(([start, end]) => { + return start <= day && day <= end; + }); + }, + // Get date from day and month and check if it is between start and end + isDayBetweenStartAndEndDate(day) { + if (!this.startDate || !this.endDate) return false; + const date = new Date( + this.monthDate.getFullYear(), + this.monthDate.getMonth(), + day + ); + return ( + this.startDate.getTime() < date.getTime() && + date.getTime() < this.endDate.getTime() + ); + }, + doesDaysSelectedContainBlockedDays() { + return false; + }, + }, +}; +</script> + +<style scoped> +.calendar { + max-width: 400px; +} + +.months { + font-size: small; + text-align: center; +} +.days { + font-size: small; + text-align: center; +} + +.blocked button { + cursor: not-allowed; + color: gray; +} + +.disabled { + cursor: not-allowed; +} + +.disabled button { + cursor: not-allowed; +} + +button { + background-color: transparent; + width: 3rem; + height: 3rem; +} + +.disabled span { + background-color: red; + border-radius: 5px; + opacity: 0.6; +} + +.disabled button { + color: black; +} + +.selected.start span { + background-color: blue; + opacity: 0.8; + border-radius: 5px; +} + +.start span { + background-color: blue; + opacity: 0.8; + border-radius: 10px 0 0 10px; +} + +.start button { + color: white; + font-weight: bold; +} + +.end button { + color: white; + font-weight: bold; +} + +.between span { + background-color: lightblue; + opacity: 0.8; +} + +.error span { + background-color: red; + opacity: 0.7; + border-radius: 0; +} + +.end button { + color: white; + font-weight: bold; +} + +.end span { + background-color: blue; + opacity: 0.8; + border-radius: 0 10px 10px 0; +} + +.overlay { + z-index: -1; +} +</style> diff --git a/src/components/TimepickerComponents/DatepickerRange/DatepickerRange.vue b/src/components/TimepickerComponents/DatepickerRange/DatepickerRange.vue new file mode 100644 index 0000000000000000000000000000000000000000..53c92481e3318bfe86ccc699f80c6ed24ffbceb7 --- /dev/null +++ b/src/components/TimepickerComponents/DatepickerRange/DatepickerRange.vue @@ -0,0 +1,290 @@ +<template> + <div> + <div class="input" v-on:click="openCalendar()"> + <label> + <input + type="text" + v-model="value" + v-bind:placeholder="messageOnDisplay" + /> + </label> + </div> + <div class="picker" :style="style"> + <div class="calenders"> + <div> + <month-selector + @back="back" + type="month" + @forward="forward('month')" + :month="month" + ></month-selector> + <calendar-component + :month="month" + :start="startDate" + :end="endDate" + @selectDate="selectDate" + :blockedDaysRange="blockedDaysRange" + ></calendar-component> + </div> + <div class="split"></div> + <div> + <month-selector + @back="back" + type="monghM" + @forward="forward('monghM')" + :month="monghM" + ></month-selector> + <calendar-component + :month="monghM" + :start="startDate" + :end="endDate" + @selectDate="selectDate" + :blockedDaysRange="blockedDaysRange" + ></calendar-component> + </div> + </div> + <div class="footer"> + <p v-if="error" class="error">{{ errorMessage }}</p> + <div v-else></div> + <div class="buttons"> + <button class="btn btn-gray" v-on:click="reset()">RESET</button> + <button class="btn btn-blue" v-on:click="complete()">COMPLETE</button> + </div> + </div> + </div> + </div> +</template> + +<script> +import CalendarComponent from "./CalendarComponent.vue"; +import MonthSelector from "./MonthSelector.vue"; +export default { + components: { + CalendarComponent, + MonthSelector, + }, + props: { + range: { + type: Boolean, + default: true, + }, + blockedDaysRange: { + type: Array, + default: () => [], + }, + messageOnDisplay: String, + }, + data() { + return { + month: new Date(), + monghM: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1), + startDate: new Date(Number.MAX_VALUE), + endDate: null, + error: false, + errorMessage: "Produktet kan ikke utlånes i denne perioden", + open: false, + value: null, + style: { + display: "none", + }, + }; + }, + methods: { + openCalendar() { + // Edit style + this.open = true; + this.style = { + display: "flex", + }; + }, + reset() { + this.startDate = new Date(Number.MAX_VALUE); + this.endDate = null; + this.value = null; + this.error = false; + this.$emit("value", { + startDate: null, + endDate: null, + }); + }, + complete() { + if (this.error) return; + this.open = false; + this.style = { + display: "none", + }; + + // Set value to the start and end date + this.value = `${this.startDate.toLocaleDateString()} - ${this.endDate.toLocaleDateString()}`; + this.$emit("value", { + startDate: this.startDate, + endDate: this.endDate, + }); + }, + selectDate(date) { + const day = date.getDate(); + const month = date.getMonth(); + const year = date.getFullYear(); + if (this.isBlocked(day, month, year)) return; + if (date > this.startDate) { + this.endDate = date; + } else { + this.startDate = date; + } + + if (this.range) { + this.value = `${this.startDate?.toLocaleDateString()} - ${ + this.endDate?.toLocaleDateString() || "" + }`; + } else { + this.value = `${this.startDate.toLocaleDateString()}`; + } + // Check if start and end check if any days in range is blocked + for ( + let i = new Date(this.startDate.toUTCString()); + i <= this.endDate; + i.setDate(i.getDate() + 1) + ) { + if (this.isBlocked(i.getDate(), i.getMonth(), i.getFullYear())) { + this.error = true; + return; + } + } + + if (this.error) this.error = false; + }, + isBlocked(day, month, year) { + return this.blockedDaysRange.some(([start, end]) => { + // Check if start is the same day + if ( + start.getDate() === day && + start.getMonth() === month && + start.getFullYear() === year + ) { + return true; + } + return ( + start <= new Date(year, month, day) && + new Date(year, month, day) <= end + ); + }); + }, + back(type) { + switch (type) { + case "month": { + this.month.setMonth(this.month.getMonth() - 1); + this.month = new Date(this.month); + break; + } + case "monghM": + this.monghM.setMonth(this.monghM.getMonth() - 1); + this.monghM = new Date(this.monghM); + if (this.monghM.getMonth() === this.month.getMonth()) { + this.month.setMonth(this.month.getMonth() - 1); + this.month = new Date(this.month); + } + break; + } + }, + forward(type) { + switch (type) { + case "month": { + this.month.setMonth(this.month.getMonth() + 1); + this.month = new Date(this.month); + if (this.month.getMonth() == this.monghM.getMonth()) { + this.monghM.setMonth(this.monghM.getMonth() + 1); + this.monghM = new Date(this.monghM); + } + break; + } + case "monghM": + this.monghM.setMonth(this.monghM.getMonth() + 1); + this.monghM = new Date(this.monghM); + break; + } + }, + }, +}; +</script> + +<style scoped> +.input label { + display: block; + position: relative; +} + +.input input { + position: relative; + width: 100%; + overflow: hidden; + border: 1px solid #ccc; + border-radius: 10px; + padding: 10px; + padding-right: 50px; +} + +.picker { + position: absolute; + background-color: white; + margin-top: 5px; + z-index: 50; + border: 1px solid black; + border-radius: 4px; + padding: 5px; + flex-direction: column; + width: auto; + max-width: 700px; + overflow: auto; + -webkit-box-shadow: 0px 10px 13px -7px #000000, + 5px 5px 14px 5px rgba(0, 0, 0, 0); + box-shadow: 0px 10px 13px -7px #000000, 5px 5px 14px 5px rgba(0, 0, 0, 0); +} +.split { + border-left: 2px solid black; + margin-left: 10px; + margin-right: 10px; + height: auto; +} + +.calenders { + display: flex; +} + +.footer { + display: flex; + justify-content: space-between; +} + +.error { + align-self: center; + color: red; +} + +.buttons { + float: right; + display: flex; + justify-content: flex-end; +} + +@media only screen and (max-width: 800px) { + .calenders { + flex-direction: column; + } + + .picker { + max-width: 420px; + } +} +.btn { + @apply font-bold py-2 px-4 rounded; +} +.btn-blue { + @apply bg-primary-light text-white; +} +.btn-blue:hover { + @apply bg-primary-medium; +} +.btn-gray:hover { + @apply bg-gray-300; +} +</style> diff --git a/src/components/TimepickerComponents/DatepickerRange/MonthSelector.vue b/src/components/TimepickerComponents/DatepickerRange/MonthSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..a16a3b840b9efd770d970b0f7313f5e1d0198ec9 --- /dev/null +++ b/src/components/TimepickerComponents/DatepickerRange/MonthSelector.vue @@ -0,0 +1,120 @@ +<template> + <div class="container-c main"> + <div class="button" v-on:click="back"> + <button> + <span> + <svg + class="w-5 h-5" + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.5" + d="M15 19l-7-7 7-7" + ></path> + </svg> + </span> + </button> + </div> + <div class="container-c text"> + <div class="date"> + {{ monthShort }} + </div> + <div class="date"> + {{ year }} + </div> + </div> + <div class="button" v-on:click="forward"> + <button> + <span> + <svg + class="w-5 h-5" + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.5" + d="M9 5l7 7-7 7" + ></path> + </svg> + </span> + </button> + </div> + </div> +</template> + +<script> +export default { + props: { + month: { + type: Date, + required: true, + }, + type: { + type: String, + required: true, + }, + }, + computed: { + monthDate() { + return new Date(this.month); + }, + monthShort() { + return this.monthDate + .toLocaleString("default", { month: "short" }) + .toUpperCase(); + }, + year() { + return this.monthDate.getFullYear(); + }, + }, + methods: { + back() { + this.$emit("back", this.type); + }, + forward() { + console.log(this.type); + this.$emit("forward", this.type); + }, + }, +}; +</script> + +<style> +.container-c { + display: flex; +} + +.container-c.main { + justify-content: space-between; + border: 1px solid black; + padding: 5px; + border-radius: 5px; + max-width: 400px; +} + +.text { + justify-content: space-between; + width: 50%; + text-align: center; +} + +.date { + width: 50%; + text-align: center; +} + +.button { + display: flex; + justify-content: center; + align-items: center; +} +</style> diff --git a/src/components/UserProfileComponents/UserListItemCard.vue b/src/components/UserProfileComponents/UserListItemCard.vue index 90180d2df55f207bf11a4ebe1fab595da63bf1f4..a69c38b9931e1eb3c08d4c21290a33a48f91a5de 100644 --- a/src/components/UserProfileComponents/UserListItemCard.vue +++ b/src/components/UserProfileComponents/UserListItemCard.vue @@ -18,7 +18,7 @@ <div class="flex flex-row justify-center"> <button v-if="!admin" - class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-blue-600 rounded-md hover:bg-blue-500 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-80" + class="px-4 py-2 font-medium tracking-wide text-white capitalize transition-colors duration-200 transform bg-primary-medium rounded-md hover:bg-primary-light focus:outline-none focus:ring focus:ring-opacity-80" > Åpne chat </button> diff --git a/src/router/index.js b/src/router/index.js index 68199685ba9f8d6a9ee545c619630f25409ac08a..f9bc42df2a48bfc2c41262b4bcefcb4cebbeec86 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -95,10 +95,16 @@ const routes = [ component: () => import("../views/CommunityViews/CommunityHomeView.vue"), }, { + beforeEnter: guardRoute, path: "/test", name: "test", component: () => import("../views/TestView.vue"), }, + { + path: "/itempage/:id", + name: "ItemInfo", + component: () => import("../views/RentingViews/ItemInfoPageView.vue"), + }, ]; const router = createRouter({ diff --git a/src/utils/apiutil.js b/src/utils/apiutil.js index 6611f8a00bb2342d87c3bf9fba4a5c6e40471474..1397849ea208d2760f3b8ac372ba1d6820892d73 100644 --- a/src/utils/apiutil.js +++ b/src/utils/apiutil.js @@ -86,20 +86,20 @@ export function getAverageRating(userid) { } export async function doNewPassword(password) { let res = await axios({ - method: 'put', + method: "put", url: API_URL + "user/profile/password", headers: tokenHeader(), data: { password: password, - } + }, }) - .then((response) => { - return response; - }) - .catch((error) => { - console.log(error); - }); - return res.data; + .then((response) => { + return response; + }) + .catch((error) => { + console.log(error); + }); + return res.data; } export function postNewItem(itemInfo) { @@ -128,6 +128,20 @@ export function postNewgroup(groupInfo) { return error; }); } +export function postNewRent(rentInfo) { + return axios + .post(API_URL + "renting/renter/save", rentInfo, { + headers: tokenHeader(), + }) + .then((response) => { + console.log("poster: " + response.data); + return response; + }) + .catch((error) => { + console.log(error.response); + return error; + }); +} export function getMyGroups() { return axios @@ -155,6 +169,33 @@ export function getVisibleGroups() { }); } +export function getItem(itemid) { + return axios + .get(API_URL + "listing/" + itemid, { + headers: tokenHeader(), + }) + .then((response) => { + return response.data; + }) + .catch((error) => { + console.error(error); + }); +} + +export async function getItemPictures(itemid) { + let res = await axios + .get(API_URL + "listing/" + itemid + "/pictures", { + headers: tokenHeader(), + }) + .then((response) => { + return response.data; + }) + .catch((error) => { + console.error(error); + }); + return res; +} + export async function GetCommunity(communityID) { return axios .get(API_URL + "community/" + communityID, { diff --git a/src/views/RentingViews/ItemInfoPageView.vue b/src/views/RentingViews/ItemInfoPageView.vue new file mode 100644 index 0000000000000000000000000000000000000000..c08362fb9094c59ca4b1419893314bc609648c38 --- /dev/null +++ b/src/views/RentingViews/ItemInfoPageView.vue @@ -0,0 +1,15 @@ +<template> + <ItemInfo></ItemInfo> +</template> + +<script> +import ItemInfo from "@/components/RentingComponents/ItemInfo"; +export default { + name: "ItemInfoPage.vue", + components: { + ItemInfo, + }, +}; +</script> + +<style scoped></style> diff --git a/tailwind.config.js b/tailwind.config.js index 1108079401f30bb7cfcf539149d36ff105100116..0089d1c11fbc2c51520dc1501abb2dec976d7fa7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,10 @@ module.exports = { darkMode: "class", - content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + "./node_modules/tw-elements/dist/js/**/*.js", + ], theme: { colors: { white: "#fff", @@ -31,5 +35,5 @@ module.exports = { success: "#82DD55", }, }, - plugins: [], + plugins: [require("tw-elements/dist/plugin")], }; diff --git a/tests/unit/apiutil-communityHome-mock.spec.js b/tests/unit/apiutil-communityHome-mock.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4ab75d6469cf79e31546054eebae45ce5976284d --- /dev/null +++ b/tests/unit/apiutil-communityHome-mock.spec.js @@ -0,0 +1,55 @@ +import { GetCommunity, GetListingsInCommunity } from "@/utils/apiutil"; +import axios from "axios"; + +jest.mock("axios"); + +describe("testing mocking of apiutil.js", () => { + it("check that existing group returns correctly", async () => { + const expectedResponse = { + communityId: 4040, + name: "Fisken i vannet", + description: "For vi som liker fjell fisk", + visibility: 1, + location: "Bergen brygge", + picture: "fish blub blub", + }; + + axios.get.mockImplementation(() => + Promise.resolve({ data: expectedResponse }) + ); + + const communityResponse = await GetCommunity(4040); + expect(communityResponse.name).toBe(expectedResponse.name); + }); + + it("check that existing group returns correct listings", async () => { + const expectedResponse = { + item1: { + title: "Fiskekurs", + description: "Fisking og sånn", + pricePerDay: 200, + address: "Vannet", + userID: 6, + categoryNames: null, + communityIDs: null, + }, + + item2: { + title: "TestFraFrontend", + description: "oslo", + pricePerDay: 500, + address: "oslo", + userID: 1, + categoryNames: null, + communityIDs: null, + }, + }; + + axios.get.mockImplementation(() => + Promise.resolve({ data: expectedResponse }) + ); + + const communityItemResponse = await GetListingsInCommunity(4040); + expect(communityItemResponse).toBe(expectedResponse); + }); +}); diff --git a/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap b/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap deleted file mode 100644 index 232516e6546b4be3f3852f589671a41a1e929911..0000000000000000000000000000000000000000 --- a/tests/unit/component-tests/community-component-tests/__snapshots__/item-card.spec.js.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ItemCard component renders correctly 1`] = ` -<div - class="mt-5" -> - <div - class="w-4/5 rounded bg-gray-200" - > - <img - alt="Item image" - class="w-full" - src="String" - /> - <div - class="p-1 m-1" - > - <p - class="text-gray-700 text-xs font-bold" - id="adress" - > - String - </p> - <p - class="font-bold text-sm" - id="title" - > - String - </p> - <p - class="text-gray-700 text-xs" - id="price" - > - 0 kr - </p> - </div> - </div> -</div> -`;