Merge branch 'master' into 'production'
Merge master to production. See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!110
This commit is contained in:
@@ -46,5 +46,6 @@ module.exports = {
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
"@typescript-eslint/default-param-last": "warn",
|
||||
},
|
||||
};
|
||||
|
||||
Generated
+170
-213
@@ -20,8 +20,10 @@
|
||||
"next": "^12.1.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-csv": "^2.2.2",
|
||||
"react-dnd": "15.0.2",
|
||||
"react-dnd-html5-backend": "15.0.2",
|
||||
"react-dnd-touch-backend": "15.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^8.0.2",
|
||||
@@ -39,7 +41,6 @@
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/node": "^16.11.36",
|
||||
"@types/react": "^17.0.19",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-csv": "^1.1.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/shortid": "^0.0.29",
|
||||
@@ -2746,6 +2747,21 @@
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
||||
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g=="
|
||||
},
|
||||
"node_modules/@react-dnd/asap": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz",
|
||||
"integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg=="
|
||||
},
|
||||
"node_modules/@react-dnd/invariant": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz",
|
||||
"integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw=="
|
||||
},
|
||||
"node_modules/@react-dnd/shallowequal": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz",
|
||||
"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="
|
||||
},
|
||||
"node_modules/@rjsf/core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-4.2.0.tgz",
|
||||
@@ -3104,6 +3120,7 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
@@ -3200,7 +3217,7 @@
|
||||
"version": "16.11.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz",
|
||||
"integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
@@ -3240,15 +3257,6 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-beautiful-dnd": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
|
||||
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-csv": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-csv/-/react-csv-1.1.2.tgz",
|
||||
@@ -3267,17 +3275,6 @@
|
||||
"@types/react": "^17"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
@@ -4895,14 +4892,6 @@
|
||||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"dependencies": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
@@ -5307,6 +5296,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dnd-core": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-15.0.2.tgz",
|
||||
"integrity": "sha512-38dMXvgyucWsp7cjdg+OtLqghLPZmr3H8Pf3I5OAqnpLWeT5aY3W5NCsJruoGn0pKFoL+Rh73WlT4aGSe5+rKg==",
|
||||
"dependencies": {
|
||||
"@react-dnd/asap": "^4.0.0",
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"redux": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
@@ -6601,7 +6600,7 @@
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.7",
|
||||
@@ -9099,11 +9098,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"node_modules/memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
@@ -9735,9 +9729,9 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"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==",
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -9915,9 +9909,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.15.0.tgz",
|
||||
"integrity": "sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==",
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz",
|
||||
"integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
@@ -9926,9 +9920,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
|
||||
"integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
@@ -10856,9 +10850,9 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.0.tgz",
|
||||
"integrity": "sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
@@ -10867,7 +10861,6 @@
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"npmlog": "^4.0.1",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
@@ -11050,11 +11043,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
@@ -11072,7 +11060,7 @@
|
||||
"node_modules/rc/node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11089,29 +11077,57 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-beautiful-dnd": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
|
||||
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.5 || ^17.0.0",
|
||||
"react-dom": "^16.8.5 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-csv": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
|
||||
"integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
|
||||
},
|
||||
"node_modules/react-dnd": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-15.0.2.tgz",
|
||||
"integrity": "sha512-TC2fLltThXNo8haTfZmwOwZ8+QIscxqfMLPPQQ0lB5+hYRHO5LMNfER39Ca5b6OKWe78o+ldPRRdsjPUxJ9hJg==",
|
||||
"dependencies": {
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"@react-dnd/shallowequal": "^2.0.0",
|
||||
"dnd-core": "15.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"hoist-non-react-statics": "^3.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/hoist-non-react-statics": ">= 3.3.1",
|
||||
"@types/node": ">= 12",
|
||||
"@types/react": ">= 16",
|
||||
"react": ">= 16.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/hoist-non-react-statics": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd-html5-backend": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-15.0.2.tgz",
|
||||
"integrity": "sha512-PTXbWyv9Ij9BnOjDrH5wfIeBJNH9dD9Q4YAvDCBF7zOG7GLY8hvwNj9oT/7+EnkGe5qSFiqL+T7VPwAQdGEpFA==",
|
||||
"dependencies": {
|
||||
"dnd-core": "15.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd-touch-backend": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-15.0.2.tgz",
|
||||
"integrity": "sha512-hF/mxkOChur26dKNUIbb2rq4MdOqjFz9Y6FprKaYVKBHIaAc9nNBwgcH2j4/PI1diy8H0pYnU0BkfEFYpMJ75g==",
|
||||
"dependencies": {
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"dnd-core": "15.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||
@@ -11174,30 +11190,6 @@
|
||||
"react-dom": "^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
|
||||
"integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3 || ^17 || ^18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz",
|
||||
@@ -11795,15 +11787,15 @@
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.30.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.4.tgz",
|
||||
"integrity": "sha512-3Onig53Y6lji4NIZo69s14mERXXY/GV++6CzOYx/Rd8bnTwbhFbL09WZd7Ag/CCnA0WxFID8tkY0QReyfL6v0Q==",
|
||||
"version": "0.30.7",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.7.tgz",
|
||||
"integrity": "sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.1",
|
||||
"node-addon-api": "^4.3.0",
|
||||
"prebuild-install": "^7.0.1",
|
||||
"node-addon-api": "^5.0.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^2.1.1",
|
||||
@@ -13434,11 +13426,6 @@
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz",
|
||||
@@ -13947,14 +13934,6 @@
|
||||
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/use-memo-one": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
|
||||
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
@@ -16430,6 +16409,21 @@
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
|
||||
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g=="
|
||||
},
|
||||
"@react-dnd/asap": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz",
|
||||
"integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg=="
|
||||
},
|
||||
"@react-dnd/invariant": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz",
|
||||
"integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw=="
|
||||
},
|
||||
"@react-dnd/shallowequal": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz",
|
||||
"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="
|
||||
},
|
||||
"@rjsf/core": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-4.2.0.tgz",
|
||||
@@ -16724,6 +16718,7 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
@@ -16820,7 +16815,7 @@
|
||||
"version": "16.11.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz",
|
||||
"integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
@@ -16860,15 +16855,6 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-beautiful-dnd": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz",
|
||||
"integrity": "sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-csv": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-csv/-/react-csv-1.1.2.tgz",
|
||||
@@ -16887,17 +16873,6 @@
|
||||
"@types/react": "^17"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
@@ -18111,14 +18086,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"requires": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
@@ -18413,6 +18380,16 @@
|
||||
"path-type": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"dnd-core": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-15.0.2.tgz",
|
||||
"integrity": "sha512-38dMXvgyucWsp7cjdg+OtLqghLPZmr3H8Pf3I5OAqnpLWeT5aY3W5NCsJruoGn0pKFoL+Rh73WlT4aGSe5+rKg==",
|
||||
"requires": {
|
||||
"@react-dnd/asap": "^4.0.0",
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"redux": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
@@ -19414,7 +19391,7 @@
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.7",
|
||||
@@ -21327,11 +21304,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
@@ -21698,9 +21670,9 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true
|
||||
},
|
||||
"moment-duration-format-commonjs": {
|
||||
@@ -21812,17 +21784,17 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.15.0.tgz",
|
||||
"integrity": "sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA==",
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz",
|
||||
"integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==",
|
||||
"requires": {
|
||||
"semver": "^7.3.5"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
|
||||
"integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
@@ -22506,9 +22478,9 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.0.tgz",
|
||||
"integrity": "sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"requires": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
@@ -22517,7 +22489,6 @@
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"npmlog": "^4.0.1",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
@@ -22650,11 +22621,6 @@
|
||||
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
|
||||
"dev": true
|
||||
},
|
||||
"raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
@@ -22669,7 +22635,7 @@
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -22682,25 +22648,40 @@
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"react-beautiful-dnd": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
|
||||
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"react-csv": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
|
||||
"integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
|
||||
},
|
||||
"react-dnd": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-15.0.2.tgz",
|
||||
"integrity": "sha512-TC2fLltThXNo8haTfZmwOwZ8+QIscxqfMLPPQQ0lB5+hYRHO5LMNfER39Ca5b6OKWe78o+ldPRRdsjPUxJ9hJg==",
|
||||
"requires": {
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"@react-dnd/shallowequal": "^2.0.0",
|
||||
"dnd-core": "15.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"hoist-non-react-statics": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"react-dnd-html5-backend": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-15.0.2.tgz",
|
||||
"integrity": "sha512-PTXbWyv9Ij9BnOjDrH5wfIeBJNH9dD9Q4YAvDCBF7zOG7GLY8hvwNj9oT/7+EnkGe5qSFiqL+T7VPwAQdGEpFA==",
|
||||
"requires": {
|
||||
"dnd-core": "15.0.2"
|
||||
}
|
||||
},
|
||||
"react-dnd-touch-backend": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-15.0.2.tgz",
|
||||
"integrity": "sha512-hF/mxkOChur26dKNUIbb2rq4MdOqjFz9Y6FprKaYVKBHIaAc9nNBwgcH2j4/PI1diy8H0pYnU0BkfEFYpMJ75g==",
|
||||
"requires": {
|
||||
"@react-dnd/invariant": "^2.0.0",
|
||||
"dnd-core": "15.0.2"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||
@@ -22751,19 +22732,6 @@
|
||||
"integrity": "sha512-CH/VK6d+tpVjJ8rTXfh1dDt6GWedTgCU0668p8toqhAc3vy0Lu872O2RKYDSpkUrlbHI08fjUPTl++nExp6gag==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
|
||||
"integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"react-toastify": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz",
|
||||
@@ -23215,14 +23183,14 @@
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.30.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.4.tgz",
|
||||
"integrity": "sha512-3Onig53Y6lji4NIZo69s14mERXXY/GV++6CzOYx/Rd8bnTwbhFbL09WZd7Ag/CCnA0WxFID8tkY0QReyfL6v0Q==",
|
||||
"version": "0.30.7",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.30.7.tgz",
|
||||
"integrity": "sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==",
|
||||
"requires": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.1",
|
||||
"node-addon-api": "^4.3.0",
|
||||
"prebuild-install": "^7.0.1",
|
||||
"node-addon-api": "^5.0.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^2.1.1",
|
||||
@@ -24520,11 +24488,6 @@
|
||||
"integrity": "sha512-FLHDDsIDducw7MBcRWlFtW2Tm50DoKOSFf0Nzx17qwXj8REXCte0eUkHrJl9QU3Bl9arG3XNYX0PcHpZ9xyuLw==",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz",
|
||||
@@ -24886,12 +24849,6 @@
|
||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
||||
"dev": true
|
||||
},
|
||||
"use-memo-one": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
|
||||
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
|
||||
+3
-2
@@ -38,7 +38,6 @@
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/node": "^16.11.36",
|
||||
"@types/react": "^17.0.19",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-csv": "^1.1.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/shortid": "^0.0.29",
|
||||
@@ -76,8 +75,10 @@
|
||||
"next": "^12.1.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-csv": "^2.2.2",
|
||||
"react-dnd": "15.0.2",
|
||||
"react-dnd-html5-backend": "15.0.2",
|
||||
"react-dnd-touch-backend": "15.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^8.0.2",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
|
||||
} from "@utils/auth";
|
||||
import { APIPath, postBackendAPI } from "./backend";
|
||||
|
||||
export type AuthTokenRequest = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type AuthToken = {
|
||||
access: string;
|
||||
refresh: string;
|
||||
};
|
||||
|
||||
export type AuthRefreshRequest = {
|
||||
refresh: AuthToken["refresh"]
|
||||
};
|
||||
|
||||
export type RefreshedAuthToken = {
|
||||
access: string;
|
||||
};
|
||||
|
||||
async function generateToken(username: string, password: string): Promise<AuthToken> {
|
||||
const resp = await postBackendAPI<AuthTokenRequest, AuthToken>({ path: APIPath.AUTH_TOKEN_GENERATE }, { username, password });
|
||||
return {
|
||||
access: resp.access,
|
||||
refresh: resp.refresh,
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshToken(): Promise<boolean> {
|
||||
// Get refresh token if exists
|
||||
const refresh = getRefreshTokenCookie();
|
||||
if (!refresh) {
|
||||
deleteTokenCookies();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Renew access token
|
||||
const { access } = await postBackendAPI<AuthRefreshRequest, RefreshedAuthToken>({ path: APIPath.AUTH_TOKEN_REFRESH }, { refresh });
|
||||
setAccessTokenCookie(access);
|
||||
} catch (err) {
|
||||
// If we get HTTP500 or something form backend, do not clear cookies
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const login = async (username: string, password: string): Promise<void> => {
|
||||
const { access, refresh } = await generateToken(username, password);
|
||||
setAccessTokenCookie(access);
|
||||
setRefreshTokenCookie(refresh);
|
||||
};
|
||||
|
||||
export const authenticate = async (): Promise<boolean> => {
|
||||
// Find access token
|
||||
const token = getAccessTokenCookie();
|
||||
if (!token) {
|
||||
// Unnecessary, but might be good idea to clear old refresh tokens etc.
|
||||
deleteTokenCookies();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await postBackendAPI({ path: APIPath.AUTH_TOKEN_VERIFY }, { token });
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Handle refresh automatically
|
||||
return refreshToken();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
|
||||
import { getAccessTokenCookie } from "@utils/auth";
|
||||
|
||||
const axiosInstance: AxiosInstance = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
});
|
||||
|
||||
export enum APIPath {
|
||||
TAGS = "/tags/:id",
|
||||
EVENTS = "/events/:id",
|
||||
FEED = "/feed/:id",
|
||||
JOBADS = "/jobads/:id",
|
||||
SIGNUPS = "/signup/:id",
|
||||
SIGNUPS_EDIT = "/signup/:id/edit",
|
||||
SIGNUP_FORMS = "/signupForm/:id",
|
||||
SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail",
|
||||
SIGNUP_FORMS_SIGNUPS = "/signupForm/:id/signups",
|
||||
AUTH_TOKEN_GENERATE = "/token",
|
||||
AUTH_TOKEN_VERIFY = "/token/verify",
|
||||
AUTH_TOKEN_REFRESH = "/token/refresh",
|
||||
}
|
||||
|
||||
export type API = {
|
||||
path: APIPath;
|
||||
urlParams?: {
|
||||
id?: string | number;
|
||||
};
|
||||
queryParams?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
since?: Date;
|
||||
uuid?: string;
|
||||
};
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
type Headers = {
|
||||
Authorization?: string;
|
||||
};
|
||||
|
||||
const getAuthHeader = (): string => {
|
||||
const jwt = getAccessTokenCookie();
|
||||
return `Bearer ${jwt}`;
|
||||
};
|
||||
|
||||
const getHeaders = (auth?: boolean): Headers => {
|
||||
if (auth) {
|
||||
return {
|
||||
Authorization: getAuthHeader(),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const fillUrlParams = (apiPath: APIPath, params: API["urlParams"] = {}): string => {
|
||||
const path = apiPath
|
||||
.split("/")
|
||||
.map((urlComponent) => {
|
||||
// fill in each placeholder component like ':id' with value from params
|
||||
if (urlComponent.startsWith(":")) {
|
||||
const key = urlComponent.substring(1);
|
||||
const value = params[key] ?? "";
|
||||
return value;
|
||||
}
|
||||
return urlComponent;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("/");
|
||||
// code above strips leading and trailing '/' from path
|
||||
return `/${path}/`;
|
||||
};
|
||||
|
||||
const callBackendAPI = async <RequestType, ResponseType>(
|
||||
path: APIPath,
|
||||
urlParams: API["urlParams"],
|
||||
queryParams: API["queryParams"],
|
||||
method: AxiosRequestConfig["method"],
|
||||
headers: Headers,
|
||||
requestBody: RequestType,
|
||||
): Promise<ResponseType> => {
|
||||
const url = fillUrlParams(path, urlParams);
|
||||
const request: AxiosRequestConfig = {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
params: queryParams,
|
||||
data: requestBody,
|
||||
responseType: "json",
|
||||
};
|
||||
const response = await axiosInstance.request<ResponseType>(request);
|
||||
|
||||
const arrayResp = (response.data as { results?: ResponseType });
|
||||
if (Array.isArray(arrayResp.results)) {
|
||||
return arrayResp.results;
|
||||
}
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getBackendAPI = async <ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
}: API): Promise<ResponseType> => {
|
||||
const headers = getHeaders(authenticated);
|
||||
return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "GET", headers, undefined);
|
||||
};
|
||||
|
||||
export const postBackendAPI = async <RequestType, ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
}: API, body: RequestType): Promise<ResponseType> => {
|
||||
const headers = getHeaders(authenticated);
|
||||
return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "POST", headers, body);
|
||||
};
|
||||
|
||||
export const putBackendAPI = async <RequestType, ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
}: API, body: RequestType): Promise<ResponseType> => {
|
||||
const headers = getHeaders(authenticated);
|
||||
return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "PUT", headers, body);
|
||||
};
|
||||
|
||||
export const deleteBackendAPI = async <ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
}: API): Promise<ResponseType> => {
|
||||
const headers = getHeaders(authenticated);
|
||||
return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "DELETE", headers, undefined);
|
||||
};
|
||||
|
||||
export const fetcher = <ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
}: API) => getBackendAPI<ResponseType>({
|
||||
path, urlParams, queryParams, authenticated,
|
||||
});
|
||||
+38
-56
@@ -1,11 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from "axios";
|
||||
import Event from "@models/Event";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import {
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
|
||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/events/`;
|
||||
|
||||
export interface Options {
|
||||
interface Options {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
auth?: boolean;
|
||||
@@ -13,83 +12,66 @@ export interface Options {
|
||||
}
|
||||
|
||||
class EventApi {
|
||||
static async getEvent(id: number, auth = false): Promise<Event> {
|
||||
static getEvent = async (id: number, auth = false): Promise<Event> => {
|
||||
try {
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(`${URL}${id}/`, {
|
||||
headers,
|
||||
return await getBackendAPI<Event>({
|
||||
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getEvents(options: Options = {}): Promise<Event[]> {
|
||||
const {
|
||||
since, limit, offset, auth,
|
||||
} = options;
|
||||
static getEvents = async ({
|
||||
since, limit, offset, auth,
|
||||
}: Options = {}): Promise<Event[]> => {
|
||||
try {
|
||||
const params = {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(`${URL}`, {
|
||||
headers,
|
||||
params,
|
||||
});
|
||||
return resp.data.results;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async createEvent(data: Event): Promise<Event> {
|
||||
try {
|
||||
const resp = await axios.post(URL, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
return await getBackendAPI<Event[]>({
|
||||
path: APIPath.EVENTS,
|
||||
queryParams: {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async updateEvent(data: Event): Promise<Event> {
|
||||
static createEvent = async (data: Event): Promise<Event> => {
|
||||
try {
|
||||
const putUrl = `${URL}${data.id}/`;
|
||||
const resp = await axios.put(putUrl, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await postBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async deleteEvent(id: number) {
|
||||
static updateEvent = async (data: Event): Promise<Event> => {
|
||||
try {
|
||||
const resp = await axios.delete(`${URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await putBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static deleteEvent = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default EventApi;
|
||||
|
||||
+39
-57
@@ -1,89 +1,71 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from "axios";
|
||||
import Post from "@models/Feed";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import {
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
|
||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/feed/`;
|
||||
|
||||
export interface Options {
|
||||
interface Options {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
auth?: boolean;
|
||||
}
|
||||
|
||||
class FeedApi {
|
||||
static async getFeed(options: Options = {}): Promise<Post[]> {
|
||||
const {
|
||||
limit, offset, auth,
|
||||
} = options;
|
||||
const params = {
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
static getPost = async (id: number, auth?: boolean): Promise<Post> => {
|
||||
try {
|
||||
const resp = await axios.get(URL, { params, headers });
|
||||
return resp.data.results;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async getPost(id: number, options: Options = {}): Promise<Post> {
|
||||
const { auth } = options;
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
try {
|
||||
const resp = await axios.get(`${URL}${id}/`, { headers });
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async createPost(data: Post): Promise<Post> {
|
||||
try {
|
||||
const resp = await axios.post(URL, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
return await getBackendAPI<Post>({
|
||||
path: APIPath.FEED, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async updatePost(data: Post): Promise<Post> {
|
||||
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<Post[]> => {
|
||||
try {
|
||||
const putUrl = `${URL}${data.id}/`;
|
||||
const resp = await axios.put(putUrl, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
return await getBackendAPI<Post[]>({
|
||||
path: APIPath.FEED,
|
||||
queryParams: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async deletePost(id: number) {
|
||||
static createPost = async (data: Post): Promise<Post> => {
|
||||
try {
|
||||
const resp = await axios.delete(`${URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static updatePost = async (data: Post): Promise<Post> => {
|
||||
try {
|
||||
return await putBackendAPI<Post, Post>({
|
||||
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
static deletePost = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default FeedApi;
|
||||
|
||||
+38
-56
@@ -1,11 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from "axios";
|
||||
import JobAd from "@models/JobAd";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import {
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
|
||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/jobads/`;
|
||||
|
||||
export interface Options {
|
||||
interface Options {
|
||||
since?: Date;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
@@ -13,83 +12,66 @@ export interface Options {
|
||||
}
|
||||
|
||||
class JobAdApi {
|
||||
static async getJobAds(options: Options = {}): Promise<JobAd[]> {
|
||||
const {
|
||||
since, limit, offset, auth,
|
||||
} = options;
|
||||
static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
|
||||
try {
|
||||
const params = {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(`${URL}`, {
|
||||
headers,
|
||||
params,
|
||||
return await getBackendAPI({
|
||||
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
return resp.data.results;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getJobAd(id: number, auth = false): Promise<JobAd> {
|
||||
static getJobAds = async ({
|
||||
since, limit, offset, auth,
|
||||
}: Options = {}): Promise<JobAd[]> => {
|
||||
try {
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(`${URL}${id}/`, {
|
||||
headers,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async createJobAd(data: JobAd): Promise<JobAd> {
|
||||
try {
|
||||
const resp = await axios.post(URL, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
return await getBackendAPI<JobAd[]>({
|
||||
path: APIPath.JOBADS,
|
||||
queryParams: {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async updateJobAd(data: JobAd): Promise<JobAd> {
|
||||
static createJobAd = async (data: JobAd): Promise<JobAd> => {
|
||||
try {
|
||||
const putUrl = `${URL}${data.id}/`;
|
||||
const resp = await axios.put(putUrl, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await postBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async deleteJobAd(id: number) {
|
||||
static updateJobAd = async (data: JobAd): Promise<JobAd> => {
|
||||
try {
|
||||
const resp = await axios.delete(`${URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await putBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static deleteJobAd = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default JobAdApi;
|
||||
|
||||
+66
-95
@@ -1,182 +1,153 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from "axios";
|
||||
import { Signup, SignupForm } from "@models/Signup";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import {
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
|
||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
|
||||
export const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Options {
|
||||
// limit?: number;
|
||||
// offset?: number;
|
||||
// auth?: boolean;
|
||||
}
|
||||
export type EmailRequest = {
|
||||
mode: "all" | "actual" | "reserve";
|
||||
subject: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
class SignupApi {
|
||||
static async getSignup(id: number): Promise<Signup> {
|
||||
static getSignup = async (id: number): Promise<Signup> => {
|
||||
try {
|
||||
const resp = await axios.get(`${URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
return await getBackendAPI<Signup>({
|
||||
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async createSignup(data: Signup): Promise<Signup> {
|
||||
static createSignup = async (data: Signup): Promise<Signup> => {
|
||||
try {
|
||||
const resp = await axios.post(URL, data);
|
||||
return resp.data;
|
||||
return await postBackendAPI<Signup, Signup>({
|
||||
path: APIPath.SIGNUPS,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async updateSignup(data: Signup, uuid: string): Promise<Signup> {
|
||||
static updateSignup = async (data: Signup, uuid: string): Promise<Signup> => {
|
||||
try {
|
||||
const { id } = data;
|
||||
if (!id) throw new Error("SignupId required!");
|
||||
const resp = await axios.put(`${URL}${id}/edit/`, data, {
|
||||
params: { uuid },
|
||||
});
|
||||
return resp.data;
|
||||
return await putBackendAPI<Signup, Signup>({
|
||||
path: APIPath.SIGNUPS_EDIT,
|
||||
urlParams: {
|
||||
id,
|
||||
},
|
||||
queryParams: {
|
||||
uuid,
|
||||
},
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getSignupUUID(id: number, uuid: string): Promise<Signup> {
|
||||
static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
|
||||
try {
|
||||
const resp = await axios.get(`${URL}${id}/edit/`, {
|
||||
params: {
|
||||
return await getBackendAPI<Signup>({
|
||||
path: APIPath.SIGNUPS_EDIT,
|
||||
urlParams: {
|
||||
id,
|
||||
},
|
||||
queryParams: {
|
||||
uuid,
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async deleteSignup(id: number) {
|
||||
static deleteSignup = async (id: number): Promise<void> => {
|
||||
try {
|
||||
const resp = await axios.delete(`${URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getForms(auth = false): Promise<SignupForm[]> {
|
||||
static getForm = async (id: number, auth = false): Promise<SignupForm> => {
|
||||
try {
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(FORM_URL, {
|
||||
headers,
|
||||
return await getBackendAPI<SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
const { results } = resp.data;
|
||||
return results;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getForm(id: number, auth = false): Promise<SignupForm> {
|
||||
static getForms = async (auth = false): Promise<SignupForm[]> => {
|
||||
try {
|
||||
const headers = auth ? { Authorization: getAuthHeader() } : null;
|
||||
const resp = await axios.get(`${FORM_URL}${id}/`, {
|
||||
headers,
|
||||
return await getBackendAPI<SignupForm[]>({
|
||||
path: APIPath.SIGNUP_FORMS, authenticated: auth,
|
||||
});
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async createForm(data: SignupForm): Promise<SignupForm> {
|
||||
static createForm = async (data: SignupForm): Promise<SignupForm> => {
|
||||
try {
|
||||
const resp = await axios.post(FORM_URL, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await postBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async updateForm(data: SignupForm): Promise<SignupForm> {
|
||||
static updateForm = async (data: SignupForm): Promise<SignupForm> => {
|
||||
try {
|
||||
const putUrl = `${FORM_URL}${data.id}/`;
|
||||
const resp = await axios.put(putUrl, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await putBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async deleteForm(id: number) {
|
||||
static deleteForm = async (id: number): Promise<void> => {
|
||||
try {
|
||||
const resp = await axios.delete(`${FORM_URL}${id}`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async signupFormSendEmail(data: any, id: number): Promise<any> {
|
||||
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
|
||||
try {
|
||||
const resp = await axios.post(`${FORM_URL}${id}/sendemail/`, data, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
await postBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id }, authenticated: true }, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async getSignups(id: number): Promise<Signup[]> {
|
||||
static getSignups = async (id: number): Promise<Signup[]> => {
|
||||
try {
|
||||
const resp = await axios.get(`${FORM_URL}${id}/signups/`, {
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
},
|
||||
});
|
||||
return resp.data;
|
||||
return await getBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SignupApi;
|
||||
|
||||
+4
-14
@@ -1,26 +1,16 @@
|
||||
/* eslint-disable no-console */
|
||||
import axios from "axios";
|
||||
import Tag from "@models/Tag";
|
||||
|
||||
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/tags/`;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Options {
|
||||
// limit?: number;
|
||||
// offset?: number;
|
||||
// auth?: boolean;
|
||||
}
|
||||
import { APIPath, getBackendAPI } from "./backend";
|
||||
|
||||
class TagApi {
|
||||
static async getTags(): Promise<Tag[]> {
|
||||
static getTags = async (): Promise<Tag[]> => {
|
||||
try {
|
||||
const resp = await axios.get(URL);
|
||||
return resp.data.results;
|
||||
return await getBackendAPI<Tag[]>({ path: APIPath.TAGS });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default TagApi;
|
||||
|
||||
@@ -8,7 +8,7 @@ interface ButtonProps {
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
const StyledButton = styled.button<{ $selected: boolean }>`
|
||||
const StyledButton = styled.button<{ $selected?: boolean }>`
|
||||
border-radius: none;
|
||||
padding: 0.8rem 2rem;
|
||||
margin: 0.5rem;
|
||||
|
||||
@@ -70,9 +70,9 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
|
||||
$duration={len * SINGLE_IMAGE_TIME}
|
||||
>
|
||||
{ images.map((image, idx) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={idx} className={idx > 0 ? "not-first" : undefined}>
|
||||
<AnimatedImage
|
||||
key={image}
|
||||
src={image}
|
||||
objectFit="cover"
|
||||
width={width}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import React, { useRef } from "react";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
|
||||
const type = "Draggable";
|
||||
|
||||
const Draggable = ({
|
||||
id, index, handleDrag, children,
|
||||
}) => {
|
||||
const ref = useRef(null); // Initialize the reference
|
||||
|
||||
// useDrop hook is responsible for handling whether any item gets hovered or dropped on the element
|
||||
const [, drop] = useDrop({
|
||||
// accept receives a definition of what must be the type of the dragged item to be droppable
|
||||
accept: type,
|
||||
// This method is called when we hover over an element while dragging
|
||||
drop(item: { index: number }) { // item is the dragged element
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
const dragIndex = item.index;
|
||||
// current element where the dragged element is hovered on
|
||||
const hoverIndex = index;
|
||||
// If the dragged element is hovered in the same place, then do nothing
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
// If it is dragged around other elements, then move the image and set the state with position changes
|
||||
handleDrag(dragIndex, hoverIndex);
|
||||
/*
|
||||
Update the index for dragged item directly to avoid flickering
|
||||
when the image was half dragged into the next
|
||||
*/
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
item.index = hoverIndex;
|
||||
},
|
||||
});
|
||||
|
||||
// useDrag will be responsible for making an element draggable. It also expose, isDragging method to add any styles while dragging
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
// what type of item this to determine if a drop target accepts it
|
||||
type,
|
||||
// data of the item to be available to the drop methods
|
||||
item: { id, index },
|
||||
// method to collect additional data for drop handling like whether is currently being dragged
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
}));
|
||||
|
||||
/*
|
||||
Initialize drag and drop into the element using its reference.
|
||||
Here we initialize both drag and drop on the same element (i.e., Image component)
|
||||
*/
|
||||
drag(drop(ref));
|
||||
|
||||
return (
|
||||
<div ref={ref}>{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Draggable;
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
|
||||
const Icons = (): JSX.Element => (
|
||||
<>
|
||||
<link rel="shortcut icon" href="/favicons/favicon.ico" />
|
||||
<link rel="icon" href="/favicons/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="/favicons/favicon-48x48.png" />
|
||||
|
||||
@@ -15,7 +15,7 @@ interface IconProps {
|
||||
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
||||
}
|
||||
|
||||
const nameToIcon = (name: IconType): JSX.Element | string => {
|
||||
const nameToIcon = (name: IconType): JSX.Element | null => {
|
||||
if (name === IconType.Facebook) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -5,20 +5,18 @@ import Checkbox from "./Checkbox";
|
||||
|
||||
// See https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/components/widgets/CheckboxesWidget.js
|
||||
|
||||
function selectValue(value, selected, all) {
|
||||
const selectValue = (value, selected, all) => {
|
||||
const at = all.indexOf(value);
|
||||
const updated = selected.slice(0, at).concat(value, selected.slice(at));
|
||||
// As inserting values at predefined index positions doesn't work with empty
|
||||
// arrays, we need to reorder the updated selection to match the initial order
|
||||
return updated.sort((a, b) => all.indexOf(a) > all.indexOf(b));
|
||||
}
|
||||
};
|
||||
|
||||
function deselectValue(value, selected) {
|
||||
return selected.filter((v) => v !== value);
|
||||
}
|
||||
const deselectValue = (value, selected) => selected.filter((v) => v !== value);
|
||||
|
||||
type CheckboxesProps = Omit<WidgetProps, "options"> & {
|
||||
options: any;
|
||||
options: Record<string, any>;
|
||||
};
|
||||
|
||||
const CheckboxContainer = styled.div`
|
||||
@@ -32,12 +30,13 @@ const Checkboxes: React.FC<CheckboxesProps> = ({
|
||||
return (
|
||||
<div className="checkboxes" id={id}>
|
||||
{enumOptions.map((option, index) => {
|
||||
const key = `${id}_${index}`;
|
||||
const checked = value.indexOf(option.value) !== -1;
|
||||
const itemDisabled = enumDisabled && enumDisabled.indexOf(option.value) !== -1;
|
||||
const disabledCls = disabled || itemDisabled || readonly ? "disabled" : "";
|
||||
const checkbox = (
|
||||
<Checkbox
|
||||
id={`${id}_${index}`}
|
||||
id={key}
|
||||
checked={checked}
|
||||
disabled={disabled || itemDisabled || readonly}
|
||||
autoFocus={autofocus && index === 0}
|
||||
@@ -54,11 +53,11 @@ const Checkboxes: React.FC<CheckboxesProps> = ({
|
||||
</Checkbox>
|
||||
);
|
||||
return inline ? (
|
||||
<label key={index} className={`checkbox-inline ${disabledCls}`}>
|
||||
<label key={key} className={`checkbox-inline ${disabledCls}`}>
|
||||
{checkbox}
|
||||
</label>
|
||||
) : (
|
||||
<CheckboxContainer key={index} className={disabledCls}>
|
||||
<CheckboxContainer key={key} className={disabledCls}>
|
||||
{checkbox}
|
||||
</CheckboxContainer>
|
||||
);
|
||||
|
||||
@@ -38,7 +38,8 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
|
||||
// this is a temporary fix for radio button rendering bug in React, facebook/react#7630.
|
||||
return (
|
||||
<div className="field-radio-group" id={id}>
|
||||
{enumOptions.map((option, i) => {
|
||||
{enumOptions.map((option, index) => {
|
||||
const key = `${id}_${index}`;
|
||||
const checked = option.value === value;
|
||||
const itemDisabled = enumDisabled && enumDisabled.indexOf(option.value) !== -1;
|
||||
const disabledCls = disabled || itemDisabled || readonly ? "disabled" : "";
|
||||
@@ -49,7 +50,7 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
|
||||
required={required}
|
||||
value={option.value}
|
||||
disabled={disabled || itemDisabled || readonly}
|
||||
autoFocus={autofocus && i === 0}
|
||||
autoFocus={autofocus && index === 0}
|
||||
onChange={() => onChange(option.value)}
|
||||
onBlur={onBlur && ((event) => onBlur(id, event.target.value))}
|
||||
onFocus={onFocus && ((event) => onFocus(id, event.target.value))}
|
||||
@@ -59,11 +60,11 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
|
||||
);
|
||||
|
||||
return inline ? (
|
||||
<label key={i} className={`radio-inline ${disabledCls}`}>
|
||||
<label key={key} className={`radio-inline ${disabledCls}`}>
|
||||
{radio}
|
||||
</label>
|
||||
) : (
|
||||
<RadioButtonContainer key={i} className={disabledCls}>
|
||||
<RadioButtonContainer key={key} className={disabledCls}>
|
||||
{radio}
|
||||
</RadioButtonContainer>
|
||||
);
|
||||
|
||||
@@ -54,10 +54,10 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
|
||||
const { onChange } = this.props;
|
||||
const val = event.target.value;
|
||||
if (val !== "") {
|
||||
const lst = val.split(";").map((p) => p.trimLeft());
|
||||
const lst: number[] = val.split(";").map((p) => Number(p.trimStart()));
|
||||
// Ignore everything else but the two first values
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options.enum = lst.splice(0, 2);
|
||||
questions[index].options.enum = lst.splice(0, 2) as unknown[] as string[];
|
||||
} else {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
questions[index].options.enum = [];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import Draggable from "@components/Draggable";
|
||||
import colors from "@theme/colors";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
import { Lang } from "../../../i18n";
|
||||
@@ -18,14 +18,24 @@ const WidgetRow = styled.div`
|
||||
|
||||
interface QuestionListProps {
|
||||
questions: SignupFormQuestion[];
|
||||
innerRef: React.Ref<HTMLDivElement>;
|
||||
placeholder: ReactNode;
|
||||
onChange: (value: SignupFormQuestion[]) => void;
|
||||
}
|
||||
|
||||
class QuestionList extends React.Component<QuestionListProps> {
|
||||
handleNameInputChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const { onChange } = this.props;
|
||||
const QuestionList: React.FC<QuestionListProps> = ({ questions, onChange }): JSX.Element => {
|
||||
const handleDrag = (srcIndex, dstIndex) => {
|
||||
const srcCopy = { ...questions[srcIndex] };
|
||||
questions.splice(srcIndex, 1);
|
||||
questions.splice(dstIndex, 0, srcCopy);
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
const handleElementRemove = (index: number) => (): void => {
|
||||
const newQuestions = [...questions];
|
||||
newQuestions.splice(index, 1);
|
||||
onChange(newQuestions);
|
||||
};
|
||||
|
||||
const handleNameInputChange = (index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
|
||||
const val = event.target.value;
|
||||
if (lang === "fi") {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@@ -38,54 +48,39 @@ class QuestionList extends React.Component<QuestionListProps> {
|
||||
onChange(questions);
|
||||
};
|
||||
|
||||
handleElementRemove = (questions: SignupFormQuestion[], index: number) => (): void => {
|
||||
const { onChange } = this.props;
|
||||
const newQuestions = [...questions];
|
||||
newQuestions.splice(index, 1);
|
||||
onChange(newQuestions);
|
||||
};
|
||||
|
||||
renderQuestions(): JSX.Element[] {
|
||||
const { questions, onChange } = this.props;
|
||||
return questions.map((q, index) => {
|
||||
const dataProps = {
|
||||
value: q.options, type: q.type, questions, index,
|
||||
};
|
||||
const optionsWidget = <OptionsWidget inputProps={dataProps} onChange={onChange} />;
|
||||
const typeSelectWidget = <TypeWidget inputProps={dataProps} onChange={onChange} />;
|
||||
return (
|
||||
<Draggable draggableId={q.id} key={q.id} index={index}>
|
||||
{(provided) => (
|
||||
<WidgetRow
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
return (
|
||||
<div data-e2e="admin-signup-question">
|
||||
{questions.map((q, index) => {
|
||||
const inputProps = {
|
||||
value: q.options,
|
||||
type: q.type,
|
||||
questions,
|
||||
index,
|
||||
};
|
||||
const optionsWidget = <OptionsWidget inputProps={inputProps} onChange={onChange} />;
|
||||
const typeSelectWidget = <TypeWidget inputProps={inputProps} onChange={onChange} />;
|
||||
return (
|
||||
<Draggable
|
||||
key={q.id}
|
||||
id={q.id}
|
||||
index={index}
|
||||
handleDrag={handleDrag}
|
||||
>
|
||||
<WidgetRow>
|
||||
<QuestionElement
|
||||
onClick={this.handleElementRemove(questions, index)}
|
||||
onClick={handleElementRemove(index)}
|
||||
>
|
||||
<input type="text" value={q.title_fi} onChange={this.handleNameInputChange(questions, index, "fi")} />
|
||||
<input type="text" value={q.title_en} onChange={this.handleNameInputChange(questions, index, "en")} />
|
||||
<input type="text" value={q.title_fi} onChange={handleNameInputChange(index, "fi")} />
|
||||
<input type="text" value={q.title_en} onChange={handleNameInputChange(index, "en")} />
|
||||
{typeSelectWidget}
|
||||
{optionsWidget}
|
||||
</QuestionElement>
|
||||
</WidgetRow>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const { placeholder, innerRef } = this.props;
|
||||
|
||||
return (
|
||||
<div ref={innerRef} data-e2e="admin-signup-question">
|
||||
{this.renderQuestions()}
|
||||
{placeholder}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionList;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import shortid from "shortid";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import colors from "@theme/colors";
|
||||
import AddIcon from "@components/AddIcon";
|
||||
import { SignupFormQuestion } from "@models/Signup";
|
||||
@@ -34,12 +33,9 @@ const AddQuestionButton = styled.button`
|
||||
interface SignupQuestionsWidgetProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onFocus: () => void;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, onFocus, onChange }) => {
|
||||
const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, onChange }) => {
|
||||
const onValueChange = (questions: SignupFormQuestion[]) => {
|
||||
const newValue = JSON.stringify(questions);
|
||||
onChange(newValue);
|
||||
@@ -62,35 +58,14 @@ const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, on
|
||||
onValueChange(newQuestions);
|
||||
};
|
||||
|
||||
const handleDragEnd = (questions: SignupFormQuestion[]) => (result) => {
|
||||
const srcIndex = result.source.index;
|
||||
const dstIndex = result.destination.index;
|
||||
const srcCopy = { ...questions[srcIndex] };
|
||||
questions.splice(srcIndex, 1);
|
||||
questions.splice(dstIndex, 0, srcCopy);
|
||||
|
||||
onValueChange(questions);
|
||||
};
|
||||
const questions: SignupFormQuestion[] = JSON.parse(value);
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<DragDropContext
|
||||
onDragEnd={handleDragEnd(questions)}
|
||||
onDragStart={onFocus}
|
||||
>
|
||||
<Droppable droppableId="questions">
|
||||
{(provided) => (
|
||||
<QuestionList
|
||||
{...provided.droppableProps}
|
||||
innerRef={provided.innerRef}
|
||||
questions={questions}
|
||||
onChange={onValueChange}
|
||||
placeholder={provided.placeholder}
|
||||
/>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<QuestionList
|
||||
questions={questions}
|
||||
onChange={onValueChange}
|
||||
/>
|
||||
<AddQuestionButton type="button" onClick={handleNewRowClick(questions)} data-e2e="admin-signup-new-question">
|
||||
<AddIcon />
|
||||
New Question
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
import isDeepEqual from "fast-deep-equal/react";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import Event from "@models/Event";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import { URL, Options } from "@api/eventApi";
|
||||
|
||||
const fetcher = (url: string, config: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
|
||||
|
||||
const generateFetchParams = (id = "", options: Options = {}) => {
|
||||
const url = `${URL}${id}`;
|
||||
const {
|
||||
auth, since, limit, offset,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
url,
|
||||
config: {
|
||||
params: {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
headers: auth ? { Authorization: getAuthHeader() } : null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface FetchArguments {
|
||||
fallbackData?: Event | Event[],
|
||||
id?: string;
|
||||
options?: Options
|
||||
}
|
||||
|
||||
const useFetchEvents = ({
|
||||
fallbackData,
|
||||
id = "",
|
||||
options = {},
|
||||
}: FetchArguments) => {
|
||||
const { url, config } = generateFetchParams(id, options);
|
||||
|
||||
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
|
||||
const configRef = useRef(config);
|
||||
if (!isDeepEqual(configRef.current, config)) {
|
||||
configRef.current = config;
|
||||
}
|
||||
|
||||
const { data, error } = useSWR([url, configRef.current], fetcher, { fallbackData });
|
||||
return {
|
||||
data: data?.results || data,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFetchEvents;
|
||||
@@ -1,53 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
import isDeepEqual from "fast-deep-equal/react";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import Post from "@models/Feed";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import { URL, Options } from "@api/feedApi";
|
||||
|
||||
const feedFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
|
||||
|
||||
const generateFetchParams = (id = "", options: Options = {}) => {
|
||||
const url = `${URL}${id}`;
|
||||
const { auth, limit, offset } = options;
|
||||
|
||||
return {
|
||||
url,
|
||||
config: {
|
||||
params: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
headers: auth ? { Authorization: getAuthHeader() } : null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface FetchArguments {
|
||||
fallbackData?: Post | Post[],
|
||||
id?: string;
|
||||
options?: Options
|
||||
}
|
||||
|
||||
const useFetchFeed = ({
|
||||
fallbackData,
|
||||
id = "",
|
||||
options = {},
|
||||
}: FetchArguments) => {
|
||||
const { url, config } = generateFetchParams(id, options);
|
||||
|
||||
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
|
||||
const configRef = useRef(config);
|
||||
if (!isDeepEqual(configRef.current, config)) {
|
||||
configRef.current = config;
|
||||
}
|
||||
|
||||
const { data, error } = useSWR([url, configRef.current], feedFetcher, { fallbackData });
|
||||
return {
|
||||
data: data?.results || data,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFetchFeed;
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
import isDeepEqual from "fast-deep-equal/react";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import JobAd from "@models/JobAd";
|
||||
import { getAuthHeader } from "@utils/auth";
|
||||
import { URL, Options } from "@api/jobAdApi";
|
||||
|
||||
const jobAdFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
|
||||
|
||||
const generateFetchParams = (id = "", options: Options = {}) => {
|
||||
const url = `${URL}${id}`;
|
||||
const {
|
||||
since, limit, offset, auth,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
url,
|
||||
config: {
|
||||
params: {
|
||||
since,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
headers: auth ? { Authorization: getAuthHeader() } : null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface FetchArguments {
|
||||
fallbackData?: JobAd | JobAd[],
|
||||
id?: string;
|
||||
options?: Options;
|
||||
}
|
||||
|
||||
const useFetchJobAds = ({
|
||||
fallbackData,
|
||||
id = "",
|
||||
options = {},
|
||||
}: FetchArguments) => {
|
||||
const { url, config } = generateFetchParams(id, options);
|
||||
|
||||
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
|
||||
const configRef = useRef(config);
|
||||
if (!isDeepEqual(configRef.current, config)) {
|
||||
configRef.current = config;
|
||||
}
|
||||
|
||||
const { data, error } = useSWR([url, configRef.current], jobAdFetcher, { fallbackData });
|
||||
return {
|
||||
data: data?.results || data,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFetchJobAds;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const useIsTouchDevice = () => {
|
||||
const [isTouchDevice, setTouchDevice] = useState(false);
|
||||
useEffect(() => {
|
||||
// simple way to check whether the device support touch (it doesn't check all fallback, it supports only modern browsers)
|
||||
if (window !== undefined && "ontouchstart" in window) {
|
||||
setTouchDevice(true);
|
||||
}
|
||||
}, []);
|
||||
return isTouchDevice;
|
||||
};
|
||||
|
||||
export default useIsTouchDevice;
|
||||
+34
-22
@@ -1,4 +1,7 @@
|
||||
import React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import Head from "next/head";
|
||||
import { AppProps } from "next/app";
|
||||
import styled, { createGlobalStyle } from "styled-components";
|
||||
@@ -10,6 +13,7 @@ import "react-mde/lib/styles/css/react-mde-all.css";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import "normalize.css";
|
||||
|
||||
import useIsTouchDevice from "@hooks/useIsTouchDevice";
|
||||
import LocaleStore from "../i18n";
|
||||
|
||||
const fontFamily = "'Montserrat', sans-serif";
|
||||
@@ -128,27 +132,35 @@ const AppContainer = styled.div`
|
||||
background-color: ${colors.white};
|
||||
`;
|
||||
|
||||
const Web20App = ({ Component, pageProps }: AppProps): JSX.Element => (
|
||||
<>
|
||||
<Head>
|
||||
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon."
|
||||
/>
|
||||
<meta name="keywords" content="SIK AYY" />
|
||||
</Head>
|
||||
<GlobalCommonStyles />
|
||||
<LocaleStore>
|
||||
<AppContainer>
|
||||
<Component {...pageProps} />
|
||||
</AppContainer>
|
||||
</LocaleStore>
|
||||
<ToastContainer position="bottom-right" />
|
||||
</>
|
||||
);
|
||||
const Web20App = ({ Component, pageProps }: AppProps): JSX.Element => {
|
||||
const isTouchDevice = useIsTouchDevice();
|
||||
// Assigning backend based on touch support on the device
|
||||
const backendForDND = isTouchDevice ? TouchBackend : HTML5Backend;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon."
|
||||
/>
|
||||
<meta name="keywords" content="SIK AYY" />
|
||||
</Head>
|
||||
<GlobalCommonStyles />
|
||||
<LocaleStore>
|
||||
<AppContainer>
|
||||
<DndProvider backend={backendForDND}>
|
||||
<Component {...pageProps} />
|
||||
</DndProvider>
|
||||
</AppContainer>
|
||||
</LocaleStore>
|
||||
<ToastContainer position="bottom-right" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Web20App;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ServerStyleSheet } from "styled-components";
|
||||
import Favicons from "@components/Favicons";
|
||||
|
||||
export default class MyDocument extends Document<{ styleTags: unknown }> {
|
||||
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
|
||||
static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
|
||||
const sheet = new ServerStyleSheet();
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
try {
|
||||
@@ -26,7 +26,7 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
|
||||
} finally {
|
||||
sheet.seal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
const { styleTags } = this.props;
|
||||
|
||||
@@ -19,7 +19,7 @@ const widgets = {
|
||||
markdownEditor: MarkdownEditorWidget,
|
||||
};
|
||||
|
||||
const buildSchema = (formData: Event, signupForms: SignupForm[], tags: Tag[]) => {
|
||||
const buildSchema = (formData: Event | undefined, signupForms: SignupForm[], tags: Tag[]) => {
|
||||
const date = new Date(); const
|
||||
tomorrowDate = new Date();
|
||||
const currentDatetime = date.toISOString();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import useSWR from "swr";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { toast } from "react-toastify";
|
||||
import styled from "styled-components";
|
||||
@@ -8,7 +9,7 @@ import { Button, Link } from "@components/index";
|
||||
import AddLink from "@components/AddLink";
|
||||
import Event from "@models/Event";
|
||||
import EventApi from "@api/eventApi";
|
||||
import useFetchEvents from "@hooks/useFetchEvents";
|
||||
import { fetcher, APIPath, API } from "@api/backend";
|
||||
|
||||
const URL = "/admin/events";
|
||||
|
||||
@@ -32,8 +33,19 @@ const confirmDelete = async (event: Event) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderData = (events: Event[]) => {
|
||||
if (!events || events.length === 0) {
|
||||
const Renderer: React.FC = () => {
|
||||
const api: API = { path: APIPath.EVENTS, authenticated: true };
|
||||
const { data: events, error } = useSWR<Event[]>(api, fetcher);
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return (
|
||||
<div>
|
||||
Failed loading events
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!events?.length) {
|
||||
return <div>No events.</div>;
|
||||
}
|
||||
|
||||
@@ -64,15 +76,12 @@ const renderData = (events: Event[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AdminEventPage: NextPage = () => {
|
||||
const { data } = useFetchEvents({ options: { auth: true } });
|
||||
return (
|
||||
<AdminListCommon>
|
||||
<h1>Events</h1>
|
||||
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
|
||||
{renderData(data)}
|
||||
</AdminListCommon>
|
||||
);
|
||||
};
|
||||
const AdminEventPage: NextPage = () => (
|
||||
<AdminListCommon>
|
||||
<h1>Events</h1>
|
||||
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
|
||||
<Renderer />
|
||||
</AdminListCommon>
|
||||
);
|
||||
|
||||
export default AdminEventPage;
|
||||
|
||||
@@ -150,7 +150,7 @@ const FeedCreatePage: NextPage = () => {
|
||||
|
||||
const feedId = id && Number(id);
|
||||
if (feedId !== undefined) {
|
||||
FeedApi.getPost(feedId, { auth: true })
|
||||
FeedApi.getPost(feedId, true)
|
||||
.then((res) => setFormData({
|
||||
...res,
|
||||
tags: (res.tags).map((inst) => inst.id) as any,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import useSWR from "swr";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { toast } from "react-toastify";
|
||||
import styled from "styled-components";
|
||||
@@ -8,7 +9,7 @@ import { Button, Link } from "@components/index";
|
||||
import AddLink from "@components/AddLink";
|
||||
import Post from "@models/Feed";
|
||||
import PostApi from "@api/feedApi";
|
||||
import useFetchFeed from "@hooks/useFetchFeed";
|
||||
import { fetcher, APIPath, API } from "@api/backend";
|
||||
|
||||
const URL = "/admin/feed";
|
||||
|
||||
@@ -32,9 +33,22 @@ const confirmDelete = async (post: Post) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderData = (feed: Post[]) => {
|
||||
if (!feed || feed.length === 0) {
|
||||
return <div>No posts.</div>;
|
||||
const Renderer: React.FC = () => {
|
||||
const api: API = { path: APIPath.FEED, authenticated: true };
|
||||
const { data: feed, error } = useSWR<Post[]>(api, fetcher);
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return (
|
||||
<div>
|
||||
Failed loading feed
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!feed?.length) {
|
||||
return (
|
||||
<div>No posts.</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -64,15 +78,12 @@ const renderData = (feed: Post[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AdminFeedPage: NextPage = () => {
|
||||
const { data } = useFetchFeed({ options: { auth: true } });
|
||||
return (
|
||||
<AdminListCommon>
|
||||
<h1>Feed</h1>
|
||||
<AddLink text="Create news post" to={`${URL}/create`} />
|
||||
{renderData(data)}
|
||||
</AdminListCommon>
|
||||
);
|
||||
};
|
||||
const AdminFeedPage: NextPage = () => (
|
||||
<AdminListCommon>
|
||||
<h1>Feed</h1>
|
||||
<AddLink text="Create news post" to={`${URL}/create`} />
|
||||
<Renderer />
|
||||
</AdminListCommon>
|
||||
);
|
||||
|
||||
export default AdminFeedPage;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import useSWR from "swr";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { toast } from "react-toastify";
|
||||
import styled from "styled-components";
|
||||
@@ -7,8 +8,8 @@ import AdminListCommon from "@views/admin/AdminListCommon";
|
||||
import { Button, Link } from "@components/index";
|
||||
import AddLink from "@components/AddLink";
|
||||
import JobAd from "@models/JobAd";
|
||||
import useFetchJobAds from "@hooks/useFetchJobAds";
|
||||
import JobAdApi from "@api/jobAdApi";
|
||||
import { fetcher, APIPath, API } from "@api/backend";
|
||||
|
||||
const URL = "/admin/jobads";
|
||||
|
||||
@@ -32,8 +33,18 @@ const confirmDelete = async (jobad: JobAd) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderData = (jobAds: JobAd[]) => {
|
||||
if (!jobAds || jobAds.length === 0) {
|
||||
const Renderer: React.FC = () => {
|
||||
const api: API = { path: APIPath.JOBADS, authenticated: true };
|
||||
const { data: jobAds, error } = useSWR<JobAd[]>(api, fetcher);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return (
|
||||
<div>
|
||||
Failed loading jobads
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!jobAds?.length) {
|
||||
return <div>No advertisements.</div>;
|
||||
}
|
||||
|
||||
@@ -68,15 +79,12 @@ const renderData = (jobAds: JobAd[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AdminJobAdPage: NextPage = () => {
|
||||
const { data } = useFetchJobAds({ options: { auth: true } });
|
||||
return (
|
||||
<AdminListCommon>
|
||||
<h1>Job advertisements</h1>
|
||||
<AddLink text="Create job ad" to={`${URL}/create`} />
|
||||
{renderData(data)}
|
||||
</AdminListCommon>
|
||||
);
|
||||
};
|
||||
const AdminJobAdPage: NextPage = () => (
|
||||
<AdminListCommon>
|
||||
<h1>Job advertisements</h1>
|
||||
<AddLink text="Create job ad" to={`${URL}/create`} />
|
||||
<Renderer />
|
||||
</AdminListCommon>
|
||||
);
|
||||
|
||||
export default AdminJobAdPage;
|
||||
|
||||
@@ -5,12 +5,7 @@ import React, {
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
generateToken,
|
||||
setAccessTokenCookie,
|
||||
setRefreshTokenCookie,
|
||||
isAuthenticated,
|
||||
} from "@utils/auth";
|
||||
import { authenticate, login } from "@api/auth";
|
||||
import AdminPageWrapper from "@views/common/AdminPageWrapper";
|
||||
|
||||
const Main = styled.div`
|
||||
@@ -28,8 +23,8 @@ const AdminLoginPage: NextPage = () => {
|
||||
const next = router.query.next as string || DEFAULT_REDIRECT;
|
||||
|
||||
useEffect(() => {
|
||||
isAuthenticated().then((res) => {
|
||||
if (res) {
|
||||
authenticate().then((authResult) => {
|
||||
if (authResult) {
|
||||
router.push(next);
|
||||
}
|
||||
});
|
||||
@@ -38,11 +33,7 @@ const AdminLoginPage: NextPage = () => {
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const { access, refresh } = await generateToken(username, password);
|
||||
|
||||
setAccessTokenCookie(access);
|
||||
setRefreshTokenCookie(refresh);
|
||||
|
||||
await login(username, password);
|
||||
router.push(next);
|
||||
} catch (err) {
|
||||
setError("Failed to log in!");
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toast } from "react-toastify";
|
||||
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
|
||||
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
|
||||
import { SignupForm } from "@models/Signup";
|
||||
import SignupApi from "@api/signupApi";
|
||||
import SignupApi, { EmailRequest } from "@api/signupApi";
|
||||
|
||||
const widgets = {
|
||||
markdownEditor: MarkdownEditorWidget,
|
||||
@@ -67,7 +67,7 @@ const SignupEmailPage: NextPage = () => {
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
const payload = data.formData;
|
||||
const payload: EmailRequest = data.formData;
|
||||
await SignupApi.signupFormSendEmail(payload, Number(id));
|
||||
toast.success("Email sent successfully 😎");
|
||||
} catch (err) {
|
||||
|
||||
+17
-13
@@ -1,21 +1,25 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import Event from "@models/Event";
|
||||
import EventApi from "@api/eventApi";
|
||||
import useFetchEvents from "@hooks/useFetchEvents";
|
||||
import Post from "@models/Feed";
|
||||
import FeedApi from "@api/feedApi";
|
||||
import useFetchFeed from "@hooks/useFetchFeed";
|
||||
import InEnglishPageView from "@views/InEnglishPage/InEnglishPageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { fetcher, APIPath, API } from "@api/backend";
|
||||
|
||||
const eventOptions = {
|
||||
limit: 4,
|
||||
const eventApi: API = {
|
||||
path: APIPath.EVENTS,
|
||||
queryParams: {
|
||||
limit: 4,
|
||||
},
|
||||
};
|
||||
|
||||
const feedOptions = {
|
||||
limit: 4,
|
||||
const feedApi: API = {
|
||||
path: APIPath.FEED,
|
||||
queryParams: {
|
||||
limit: 4,
|
||||
},
|
||||
};
|
||||
|
||||
interface InitialProps {
|
||||
@@ -24,8 +28,8 @@ interface InitialProps {
|
||||
}
|
||||
|
||||
const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
|
||||
const eventResult = useFetchEvents({ fallbackData: initialEvents, options: eventOptions });
|
||||
const feedResult = useFetchFeed({ fallbackData: initialFeed, options: feedOptions });
|
||||
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
|
||||
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -33,15 +37,15 @@ const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) =
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/in_english`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<InEnglishPageView events={eventResult.data as Event[]} feed={feedResult.data} />
|
||||
<InEnglishPageView events={events} feed={feed} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
|
||||
const initialEvents = await EventApi.getEvents(eventOptions);
|
||||
const initialFeed = await FeedApi.getFeed(feedOptions);
|
||||
const initialEvents = await fetcher<Event[]>(eventApi);
|
||||
const initialFeed = await fetcher<Post[]>(feedApi);
|
||||
return {
|
||||
props: {
|
||||
initialEvents,
|
||||
|
||||
+19
-16
@@ -1,31 +1,34 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import Event from "@models/Event";
|
||||
import EventApi from "@api/eventApi";
|
||||
import useFetchEvents from "@hooks/useFetchEvents";
|
||||
import Post from "@models/Feed";
|
||||
import FeedApi from "@api/feedApi";
|
||||
import useFetchFeed from "@hooks/useFetchFeed";
|
||||
import FrontPageView from "@views/FrontPage/FrontPageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { fetcher, API, APIPath } from "@api/backend";
|
||||
|
||||
const eventOptions = {
|
||||
limit: 4,
|
||||
const eventApi: API = {
|
||||
path: APIPath.EVENTS,
|
||||
queryParams: {
|
||||
limit: 4,
|
||||
},
|
||||
};
|
||||
|
||||
const feedOptions = {
|
||||
limit: 4,
|
||||
const feedApi: API = {
|
||||
path: APIPath.FEED,
|
||||
queryParams: {
|
||||
limit: 4,
|
||||
},
|
||||
};
|
||||
|
||||
interface InitialProps {
|
||||
initialEvents: Event[];
|
||||
initialFeed: Post[];
|
||||
}
|
||||
|
||||
const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
|
||||
const { data: events } = useFetchEvents({ fallbackData: initialEvents, options: eventOptions });
|
||||
const { data: feed } = useFetchFeed({ fallbackData: initialFeed, options: feedOptions });
|
||||
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
|
||||
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -33,19 +36,19 @@ const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<FrontPageView events={events as Event[]} feed={feed as Post[]} />
|
||||
<FrontPageView events={events} feed={feed} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
|
||||
const initialEvents = await EventApi.getEvents(eventOptions);
|
||||
const initialFeed = await FeedApi.getFeed(feedOptions);
|
||||
const initialEvents = fetcher<Event[]>(eventApi);
|
||||
const initialFeed = fetcher<Post[]>(feedApi);
|
||||
return {
|
||||
props: {
|
||||
initialEvents,
|
||||
initialFeed,
|
||||
initialEvents: await initialEvents,
|
||||
initialFeed: await initialFeed,
|
||||
},
|
||||
revalidate: 10,
|
||||
};
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import Event from "@models/Event";
|
||||
import EventApi from "@api/eventApi";
|
||||
import useFetchEvents from "@hooks/useFetchEvents";
|
||||
import Post from "@models/Feed";
|
||||
import FeedApi from "@api/feedApi";
|
||||
import useFetchFeed from "@hooks/useFetchFeed";
|
||||
import ActualPageView from "@views/ActualPage/ActualPageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { fetcher, APIPath, API } from "@api/backend";
|
||||
|
||||
interface InitialProps {
|
||||
initialEvents: Event[];
|
||||
initialFeed: Post[];
|
||||
}
|
||||
|
||||
const eventApi: API = {
|
||||
path: APIPath.EVENTS,
|
||||
};
|
||||
|
||||
const feedApi: API = {
|
||||
path: APIPath.FEED,
|
||||
};
|
||||
|
||||
const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
|
||||
const eventResult = useFetchEvents({ fallbackData: initialEvents });
|
||||
const feedResult = useFetchFeed({ fallbackData: initialFeed });
|
||||
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
|
||||
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -25,7 +33,7 @@ const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/toiminta`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<ActualPageView events={eventResult.data} feed={feedResult.data} />
|
||||
<ActualPageView events={events} feed={feed} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
|
||||
const router = useRouter();
|
||||
const id = String(initialForm?.id ?? "");
|
||||
const URL = `${FORM_URL}${id}/`;
|
||||
const { data, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { fallbackData: initialForm });
|
||||
const { data: signupForm, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { fallbackData: initialForm });
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
@@ -35,7 +35,7 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
|
||||
return <LoadingView />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
if (!signupForm) {
|
||||
return (
|
||||
<NotFoundPage />
|
||||
);
|
||||
@@ -43,7 +43,7 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
|
||||
|
||||
const onSubmit = async ({ formData }: ISubmitEvent<string>) => {
|
||||
const payload: Signup = {
|
||||
signupForm_id: data.id,
|
||||
signupForm_id: signupForm.id,
|
||||
answer: formData,
|
||||
};
|
||||
|
||||
@@ -60,11 +60,11 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${data.id}`} />
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${signupForm.id}`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<SignUpPageView
|
||||
signUpForm={data}
|
||||
signUpForm={signupForm}
|
||||
formData={{}}
|
||||
onChange={noop}
|
||||
onSubmit={onSubmit}
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import JobAd from "@models/JobAd";
|
||||
import JobAdApi from "@api/jobAdApi";
|
||||
import useFetchJobAds from "@hooks/useFetchJobAds";
|
||||
import CorporatePageView from "@views/CorporatePage/CorporatePageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { API, APIPath, fetcher } from "@api/backend";
|
||||
|
||||
interface InitialProps {
|
||||
initialJobAds: JobAd[];
|
||||
}
|
||||
|
||||
const jobAdApi: API = {
|
||||
path: APIPath.JOBADS,
|
||||
};
|
||||
|
||||
const CorporatePage: NextPage<InitialProps> = ({ initialJobAds }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { data, error } = useFetchJobAds({ fallbackData: initialJobAds });
|
||||
const { data: jobAds } = useSWR<JobAd[]>(jobAdApi, fetcher, { fallbackData: initialJobAds });
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/yritysyhteistyo`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<CorporatePageView jobAds={data as JobAd[]} />
|
||||
<CorporatePageView jobAds={jobAds} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
|
||||
const initialJobAds = await JobAdApi.getJobAds();
|
||||
const initialJobAds = await fetcher<JobAd[]>(jobAdApi);
|
||||
return {
|
||||
props: {
|
||||
initialJobAds,
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
import axios from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const tokenUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/`;
|
||||
const checkUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/verify/`;
|
||||
const refreshUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/refresh/`;
|
||||
|
||||
export async function generateToken(username: string, password: string): Promise<{"access":string, "refresh":string}> {
|
||||
const resp = await axios.post(tokenUrl, {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
return {
|
||||
"access": resp.data.access,
|
||||
"refresh": resp.data.refresh,
|
||||
};
|
||||
}
|
||||
|
||||
export function setAccessTokenCookie(access_token: string): void {
|
||||
Cookies.set("jwt_access", access_token);
|
||||
Cookies.set("jwt_access", access_token, { domain: ".sahkoinsinoorikilta.fi" });
|
||||
@@ -40,41 +24,3 @@ export function deleteTokenCookies(): void {
|
||||
Cookies.remove("jwt_refresh", { domain: ".sahkoinsinoorikilta.fi" });
|
||||
Cookies.remove("jwt_refresh");
|
||||
}
|
||||
|
||||
export async function isAuthenticated(): Promise<boolean> {
|
||||
try {
|
||||
const token = getAccessTokenCookie();
|
||||
|
||||
await axios.post(checkUrl, {
|
||||
token,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
return refreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshToken(): Promise<boolean> {
|
||||
try {
|
||||
const refresh = getRefreshTokenCookie();
|
||||
if (refresh) {
|
||||
const resp = await axios.post(refreshUrl, {
|
||||
refresh,
|
||||
});
|
||||
|
||||
setAccessTokenCookie(resp.data.access);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
deleteTokenCookies();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAuthHeader(): string {
|
||||
const jwt_access = getAccessTokenCookie();
|
||||
return `Bearer ${jwt_access}`;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
|
||||
|
||||
<CTASection
|
||||
bgColor="orange1"
|
||||
link="https://sosso.fi/wp-content/uploads/2021/01/sossomediakortti21.pdf"
|
||||
link="https://sosso.fi/wp-content/uploads/2022/01/sossomediakortti22.pdf"
|
||||
linkText="Killan lehden mediakortin löydät täältä ›"
|
||||
>
|
||||
Mainos Sössöön?
|
||||
|
||||
@@ -38,7 +38,7 @@ type AdminCreateCommonProps = {
|
||||
onChange?: (e: IChangeEvent<FormTypes>, es?: ErrorSchema) => unknown;
|
||||
onFocus?: (id: string, value: string | number | boolean) => void;
|
||||
onSubmit: (e: ISubmitEvent<FormTypes>) => unknown;
|
||||
error: string;
|
||||
error?: string;
|
||||
widgets: {
|
||||
[name: string]: any;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import styled from "styled-components";
|
||||
import colors from "@theme/colors";
|
||||
import breakpoints from "@theme/breakpoints";
|
||||
import AdminHeader from "@components/AdminHeader";
|
||||
import AdminSidebar from "@components/AdminSidebar";
|
||||
import { isAuthenticated } from "@utils/auth";
|
||||
import { useRouter } from "next/router";
|
||||
import { authenticate } from "@api/auth";
|
||||
import LoadingView from "./LoadingView";
|
||||
|
||||
const Main = styled.main`
|
||||
@@ -43,8 +43,8 @@ const useShouldRedirect = (enabled = true) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
isAuthenticated().then((result) => {
|
||||
setRedirect(!result);
|
||||
authenticate().then((authResult) => {
|
||||
setRedirect(!authResult);
|
||||
setCompleted(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ fixture`Admin signup form`.page(`${getSiteRoot()}/admin/signups`)
|
||||
.requestHooks(LOGGER)
|
||||
.after(async (ctx) => {
|
||||
const token = await generateAccessToken();
|
||||
|
||||
await deleteForm(ctx.formId, token);
|
||||
});
|
||||
|
||||
@@ -35,7 +34,8 @@ test("Logged in user can create signup", async (t) => {
|
||||
.click(visible);
|
||||
|
||||
const newQuestionButton = Selector("[data-e2e=\"admin-signup-new-question\"]");
|
||||
const lastQuestion = () => Selector("[data-e2e=\"admin-signup-question\"]").child("div").child("div").nth(-1);
|
||||
const lastQuestion = () => Selector("[data-e2e=\"admin-signup-question\"]").child("div").child("div").child("div")
|
||||
.nth(-1);
|
||||
|
||||
await t.click(newQuestionButton);
|
||||
let question = lastQuestion();
|
||||
|
||||
@@ -7,7 +7,7 @@ const API_URL = "https://api.dev.sahkoinsinoorikilta.fi/api";
|
||||
export const getSiteRoot = (): string => process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";
|
||||
export const getPageUrl = ClientFunction(() => window.location.pathname);
|
||||
|
||||
export const getPostRequestLogger = (url: string) => RequestLogger({ url: `${API_URL}/${url}`, method: "post" }, {
|
||||
export const getPostRequestLogger = (url: string) => RequestLogger({ url: `${API_URL}/${url}`, method: "POST" }, {
|
||||
// logResponseHeaders: true,
|
||||
logResponseBody: true,
|
||||
stringifyResponseBody: true,
|
||||
|
||||
Reference in New Issue
Block a user