diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 36e1e34e21c4674efff2897edef252a49f90f3c1..83b89b18cb31b1c87548dd99ef87d6468fbdd9c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,16 +6,55 @@ variables: cache: paths: - .cache/pip - - venv/ + - venv/ # makes the virtual env available for jobs -before_script: - - python -V +stages: + - venv update # Keep the cached virtual env up to date with dependencies + - tests and linting # Run test/linting for frontend and backend -test: +update: + before_script: + - python -V + image: python:3.9-buster + stage: venv update script: - make install + +backend lint: + before_script: + - python -V + image: python:3.9-buster + stage: tests and linting + script: - make lint + +backend test: + before_script: + - python -V + image: python:3.9-buster + stage: tests and linting + script: - make test artifacts: reports: cobertura: coverage.xml + +frontend test: + image: node:14-alpine + stage: tests and linting + script: + - cd frontend + - npm install -g npm@latest + - npm ci + - npm run coverage:ci + coverage: + '/All files[^|]*\|[^|]*\s+([\d\.]+)/' + +frontend lint: + image: node:14-alpine + stage: tests and linting + script: + - cd frontend + - npm install -g npm@latest + - npm ci + - npm run lint diff --git a/frontend/.env b/frontend/.env index 60a2d2a264d628fd76716f27d74f22778ae43b99..bd56a6e4c209ad07a85513349a27e2f5161ef0f4 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,2 +1,11 @@ REACT_APP_VERSION=$npm_package_version REACT_APP_NAME=$npm_package_name + +REACT_APP_SUPPORT_MAIL=test@example.org +REACT_APP_INST=uio +REACT_APP_SUPPORT_URL=https://example.org + +REACT_APP_RESPONSIBLE_ORGANIZATION=N/A +REACT_APP_RESPONSIBLE_ORGANIZATION_LINK=https://example.org + +REACT_APP_THEME=default diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..ef74cbdbf973cfbfe0a0c596ae4e97961d923f76 --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": [ + "plugin:react/recommended", + "airbnb", + "airbnb-typescript", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": ["react", "@typescript-eslint"], + "rules": { + "semi": "off", + "@typescript-eslint/semi": ["error", "never"] + } +} diff --git a/frontend/.gitignore b/frontend/.gitignore index 4d29575de80483b005c29bfcac5061cd2f45313e..54ce1aaf18b4e6696a0f88d0980589f740901a31 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,6 +11,9 @@ # production /build +# frontend configuration +/public/env.js + # misc .DS_Store .env.local diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9b9b5bbf781ad15a7df48a4e2a1c2d3a3467e857..164bd6ca3ec90bd4ef618b306dc6aac2a40ecb27 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "react-datepicker": "^4.2.1", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", + "react-hook-form": "^7.15.3", "react-i18next": "^11.11.4", "react-router-dom": "^5.3.0", "react-scripts": "4.0.3", @@ -33,7 +34,18 @@ "devDependencies": { "@types/react-datepicker": "^4.1.7", "@types/react-helmet": "^6.1.2", - "@types/react-router-dom": "^5.1.8" + "@types/react-router-dom": "^5.1.8", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", + "eslint": "^7.32.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-config-airbnb-typescript": "^14.0.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.24.2", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.25.1", + "eslint-plugin-react-hooks": "^4.2.0", + "jest-junit": "^12.2.0" } }, "node_modules/@babel/code-frame": { @@ -3657,12 +3669,12 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz", - "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz", + "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==", "dependencies": { - "@typescript-eslint/experimental-utils": "4.31.0", - "@typescript-eslint/scope-manager": "4.31.0", + "@typescript-eslint/experimental-utils": "4.31.1", + "@typescript-eslint/scope-manager": "4.31.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -3701,14 +3713,14 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz", - "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", + "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", "dependencies": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -3724,13 +3736,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz", - "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz", + "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==", "dependencies": { - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "debug": "^4.3.1" }, "engines": { @@ -3750,12 +3762,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz", - "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", + "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", "dependencies": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0" + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -3766,9 +3778,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", - "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", + "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, @@ -3778,12 +3790,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz", - "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", + "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", "dependencies": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -3818,11 +3830,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz", - "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", + "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", "dependencies": { - "@typescript-eslint/types": "4.31.0", + "@typescript-eslint/types": "4.31.1", "eslint-visitor-keys": "^2.0.0" }, "engines": { @@ -5192,15 +5204,6 @@ "node": ">=8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -7705,6 +7708,67 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-airbnb": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz", + "integrity": "sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^14.2.1", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "^4 || ^3 || ^2.3.0 || ^1.7.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.22.1" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-14.0.0.tgz", + "integrity": "sha512-d2Nit2ByZARGRYK6tgSNl3nnmGZPyvsgbsKFcmm+nAhvT8VjVpifG5jI4tzObUUPb0sWw0E1oO/0pSpBD/pIuQ==", + "dev": true, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.29.3", + "@typescript-eslint/parser": "^4.29.3" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -8922,12 +8986,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, "node_modules/filesize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", @@ -9351,19 +9409,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -11571,6 +11616,54 @@ "node": ">=8" } }, + "node_modules/jest-junit": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-12.2.0.tgz", + "integrity": "sha512-ecGzF3KEQwLbMP5xMO7wqmgmyZlY/5yWDvgE/vFa+/uIT0KsU5nluf0D2fjIlOKB+tb6DiuSSpZuGpsmwbf7Fw==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^5.2.0", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-junit/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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-junit/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jest-leak-detector": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", @@ -13608,12 +13701,6 @@ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - }, "node_modules/nanoid": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", @@ -16451,6 +16538,18 @@ "react": ">=16.3.0" } }, + "node_modules/react-hook-form": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.15.4.tgz", + "integrity": "sha512-jEtsDBPfpkz1uuJVlTLDOg+jO3cG9pFHT3g5uayVvlNT551IetXE1iwrSaxUR/QPWyJA2FLx4Q/VjO2viZNfLg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17" + } + }, "node_modules/react-i18next": { "version": "11.11.4", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.11.4.tgz", @@ -19981,7 +20080,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, + "devOptional": true, "bin": { "uuid": "dist/bin/uuid" } @@ -20227,24 +20326,6 @@ "node": ">=0.10.0" } }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/watchpack-chokidar2/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -20682,24 +20763,6 @@ "node": ">=6" } }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -21844,6 +21907,12 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -24555,12 +24624,12 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz", - "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz", + "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==", "requires": { - "@typescript-eslint/experimental-utils": "4.31.0", - "@typescript-eslint/scope-manager": "4.31.0", + "@typescript-eslint/experimental-utils": "4.31.1", + "@typescript-eslint/scope-manager": "4.31.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -24579,50 +24648,50 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz", - "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", + "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/parser": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz", - "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz", + "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==", "requires": { - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "debug": "^4.3.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz", - "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", + "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", "requires": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0" + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1" } }, "@typescript-eslint/types": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", - "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==" + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", + "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==" }, "@typescript-eslint/typescript-estree": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz", - "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", + "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", "requires": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -24641,11 +24710,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz", - "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", + "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", "requires": { - "@typescript-eslint/types": "4.31.0", + "@typescript-eslint/types": "4.31.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -25740,15 +25809,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "optional": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -27839,6 +27899,42 @@ } } }, + "eslint-config-airbnb": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz", + "integrity": "sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.2.1", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + } + }, + "eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + } + }, + "eslint-config-airbnb-typescript": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-14.0.0.tgz", + "integrity": "sha512-d2Nit2ByZARGRYK6tgSNl3nnmGZPyvsgbsKFcmm+nAhvT8VjVpifG5jI4tzObUUPb0sWw0E1oO/0pSpBD/pIuQ==", + "dev": true, + "requires": {} + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "requires": {} + }, "eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -28640,12 +28736,6 @@ } } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, "filesize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", @@ -28978,12 +29068,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -30610,6 +30694,41 @@ } } }, + "jest-junit": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-12.2.0.tgz", + "integrity": "sha512-ecGzF3KEQwLbMP5xMO7wqmgmyZlY/5yWDvgE/vFa+/uIT0KsU5nluf0D2fjIlOKB+tb6DiuSSpZuGpsmwbf7Fw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^5.2.0", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "jest-leak-detector": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", @@ -32150,12 +32269,6 @@ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - }, "nanoid": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", @@ -34408,6 +34521,12 @@ "react-side-effect": "^2.1.0" } }, + "react-hook-form": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.15.4.tgz", + "integrity": "sha512-jEtsDBPfpkz1uuJVlTLDOg+jO3cG9pFHT3g5uayVvlNT551IetXE1iwrSaxUR/QPWyJA2FLx4Q/VjO2viZNfLg==", + "requires": {} + }, "react-i18next": { "version": "11.11.4", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.11.4.tgz", @@ -37201,7 +37320,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true + "devOptional": true }, "v8-compile-cache": { "version": "2.3.0", @@ -37410,16 +37529,6 @@ } } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -38005,16 +38114,6 @@ "locate-path": "^3.0.0" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -38708,6 +38807,12 @@ "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==", "requires": {} }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e31e8c92ddc6cccfc7c1b6a594968fcf2fc683d0..2ee7d0571d10ac8a3745a0440eb246582827d87d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "react-datepicker": "^4.2.1", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", + "react-hook-form": "^7.15.3", "react-i18next": "^11.11.4", "react-router-dom": "^5.3.0", "react-scripts": "4.0.3", @@ -26,11 +27,16 @@ "web-vitals": "^1.1.2" }, "scripts": { - "start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && react-scripts start", - "build": "npm run build:env && react-scripts build", + "start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts start", + "build": "npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts build", "build:env": "node scripts/build-env.js", + "test": "react-scripts test --env=jsdom", + "test:ci": "react-scripts test --env=jsdom --ci --reporters=default --reporters=jest-junit --coverageReporters=cobertura --coverage --coverageDirectory=coverage", + "coverage:ci": "react-scripts test --env=jsdom --ci --coverage --color --watchAll=false", + "coverage": "react-scripts test --env=jsdom --coverage --color", + "lint": "eslint --ext js,jsx,ts,tsx src", + "lint-report": "eslint --ext js,jsx,ts,tsx -f checkstyle -o checkstyle-result.xml src", "clean": "rm -rf node_modules", - "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { @@ -54,6 +60,17 @@ "devDependencies": { "@types/react-datepicker": "^4.1.7", "@types/react-helmet": "^6.1.2", - "@types/react-router-dom": "^5.1.8" + "@types/react-router-dom": "^5.1.8", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", + "eslint": "^7.32.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-config-airbnb-typescript": "^14.0.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.24.2", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.25.1", + "eslint-plugin-react-hooks": "^4.2.0", + "jest-junit": "^12.2.0" } } diff --git a/frontend/public/env.js b/frontend/public/env.js deleted file mode 100644 index c8a0f84e3e93334b727664616bffe2bf3fc3a5c8..0000000000000000000000000000000000000000 --- a/frontend/public/env.js +++ /dev/null @@ -1,7 +0,0 @@ -window.ENV = { - NODE_ENV: 'development', - PUBLIC_URL: '', - FAST_REFRESH: true, - REACT_APP_VERSION: '0.1.0', - REACT_APP_NAME: 'greg', -} diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index fdf892b074b390c1ed1116c9a9e6c5e153d8791b..3e446301e09b80fec0560fad31d727e4f0e7d106 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -6,5 +6,12 @@ "language": { "change": "Change language to {{lang}}" }, - "loading": "Loading..." + "header": { + "applicationTitleShort": "GREG", + "applicationTitleLong": "Guest Registration", + "selectLanguage": "Select language" + }, + "loading": "Loading...", + "termsHeader": "Terms", + "staging": "Staging" } diff --git a/frontend/public/locales/en/footer.json b/frontend/public/locales/en/footer.json index 205afe095d14684e1f7f96bac62f68e5aed1292c..d052c9b6ffc6413c632c721e8e4f57d2470e9f33 100644 --- a/frontend/public/locales/en/footer.json +++ b/frontend/public/locales/en/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "This is a footer namespace value" + "footerInfo": "This is a footer namespace value", + "contactSectionHeader": "Need help?", + "contactHelp": "Contact", + "contactHelpMail": "Contact", + "responsibleOrganizationHeader": "Maintained by" } diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 96be51a3348df0f7451619613948c127d5441d10..887180ac7c0f29a071985584d002416fa659c419 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -6,5 +6,12 @@ "language": { "change": "Bytt språk til {{lang}}" }, - "loading": "Laster..." + "header":{ + "applicationTitleShort": "GREG", + "applicationTitleLong": "Gjesteregistrering", + "selectLanguage": "Velg språk" + }, + "loading": "Laster...", + "termsHeader": "Vilkår", + "staging": "Staging" } diff --git a/frontend/public/locales/nb/footer.json b/frontend/public/locales/nb/footer.json index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..436d90c7f38d75465ee6db23ba0202b7e93e7c6f 100644 --- a/frontend/public/locales/nb/footer.json +++ b/frontend/public/locales/nb/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "Verdi i namespace footer" + "footerInfo": "Verdi i namespace footer", + "contactSectionHeader": "Trenger du hjelp?", + "contactHelp": "Kontakt", + "contactHelpMail": "Kontaktveileder", + "responsibleOrganizationHeader": "Ansvarlig for tjenesten" } diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index 9da0352ce402851e72b564f51b8c909428f57937..92c11d0febbfabda6240752d39f1573cbd218a8e 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -4,7 +4,15 @@ "test": "Dette er ein 'nested' verdi" }, "language": { + "languageName": "Språk", "change": "Bytt språk til {{lang}}" }, - "loading": "Lastar..." + "header":{ + "applicationTitleShort": "GREG", + "applicationTitleLong": "Gjesteregistrering", + "selectLanguage": "Velg språk" + }, + "loading": "Lastar...", + "termsHeader": "Vilkår", + "staging": "Staging" } diff --git a/frontend/public/locales/nn/footer.json b/frontend/public/locales/nn/footer.json index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..ce248c21dd6ff9285f77f8c4dbde08303ba943e3 100644 --- a/frontend/public/locales/nn/footer.json +++ b/frontend/public/locales/nn/footer.json @@ -1,3 +1,7 @@ { - "footerInfo": "Verdi i namespace footer" + "footerInfo": "Verdi i namespace footer", + "contactSectionHeader": "Trenger du hjelp?", + "contactHelp": "Kontakt", + "contactHelpMail": "Kontaktrettleiar", + "responsibleOrganizationHeader": "Ansvarleg for tenesta" } diff --git a/frontend/public/uio/uio-app-logo-en.png b/frontend/public/uio/uio-app-logo-en.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5c8e2e2d02c81ba3554a2aca6e56427991275f Binary files /dev/null and b/frontend/public/uio/uio-app-logo-en.png differ diff --git a/frontend/public/uio/uio-app-logo-nb.png b/frontend/public/uio/uio-app-logo-nb.png new file mode 100644 index 0000000000000000000000000000000000000000..478f41b5abd9b52ed8b8dfb06ce0f46651e5353c Binary files /dev/null and b/frontend/public/uio/uio-app-logo-nb.png differ diff --git a/frontend/src/appConfig.ts b/frontend/src/appConfig.ts index ea76eda4eccb697485a4299896781824d1b78438..84880d70a088ae516f630454601f05d9eb61b823 100644 --- a/frontend/src/appConfig.ts +++ b/frontend/src/appConfig.ts @@ -1,8 +1,8 @@ declare global { - /* tslint:disable */ - interface Window { - ENV: any - } + /* tslint:disable */ + interface Window { + ENV: any + } } /* Locate the client environment */ @@ -16,7 +16,19 @@ export const appTimezone: string = 'Europe/Oslo' export const appVersion: string = process.env.REACT_APP_VERSION as string export const appName: string = process.env.REACT_APP_NAME as string +/* Institution */ +export const appInst: string = env.REACT_APP_INST as string + /* Theming */ export const appTheme: string = env.REACT_APP_THEME - ? (env.REACT_APP_THEME as string) - : 'default' + ? (env.REACT_APP_THEME as string) + : 'default' + +/* Show warning in the footer about this being a staging/test system */ +export const appStagingWarning: boolean = + env.REACT_APP_STAGING_WARNING === 'true' + +/* Footer content */ +export const appTechnicalSupportLink: string = env.REACT_APP_SUPPORT_URL as string +export const reponsibleOrganization: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION as string +export const responsibleOrganizationLink: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION_LINK as string diff --git a/frontend/src/components/button/index.tsx b/frontend/src/components/button/index.tsx index d2b3635acbbc66ed2dbe9427bd6fa7ebd1b5664c..9eb200519a6b51d5342bd15a8a5b9a1b188e2fe7 100644 --- a/frontend/src/components/button/index.tsx +++ b/frontend/src/components/button/index.tsx @@ -15,3 +15,4 @@ export const Button = styled.a` } background: ${({ theme }) => theme.colors.main}; ` +export default Button diff --git a/frontend/src/components/dropdownmenu/index.tsx b/frontend/src/components/dropdownmenu/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbdbd36feb4135114f5c8be6d5260d3ad8c68013 --- /dev/null +++ b/frontend/src/components/dropdownmenu/index.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react' +import styled from 'styled-components/macro' + + +const DropDownMenu = styled.div` + position: relative; + + &:hover { + cursor: pointer; + } + + width: fit-content; + display: inline-block; +` + +const DropDownList = styled.ul` + position: absolute; + right: 1rem; + top: 2rem; + border-radius: 1rem; + list-style-type: none; + min-width: 10rem; + max-height: 50rem; + z-index: 100; + background: ${({ theme }) => theme.colors.dropDownMenuBackground}; +` + +interface IDropDownOption { + name: string; + value: any; +} + +const getValueName = (value: any, options: Array<IDropDownOption>): string => { + for (let i = 0; i < options.length; i += 1) { + if (options[i].value === value) { + return options[i].name + } + } + return '' +} + +interface IProps { + options: any[]; + placeholder?: string; + value: any; + onChange: (event: any) => void; +} + + +function DropDown(props: IProps) { + const [open, setOpen] = useState(false) + + const { + options, + placeholder, + value, + onChange, + } = props + + function handleClick(option: any) { + setOpen(!open) + onChange(option.value) + } + + function openMenu() { + if (!open) { + setOpen(true) + } + } + + return ( + + <DropDownMenu + aria-haspopup='true' + aria-expanded={open} + /* eslint-disable-next-line react/jsx-no-bind */ + onFocus={openMenu} + tabIndex={0} + > + <div> + <div role='button' + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + openMenu() + } + }} + onClick={openMenu} + tabIndex={0} + > + {/* eslint-disable-next-line */} + {placeholder + ? placeholder + : getValueName(value, options).toLowerCase()} + </div> + </div> + <div> + {open && ( + <DropDownList> + {options.map((option) => ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions + <li + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + openMenu() + } + }} + + onClick={( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + event: React.MouseEvent<HTMLElement, MouseEvent>, + ) => { + handleClick(option) + }} + > + <p>{option.name}</p> + </li> + ))} + </DropDownList> + )} + </div> + </DropDownMenu> + ) +} + + +DropDown.defaultProps = + { + placeholder: undefined, + } + +export default DropDown diff --git a/frontend/src/components/languageselector/index.tsx b/frontend/src/components/languageselector/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..575b56d7904b1558c685b9b4556a29b1c76b65e8 --- /dev/null +++ b/frontend/src/components/languageselector/index.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import DropDown from '../dropdownmenu' + + +function LanguageSelector() { + const { i18n, t } = useTranslation('common') + + const options = [ + { + name: 'English', + value: 'en', + }, + { + name: 'Norsk nynorsk', + value: 'nn', + }, + { + name: 'Norsk bokmål', + value: 'nb', + }, + ] + + + return ( + <DropDown options={options} + placeholder={t('header.selectLanguage')} + value={options[2]} + onChange={newValue => { + i18n.changeLanguage(newValue) + } + } + /> + ) +} + + +export default LanguageSelector diff --git a/frontend/src/components/link/index.tsx b/frontend/src/components/link/index.tsx index f3b06431ab0374ebc2ca642940b3f744e10e033c..f701b98cccd9dd2e504642548ed4607c520db7f5 100644 --- a/frontend/src/components/link/index.tsx +++ b/frontend/src/components/link/index.tsx @@ -1,13 +1,12 @@ -import styled, {css, DefaultTheme} from 'styled-components/macro' - +import React from 'react' +import styled, { css } from 'styled-components/macro' interface IStyledLink { - theme: DefaultTheme - external?: boolean - underline?: boolean - marginRight?: boolean - inheritColor?: boolean - noUnderline?: boolean + external?: boolean + underline?: boolean + marginRight?: boolean + inheritColor?: boolean + noUnderline?: boolean } @@ -32,25 +31,28 @@ const externalLinkIcon = ( d="M16.807.19a.596.596 0 0 0-.426-.173h-4.854a.596.596 0 0 0-.426.173.548.548 0 0 0-.18.409c0 .157.06.294.18.409l1.668 1.598-6.18 5.923a.281.281 0 0 0-.095.21c0 .078.031.148.094.208L7.67 9.983a.306.306 0 0 0 .436 0l6.18-5.923 1.67 1.599c.12.115.262.172.426.172a.596.596 0 0 0 .426-.172.547.547 0 0 0 .18-.409V.599a.548.548 0 0 0-.18-.409z"/> </g> </svg> -); +) const ExternalIcon = styled.span` margin-left: 1.2rem; position: relative; top: 1px; - zIndex: -1; + z-index: -1; ` const baseInputStyles = css<IStyledLink>` display: inline-flex; align-items: center; - color: ${props => props.external ? props.theme.linkExternalColor : props.theme.linkInternalColor}; + color: ${(props) => + props.external + ? props.theme.linkExternalColor + : props.theme.linkInternalColor}; - ${props => props.marginRight ? "margin-right: 3rem;" : ""} - ${props => props.inheritColor ? "color: inherit;" : ""} - ${props => props.underline ? "text-decoration: underline;" : ""} - ${props => props.noUnderline ? ":hover { text-decoration: none };" : ""} + ${(props) => (props.marginRight ? 'margin-right: 3rem;' : '')} + ${(props) => (props.inheritColor ? 'color: inherit;' : '')} + ${(props) => (props.underline ? 'text-decoration: underline;' : '')} + ${(props) => (props.noUnderline ? ':hover { text-decoration: none };' : '')} ` const StyledLink = styled.a<IStyledLink>` @@ -62,47 +64,67 @@ const StyledLink = styled.a<IStyledLink>` // ` function Link(props: ILink) { - const {children, external, to, noExternalIcon, mail} = props - - if (mail) { - return ( - <StyledLink href={`mailto:${to}`} {...props}> - {children} - </StyledLink> - ); - } - - if (external) { - const urlRegex = /^((http|https):\/\/)/; - const href = urlRegex.test(to) ? to : `//${to}`; - - return ( - <StyledLink - href={href} - target="_blank" - rel="noopener noreferrer" - {...props} - > - {children} - {!noExternalIcon && ( - <ExternalIcon>{externalLinkIcon}</ExternalIcon> - )} - </StyledLink> - ); - } - - // TODO Use StyledRouterLink for internal links when routes are set up - // return ( - // <StyledRouterLink {...props}> - // {children} - // </StyledRouterLink> - // ) + const { children, external, to, noExternalIcon, mail, underline, marginRight, inheritColor, noUnderline } = props + if (mail) { return ( - <StyledLink {...props}> - {children} - </StyledLink> + <StyledLink href={`mailto:${to}`} + external={external} + underline={underline} + marginRight={marginRight} + inheritColor={inheritColor} + noUnderline={noUnderline}> + {children} + </StyledLink> ) + } + + if (external) { + const urlRegex = /^((http|https):\/\/)/ + const href = urlRegex.test(to) ? to : `//${to}` + + return ( + <StyledLink + href={href} + target='_blank' + rel='noopener noreferrer' + external={external} + underline={underline} + marginRight={marginRight} + inheritColor={inheritColor} + noUnderline={noUnderline} + > + {children} + {!noExternalIcon && <ExternalIcon>{externalLinkIcon}</ExternalIcon>} + </StyledLink> + ) + } + + // TODO Use StyledRouterLink for internal links when routes are set up + // return ( + // <StyledRouterLink {...props}> + // {children} + // </StyledRouterLink> + // ) + + return <StyledLink + external={external} + underline={underline} + marginRight={marginRight} + inheritColor={inheritColor} + noUnderline={noUnderline}> + {children}</StyledLink> +} + +Link.defaultProps = { + external: false, + children: undefined, + marginRight: false, + noExternalIcon: false, + mail: false, + inheritColor: false, + underline: false, + noUnderline: false, } -export default Link \ No newline at end of file +export default Link diff --git a/frontend/src/components/link/link.test.tsx b/frontend/src/components/link/link.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6662674008c7c35a84994ff41ee79b61d3aa85cc --- /dev/null +++ b/frontend/src/components/link/link.test.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { render, waitFor, screen } from '@testing-library/react' +import Link from './index' + + +describe('Tests for Link component', () => { + test('External link prefixes route link with //', async () => { + render(<Link external to='example.org' />) + + await waitFor(() => screen.getByRole('link')) + + expect(screen.getByRole('link')).toHaveAttribute('href', '//example.org') + }) +}) \ No newline at end of file diff --git a/frontend/src/components/logobars/LogoBar.tsx b/frontend/src/components/logobars/LogoBar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2af512f339f8742d8428050f52ea4753ea82c047 --- /dev/null +++ b/frontend/src/components/logobars/LogoBar.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { appTheme } from 'appConfig' +import UiOLogoBar from './UiO' +import UiBLogoBar from './UiB' + + +function LogoBar() { + switch (appTheme) { + case 'uio': + return <UiOLogoBar /> + case 'uib': + return <UiBLogoBar /> + default: + return <UiOLogoBar /> + } +} + +export default LogoBar \ No newline at end of file diff --git a/frontend/src/components/logobars/UiB.tsx b/frontend/src/components/logobars/UiB.tsx new file mode 100644 index 0000000000000000000000000000000000000000..059ac0d05a1f779ac73565cfba4937b26d3d246c --- /dev/null +++ b/frontend/src/components/logobars/UiB.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import styled from 'styled-components/macro' + + +const LogoBarWrapper = styled.div` + background-color: ${(props) => props.theme.colors.secondary}); +` + +const LogoBar = styled.div` + margin: 0 auto; + max-width: ${({ theme }) => theme.appMaxWidth}); + padding: 0 ${({ theme }) => theme.horizontalMdPadding}); +` + +function UiBLogoBar() { + return ( + <LogoBarWrapper> + <LogoBar> + Insert UiB logo here + </LogoBar> + </LogoBarWrapper> + ) +} + +export default UiBLogoBar diff --git a/frontend/src/components/logobars/UiO.tsx b/frontend/src/components/logobars/UiO.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8796ac584d1d430e2be16ad14e6a10c0b8f32dc3 --- /dev/null +++ b/frontend/src/components/logobars/UiO.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import styled from 'styled-components/macro' + +import { useTranslation } from 'react-i18next' + + +const LogoBarWrapper = styled.div` + display: flex; + justify-content: center; + background-color: ${({ theme }) => theme.page.headerBackgroundColor}; +` + +type Language = { + language: string +} + +const Logo = styled.div<Language>` + background: ${props => props.language === 'en' ? 'url("/uio/uio-app-logo-en.png") left center no-repeat' : 'url("/uio/uio-app-logo-nb.png") left center no-repeat'}; + min-width: 20rem; + height: 2rem; +` + +function UiOLogoBar() { + const { i18n } = useTranslation(['common', 'footer']) + + return ( + <LogoBarWrapper> + <Logo language={i18n.language} /> + </LogoBarWrapper> + ) +} + +export default UiOLogoBar diff --git a/frontend/src/components/page/index.tsx b/frontend/src/components/page/index.tsx index 5d31ca8a20ff8c779db47e94380b346a8debdc21..811d46d7edec587ec56109b427987f45fb2683c1 100644 --- a/frontend/src/components/page/index.tsx +++ b/frontend/src/components/page/index.tsx @@ -1,3 +1,3 @@ import Page from 'components/page/page' -export { Page } +export default Page diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 059e8b4984845324ad08cd6a2d330e181d0c0b74..4237ac3faad693edf181048c19b300c16adc68ab 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -8,8 +8,8 @@ import 'i18n' import getTheme from 'themes' import GlobalStyle from 'globalStyles' import App from 'routes' -import reportWebVitals from './reportWebVitals' import Loading from 'components/loading' +import reportWebVitals from './reportWebVitals' function appRoot() { return ( diff --git a/frontend/src/routes/components/footer.tsx b/frontend/src/routes/components/footer.tsx index b36a7433dc291149b7100c5452e142bfb26d9204..f6821a56c801a7ae8588bbb35b3c1dc9c8f0306d 100644 --- a/frontend/src/routes/components/footer.tsx +++ b/frontend/src/routes/components/footer.tsx @@ -1,27 +1,91 @@ import React from 'react' import styled from 'styled-components/macro' +import { useTranslation } from 'react-i18next' +import { + appStagingWarning, + appTechnicalSupportLink, + reponsibleOrganization, responsibleOrganizationLink, +} from '../../appConfig' +import Link from '../../components/link' -// Placeholder footer component -const StyledFooter = styled.footer` +const FooterWrapper = styled.footer` background: ${({ theme }) => theme.footer.backgroundColor}; - padding: 4rem 0; + height: fit-content; + padding: 0rem ${({ theme }) => theme.horizontalPadding}; margin-top: auto; ` -const FooterWrapper = styled.div` - background: ${({ theme }) => theme.footer.backgroundColor}; - color: white; - height: fit-content; - max-width: ${({ theme }) => theme.appMaxWidth}; - margin: 0 auto; - padding: 0rem ${({ theme }) => theme.horizontalPadding}; +const FooterSection = styled.section` + header { + margin-bottom: 0.5rem; + font-size: 1.5rem; + } + + padding-left: 1rem; +` + + +const FooterSectionContent = styled.div` + font-size: 1rem; + padding-left: 0.3rem; +` + +const ContentContainer = styled.div` + width: fit-content; + color: ${({ theme }) => theme.footerTextColor}; + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + margin: auto; + padding-top: 1rem; + padding-right: 1rem; + padding-bottom: 1rem; ` -export default function Footer() { +const Footer: React.FunctionComponent = () => { + const { t } = useTranslation(['common', 'footer']) + return ( - <StyledFooter> - <FooterWrapper>Footer 123</FooterWrapper> - </StyledFooter> + <> + <FooterWrapper> + <ContentContainer> + <FooterSection> + <header>{t('footer:contactSectionHeader')}</header> + <FooterSectionContent> + <Link + external + to={appTechnicalSupportLink} + inheritColor + underline + > + {t('footer:contactHelp')} + </Link> + </FooterSectionContent> + </FooterSection> + + <FooterSection> + <header>{t('footer:responsibleOrganizationHeader')}</header> + <FooterSectionContent> + <Link + external + to={responsibleOrganizationLink} + inheritColor + underline + > + {reponsibleOrganization} + </Link> + </FooterSectionContent> + </FooterSection> + </ContentContainer> + </FooterWrapper> + {appStagingWarning && ( + <div className='alert'> + {t('staging')} + </div> + )} + </> ) } + +export default Footer diff --git a/frontend/src/routes/components/header.tsx b/frontend/src/routes/components/header.tsx index b297d552d8ed737f12652fc4623c20b4a58095d3..0a4f62cbdfa3e2ff31d8a8d7a05155ed1abf5361 100644 --- a/frontend/src/routes/components/header.tsx +++ b/frontend/src/routes/components/header.tsx @@ -1,45 +1,67 @@ import React from 'react' import styled from 'styled-components/macro' +import { useTranslation } from 'react-i18next' +import LogoBar from '../../components/logobars/LogoBar' +import LanguageSelector from '../../components/languageselector' -// Placeholder header component! const MainWrapper = styled.div` - background-color: green; + color: ${({ theme }) => theme.page.headerColor}; + background-color: ${({ theme }) => theme.page.headerBackgroundColor}; ` const Main = styled.div` + display: flex; + justify-content: space-between; margin: 0 auto; - max-width: ${({ theme }) => theme.maxWidth}; - height: ${({ theme }) => theme.header.height}; - padding: 2.5rem ${({ theme }) => theme.horizontalPadding} 3rem - ${({ theme }) => theme.horizontalPadding}; + max-width: ${props => props.theme.appMaxWidth}; + padding: ${props => `0.5rem ${props.theme.horizontalPadding} 1rem ${props.theme.horizontalPadding}`}; +` + +const Menu = styled.ul` + list-style-type: none; +` + +const MenuItem = styled.div` + font-size: 1rem; + display: inline; ` -const MainRow = styled.div` +const TitleBox = styled.div` display: flex; - justify-content: space-between; + flex-direction: column; + padding-left: 3rem; ` -const PageTitle = styled.div` - color: blue; +const ShortTitle = styled.div` + font-size: 2rem; ` -const H2 = styled.h2` - font-size: 2.8rem; - line-height: 4.5rem; + +const LongTitle = styled.div` + font-size: 1rem; ` -export default function Header() { +function Header() { + const { t } = useTranslation('common') + return ( <header> + <LogoBar /> <MainWrapper> <Main> - <MainRow> - <PageTitle> - <H2>Greg!</H2> - </PageTitle> - </MainRow> + <TitleBox> + <ShortTitle>{t('header.applicationTitleShort')}</ShortTitle> + <LongTitle>{t('header.applicationTitleLong')}</LongTitle> + </TitleBox> + <Menu> + <MenuItem> + <LanguageSelector/> + </MenuItem> + </Menu> </Main> </MainWrapper> </header> ) } + +export default Header diff --git a/frontend/src/routes/components/notFound.tsx b/frontend/src/routes/components/notFound.tsx index 3dbdd110d58ec4213cb4f945d958d1954607513b..a0bfc390680e2ec063f82e71c6b55109339a6f48 100644 --- a/frontend/src/routes/components/notFound.tsx +++ b/frontend/src/routes/components/notFound.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Page } from 'components/page' +import Page from 'components/page' export default function NotFound() { return ( diff --git a/frontend/src/routes/frontpage/index.tsx b/frontend/src/routes/frontpage/index.tsx index b552134e5c0110fd15b4df455ddacd263c4f3090..859b8c518425e74b075bb443bec5e6dbcb7e60d3 100644 --- a/frontend/src/routes/frontpage/index.tsx +++ b/frontend/src/routes/frontpage/index.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import DateInput from 'components/dateinput' -import { Page } from 'components/page' +import Page from 'components/page' import { appTimezone, appVersion, appTheme } from 'appConfig' import { useTranslation } from 'react-i18next' @@ -21,9 +21,9 @@ export default function FrontPage() { setApiHealth(result) } }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars .catch((error) => { setApiHealth('error') - console.log(error) }) } return ( diff --git a/frontend/src/routes/register/index.tsx b/frontend/src/routes/register/index.tsx index f9c66e80ccb980407473cf6b9e9af11cd58764d0..2e7736db6ab03af384e17a9c2d9ea3ec75b456b9 100644 --- a/frontend/src/routes/register/index.tsx +++ b/frontend/src/routes/register/index.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { Page } from 'components/page' +import Page from 'components/page' export default function Register() { return ( diff --git a/frontend/src/routes/sponsor/frontpage/index.tsx b/frontend/src/routes/sponsor/frontpage/index.tsx index c63cb35ea86a3c651666b1fdc3db77c87dc36a4c..027197853901cf466126e072941b15561f21c2b2 100644 --- a/frontend/src/routes/sponsor/frontpage/index.tsx +++ b/frontend/src/routes/sponsor/frontpage/index.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { Page } from 'components/page' +import Page from 'components/page' function FrontPage() { return ( diff --git a/frontend/src/routes/sponsor/guestInfo/index.tsx b/frontend/src/routes/sponsor/guestInfo/index.tsx index d4b0f7442bf70dcc9b4e726dd7d0a3bac918d7a2..bb652151f9fefa03d7e17ea0c4776947a68dec48 100644 --- a/frontend/src/routes/sponsor/guestInfo/index.tsx +++ b/frontend/src/routes/sponsor/guestInfo/index.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useParams } from 'react-router-dom' -import {Page} from 'components/page' +import Page from 'components/page' type GuestInfoParams = { id: string diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js index b1f04abd2b8ecd454be5833ff912e2000a8f7f86..a7d60f51a8c878a73e44ebf9849b965a65b330db 100644 --- a/frontend/src/setupProxy.js +++ b/frontend/src/setupProxy.js @@ -1,6 +1,6 @@ const { createProxyMiddleware } = require('http-proxy-middleware') -module.exports = function (app) { +module.exports = (app) => { app.use( '/api', createProxyMiddleware({ diff --git a/frontend/src/styled.d.ts b/frontend/src/styled.d.ts index a91823417eaed6818da06451516dbc9dd07a8c78..7031296ac88d87b9ff9da09ddd7a0c4d7bfcc3f4 100644 --- a/frontend/src/styled.d.ts +++ b/frontend/src/styled.d.ts @@ -7,6 +7,7 @@ declare module 'styled-components' { colors: { main: string secondary: string + dropDownMenuBackground: string } footer: { backgroundColor: string @@ -17,9 +18,14 @@ declare module 'styled-components' { horizontalPadding: string linkExternalColor: string linkInternalColor: string + footerBackgroundColor: string + footerTextColor: string + footerJustifyContent: string + horizontalMdPadding: string maxWidth: string page: { headerColor: string + headerBackgroundColor: string horizontalPadding: string } } diff --git a/frontend/src/test-utils.tsx b/frontend/src/test-utils.tsx index 10d9b958a0aa203673faa8bae81053b340b6dcf7..10782331e89c522f61684182381cdedda326771f 100644 --- a/frontend/src/test-utils.tsx +++ b/frontend/src/test-utils.tsx @@ -5,9 +5,7 @@ import { ThemeProvider } from 'styled-components/macro' import mainTheme from 'themes/main' // Providers used in test rendering -const AllTheProviders = ({ children }: any) => { - return <ThemeProvider theme={mainTheme}> {children} </ThemeProvider> -} +const AllTheProviders = ({ children }: any) => <ThemeProvider theme={mainTheme}> {children} </ThemeProvider> // Custom testing-library/react renderer using our providers. const customRender = (ui: React.ReactElement, options?: any) => diff --git a/frontend/src/themes/color.ts b/frontend/src/themes/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..d13e0f21856f5fa36dce9942881c5289cf1d0b45 --- /dev/null +++ b/frontend/src/themes/color.ts @@ -0,0 +1,10 @@ + +export module Color { + export const white = '#FFFFFF' + export const hotPink = '#FF69B4' + export const blueish = '#2771bb' + export const lightOliveGreen = '#91BD60' + export const black = '#000000' + export const darkGray = '#2D2D2E' + export const lighterBlack = '#282c34' +} diff --git a/frontend/src/themes/main.ts b/frontend/src/themes/main.ts index e03827205a038806e78c27605a3877ff81869820..8af960ea1c0c06ecbcf1b84c2efd321980353f78 100644 --- a/frontend/src/themes/main.ts +++ b/frontend/src/themes/main.ts @@ -6,6 +6,7 @@ const mainTheme: DefaultTheme = { colors: { main: 'hotPink', secondary: 'white', + dropDownMenuBackground: 'grey' }, footer: { backgroundColor: 'black', @@ -17,8 +18,13 @@ const mainTheme: DefaultTheme = { linkInternalColor: 'white', linkExternalColor: 'blueish', maxWidth: '110rem', + footerBackgroundColor: 'green', + footerTextColor: 'white', + footerJustifyContent: 'flex-end', + horizontalMdPadding: '6.5rem', page: { - headerColor: 'grey', + headerColor: 'white', + headerBackgroundColor: 'black', horizontalPadding: '0rem', }, } diff --git a/frontend/src/themes/uib.ts b/frontend/src/themes/uib.ts index 0cd69cd5732a76e9f1a4873916a17536c4de9c51..2d272789dcaba43a0569514d6d72041c38f75a25 100644 --- a/frontend/src/themes/uib.ts +++ b/frontend/src/themes/uib.ts @@ -2,6 +2,7 @@ const uibTheme = { colors: { main: 'hotPink', secondary: 'black', + dropDownMenuBackground: 'grey' }, } diff --git a/frontend/src/themes/uio.ts b/frontend/src/themes/uio.ts index 6481aeb85e9e5a362322625834cdcabf836b9c06..6f0f9a1c4c4893660ae49153eda119c888d684cf 100644 --- a/frontend/src/themes/uio.ts +++ b/frontend/src/themes/uio.ts @@ -2,6 +2,7 @@ const uioTheme = { colors: { main: 'hotPink', secondary: 'white', + dropDownMenuBackground: 'grey' }, } diff --git a/greg/api/urls.py b/greg/api/urls.py index c20513efc8e5e216945fc5e20ab8518c1859abf2..a2c98d41ba310ce9dbba1f4672a56d103206fc37 100644 --- a/greg/api/urls.py +++ b/greg/api/urls.py @@ -13,7 +13,7 @@ from greg.api.views.person import ( from greg.api.views.role_type import RoleTypeViewSet from greg.api.views.sponsor import SponsorViewSet, SponsorGuestsViewSet -router = DefaultRouter() +router = DefaultRouter(trailing_slash=False) router.register(r"persons", PersonViewSet, basename="person") router.register(r"roletypes", RoleTypeViewSet, basename="roletype") router.register(r"consenttypes", ConsentTypeViewSet, basename="consenttype") @@ -23,33 +23,34 @@ router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit") urlpatterns = router.urls +# Allowing trailing slashes to be optional for the URLs below urlpatterns += [ re_path( - r"^persons/(?P<person_id>[0-9]+)/roles/$", + r"^persons/(?P<person_id>[0-9]+)/roles/?$", RoleViewSet.as_view({"get": "list", "post": "create"}), name="person_role-list", ), re_path( - r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/$", + r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/?$", RoleViewSet.as_view( {"get": "retrieve", "patch": "partial_update", "delete": "destroy"} ), name="person_role-detail", ), re_path( - r"^persons/(?P<person_id>[0-9]+)/identities/$", + r"^persons/(?P<person_id>[0-9]+)/identities/?$", IdentityViewSet.as_view({"get": "list", "post": "create"}), name="person_identity-list", ), re_path( - r"^persons/(?P<person_id>[0-9]+)/identities/(?P<id>[0-9]+)$", + r"^persons/(?P<person_id>[0-9]+)/identities/(?P<id>[0-9]+)/?$", IdentityViewSet.as_view( {"get": "retrieve", "delete": "destroy", "patch": "partial_update"} ), name="person_identity-detail", ), re_path( - r"^sponsors/(?P<sponsor_id>[0-9]+)/guests/$", + r"^sponsors/(?P<sponsor_id>[0-9]+)/guests/?$", SponsorGuestsViewSet.as_view({"get": "list"}), name="sponsor_guests-list", ),