mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Merge pull request #46 from aaronpo97/create-brewery
Create brewery page
This commit is contained in:
208
package-lock.json
generated
208
package-lock.json
generated
@@ -13,6 +13,8 @@
|
||||
"@headlessui/tailwindcss": "^0.1.3",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@mapbox/mapbox-sdk": "^0.15.1",
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/search-js-react": "^1.0.0-beta.16",
|
||||
"@next/bundle-analyzer": "^13.4.4",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@react-email/components": "^0.0.6",
|
||||
@@ -81,7 +83,7 @@
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^4.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -638,6 +640,19 @@
|
||||
"npm": ">=6.14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/b64": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz",
|
||||
@@ -989,6 +1004,74 @@
|
||||
"polyline": "bin/polyline.bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/search-js-core": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-core/-/search-js-core-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-8Y+9HivlN/yQyEm2AqA3C9WRNiE9j8JVFoG1ZZnt5Rczm5Y+7BubdVH66buUvZxvrhzdyiyQgejGoNLxtrkhfw==",
|
||||
"dependencies": {
|
||||
"@types/geojson": "^7946.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/search-js-react": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-react/-/search-js-react-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-PsGU0D0VuoK9YUd70YGoWhaaEdZ851sBn/TLm3Rfu9HTF1ZHE7GemVS9YjmlNSf491VrIuYX3cDVElkq+J04gg==",
|
||||
"dependencies": {
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/search-js-web": "^1.0.0-beta.16",
|
||||
"@types/geojson": "^7946.0.8",
|
||||
"@types/react": "^17.0.43"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/search-js-react/node_modules/@types/react": {
|
||||
"version": "17.0.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.60.tgz",
|
||||
"integrity": "sha512-pCH7bqWIfzHs3D+PDs3O/COCQJka+Kcw3RnO9rFA2zalqoXg7cNjJDh6mZ7oRtY1wmY4LVwDdAbA1F7Z8tv3BQ==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/search-js-web": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-web/-/search-js-web-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-jCofyJZPnLUZjdEFX6cAS2V4q5RJHPe+M7JSdmnA3bfMEYZwrQdT920vy2Psm9Ep/h7sHwMzGGEtu58DpdmA7w==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.5.2",
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/sphericalmercator": "^1.2.0",
|
||||
"focus-trap": "^6.7.3",
|
||||
"no-scroll": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mapbox-gl": ">=2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/sphericalmercator": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz",
|
||||
"integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==",
|
||||
"bin": {
|
||||
"bbox": "bin/bbox.js",
|
||||
"to4326": "bin/to4326.js",
|
||||
"to900913": "bin/to900913.js",
|
||||
"xyz": "bin/xyz.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/tiny-sdf": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
|
||||
@@ -1973,8 +2056,7 @@
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.7",
|
||||
@@ -2051,8 +2133,7 @@
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.3.13",
|
||||
@@ -5343,6 +5424,14 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/focus-trap": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.4.tgz",
|
||||
"integrity": "sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==",
|
||||
"dependencies": {
|
||||
"tabbable": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@@ -8021,6 +8110,11 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/no-scroll": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/no-scroll/-/no-scroll-2.1.1.tgz",
|
||||
"integrity": "sha512-YTzGAJOo/B6hkodeT5SKKHpOhAzjMfkUCCXjLJwjWk2F4/InIg+HbdH9kmT7bKpleDuqLZDTRy2OdNtAj0IVyQ=="
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
@@ -10568,6 +10662,11 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
|
||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
@@ -11101,16 +11200,16 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
|
||||
"integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
@@ -11899,6 +11998,19 @@
|
||||
"integrity": "sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==",
|
||||
"dev": true
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
|
||||
},
|
||||
"@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"requires": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"@hapi/b64": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz",
|
||||
@@ -12178,6 +12290,54 @@
|
||||
"meow": "^6.1.1"
|
||||
}
|
||||
},
|
||||
"@mapbox/search-js-core": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-core/-/search-js-core-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-8Y+9HivlN/yQyEm2AqA3C9WRNiE9j8JVFoG1ZZnt5Rczm5Y+7BubdVH66buUvZxvrhzdyiyQgejGoNLxtrkhfw==",
|
||||
"requires": {
|
||||
"@types/geojson": "^7946.0.8"
|
||||
}
|
||||
},
|
||||
"@mapbox/search-js-react": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-react/-/search-js-react-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-PsGU0D0VuoK9YUd70YGoWhaaEdZ851sBn/TLm3Rfu9HTF1ZHE7GemVS9YjmlNSf491VrIuYX3cDVElkq+J04gg==",
|
||||
"requires": {
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/search-js-web": "^1.0.0-beta.16",
|
||||
"@types/geojson": "^7946.0.8",
|
||||
"@types/react": "^17.0.43"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": {
|
||||
"version": "17.0.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.60.tgz",
|
||||
"integrity": "sha512-pCH7bqWIfzHs3D+PDs3O/COCQJka+Kcw3RnO9rFA2zalqoXg7cNjJDh6mZ7oRtY1wmY4LVwDdAbA1F7Z8tv3BQ==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mapbox/search-js-web": {
|
||||
"version": "1.0.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/search-js-web/-/search-js-web-1.0.0-beta.16.tgz",
|
||||
"integrity": "sha512-jCofyJZPnLUZjdEFX6cAS2V4q5RJHPe+M7JSdmnA3bfMEYZwrQdT920vy2Psm9Ep/h7sHwMzGGEtu58DpdmA7w==",
|
||||
"requires": {
|
||||
"@floating-ui/dom": "^0.5.2",
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/sphericalmercator": "^1.2.0",
|
||||
"focus-trap": "^6.7.3",
|
||||
"no-scroll": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"@mapbox/sphericalmercator": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz",
|
||||
"integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ=="
|
||||
},
|
||||
"@mapbox/tiny-sdf": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
|
||||
@@ -12943,8 +13103,7 @@
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.7",
|
||||
@@ -13020,8 +13179,7 @@
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.13",
|
||||
@@ -15433,6 +15591,14 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"focus-trap": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.4.tgz",
|
||||
"integrity": "sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==",
|
||||
"requires": {
|
||||
"tabbable": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
@@ -17292,6 +17458,11 @@
|
||||
"regexparam": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"no-scroll": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/no-scroll/-/no-scroll-2.1.1.tgz",
|
||||
"integrity": "sha512-YTzGAJOo/B6hkodeT5SKKHpOhAzjMfkUCCXjLJwjWk2F4/InIg+HbdH9kmT7bKpleDuqLZDTRy2OdNtAj0IVyQ=="
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
@@ -19064,6 +19235,11 @@
|
||||
"tslib": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"tabbable": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
|
||||
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
@@ -19445,9 +19621,9 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
|
||||
"integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"devOptional": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
"@headlessui/tailwindcss": "^0.1.3",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@mapbox/mapbox-sdk": "^0.15.1",
|
||||
"@mapbox/search-js-core": "^1.0.0-beta.16",
|
||||
"@mapbox/search-js-react": "^1.0.0-beta.16",
|
||||
"@next/bundle-analyzer": "^13.4.4",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@react-email/components": "^0.0.6",
|
||||
@@ -76,16 +78,16 @@
|
||||
"eslint-config-next": "^13.4.4",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"onchange": "^7.1.0",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"onchange": "^7.1.0",
|
||||
"prisma": "^4.15.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^4.9.0"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "./src/prisma/schema.prisma",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import UserContext from '@/contexts/UserContext';
|
||||
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
|
||||
|
||||
import { Switch } from '@headlessui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Dispatch, FunctionComponent, useRef } from 'react';
|
||||
import { Dispatch, FunctionComponent, useContext, useRef } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
interface DeleteAccountProps {
|
||||
pageState: AccountPageState;
|
||||
@@ -13,6 +16,26 @@ const DeleteAccount: FunctionComponent<DeleteAccountProps> = ({
|
||||
}) => {
|
||||
const deleteRef = useRef<null | HTMLDialogElement>(null);
|
||||
const router = useRouter();
|
||||
const { user, mutate } = useContext(UserContext);
|
||||
|
||||
const onDeleteSubmit = async () => {
|
||||
deleteRef.current!.close();
|
||||
const loadingToast = toast.loading(
|
||||
'Deleting your account. We are sad to see you go. 😭',
|
||||
);
|
||||
const request = await fetch(`/api/users/${user?.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (!request.ok) {
|
||||
throw new Error('Could not delete that user.');
|
||||
}
|
||||
|
||||
toast.remove(loadingToast);
|
||||
toast.success('Deleted your account. Goodbye. 😓');
|
||||
await mutate!();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card w-full space-y-4">
|
||||
@@ -49,10 +72,7 @@ const DeleteAccount: FunctionComponent<DeleteAccountProps> = ({
|
||||
<div className="modal-action flex-col space-x-0 space-y-3">
|
||||
<button
|
||||
className="btn-error btn-sm btn w-full"
|
||||
onClick={async () => {
|
||||
deleteRef.current!.close();
|
||||
await router.replace('/api/users/logout');
|
||||
}}
|
||||
onClick={onDeleteSubmit}
|
||||
>
|
||||
Okay, delete my account
|
||||
</button>
|
||||
|
||||
@@ -72,14 +72,14 @@ const Security: FunctionComponent<SecurityProps> = ({ dispatch, pageState }) =>
|
||||
formValidationSchema={register('password')}
|
||||
/>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="password">Confirm Password</FormLabel>
|
||||
<FormLabel htmlFor="confirm-password">Confirm Password</FormLabel>
|
||||
<FormError>{formState.errors.confirmPassword?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormTextInput
|
||||
type="password"
|
||||
disabled={!pageState.securityOpen || formState.isSubmitting}
|
||||
error={!!formState.errors.confirmPassword}
|
||||
id="password"
|
||||
id="confirm-password"
|
||||
formValidationSchema={register('confirmPassword')}
|
||||
/>
|
||||
|
||||
|
||||
@@ -34,15 +34,15 @@ const CreateBeerPostForm: FunctionComponent<BeerFormProps> = ({
|
||||
types = [],
|
||||
brewery,
|
||||
}) => {
|
||||
const { register, handleSubmit, formState } = useForm<
|
||||
z.infer<typeof CreateBeerPostWithImagesValidationSchema>
|
||||
>({
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<z.infer<typeof CreateBeerPostWithImagesValidationSchema>>({
|
||||
resolver: zodResolver(CreateBeerPostWithImagesValidationSchema),
|
||||
defaultValues: { breweryId: brewery.id },
|
||||
});
|
||||
|
||||
const { errors, isSubmitting } = formState;
|
||||
|
||||
const onSubmit: SubmitHandler<
|
||||
z.infer<typeof CreateBeerPostWithImagesValidationSchema>
|
||||
> = async (data) => {
|
||||
@@ -51,9 +51,9 @@ const CreateBeerPostForm: FunctionComponent<BeerFormProps> = ({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await sendCreateBeerPostRequest(data);
|
||||
await sendUploadBeerImagesRequest({ beerPost: response, images: data.images });
|
||||
await router.push(`/beers/${response.id}`);
|
||||
const beerPost = await sendCreateBeerPostRequest(data);
|
||||
await sendUploadBeerImagesRequest({ beerPost, images: data.images });
|
||||
await router.push(`/beers/${beerPost.id}`);
|
||||
toast.success('Created beer post.');
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Something went wrong.';
|
||||
|
||||
@@ -22,14 +22,19 @@ const toastToClassName = (toastType: Toast['type']) => {
|
||||
const CustomToast: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right">
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 2500,
|
||||
}}
|
||||
>
|
||||
{(t) => {
|
||||
const alertType = toastToClassName(t.type);
|
||||
return (
|
||||
<div
|
||||
className={`alert ${alertType} flex w-11/12 items-center justify-between shadow-lg animate-in fade-in duration-200 lg:w-4/12`}
|
||||
className={`alert ${alertType} flex w-full items-start justify-between shadow-lg animate-in fade-in duration-200 lg:w-3/12`}
|
||||
>
|
||||
<p className="w-full">{resolveValue(t.message, t)}</p>
|
||||
<p className="w-full text-left">{resolveValue(t.message, t)}</p>
|
||||
{t.type !== 'loading' && (
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -20,7 +20,7 @@ const FormPageLayout: FC<FormPageLayoutProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className="my-20 flex flex-col items-center justify-center">
|
||||
<div className="w-10/12 lg:w-8/12 2xl:w-6/12">
|
||||
<div className="w-11/12 lg:w-9/12 2xl:w-7/12">
|
||||
<div className="tooltip tooltip-right" data-tip={backLinkText}>
|
||||
<Link href={backLink} className="btn-ghost btn-sm btn p-0">
|
||||
<BiArrowBack className="text-xl" />
|
||||
|
||||
@@ -11,6 +11,7 @@ interface FormInputProps {
|
||||
id: string;
|
||||
height?: string;
|
||||
disabled?: boolean;
|
||||
autoComplete?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,6 +34,7 @@ interface FormInputProps {
|
||||
* @param param0.id The id of the input.
|
||||
* @param param0.height The height of the input.
|
||||
* @param param0.disabled Whether or not the input is disabled.
|
||||
* @param param0.autoComplete The autocomplete value for the input.
|
||||
*/
|
||||
const FormTextInput: FunctionComponent<FormInputProps> = ({
|
||||
placeholder = '',
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import useUser from '@/hooks/auth/useUser';
|
||||
import GetUserSchema from '@/services/User/schema/GetUserSchema';
|
||||
import { createContext } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
const UserContext = createContext<{
|
||||
user?: z.infer<typeof GetUserSchema>;
|
||||
error?: unknown;
|
||||
isLoading: boolean;
|
||||
mutate?: ReturnType<typeof useUser>['mutate'];
|
||||
}>({ isLoading: true });
|
||||
|
||||
export default UserContext;
|
||||
24
src/contexts/UserContext.tsx
Normal file
24
src/contexts/UserContext.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import useUser from '@/hooks/auth/useUser';
|
||||
import GetUserSchema from '@/services/User/schema/GetUserSchema';
|
||||
import { ReactNode, createContext } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
const UserContext = createContext<{
|
||||
user?: z.infer<typeof GetUserSchema>;
|
||||
error?: unknown;
|
||||
isLoading: boolean;
|
||||
mutate?: ReturnType<typeof useUser>['mutate'];
|
||||
}>({ isLoading: true });
|
||||
|
||||
export default UserContext;
|
||||
|
||||
type AuthProviderComponent = (props: { children: ReactNode }) => JSX.Element;
|
||||
|
||||
export const AuthProvider: AuthProviderComponent = ({ children }) => {
|
||||
const { error, isLoading, mutate, user } = useUser();
|
||||
return (
|
||||
<UserContext.Provider value={{ isLoading, error, mutate, user }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -46,7 +46,12 @@ const useUser = () => {
|
||||
return parsedPayload.data;
|
||||
});
|
||||
|
||||
return { user, isLoading, error: error as unknown, mutate };
|
||||
return {
|
||||
mutate,
|
||||
isLoading,
|
||||
user: error ? undefined : user,
|
||||
error: error as unknown,
|
||||
};
|
||||
};
|
||||
|
||||
export default useUser;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import UserContext from '@/contexts/UserContext';
|
||||
import UserContext, { AuthProvider } from '@/contexts/UserContext';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
import type { AppProps } from 'next/app';
|
||||
@@ -13,15 +13,12 @@ import Layout from '@/components/ui/Layout';
|
||||
import useUser from '@/hooks/auth/useUser';
|
||||
import CustomToast from '@/components/ui/CustomToast';
|
||||
|
||||
const spaceGrotesk = Space_Grotesk({
|
||||
subsets: ['latin'],
|
||||
});
|
||||
const spaceGrotesk = Space_Grotesk({ subsets: ['latin'] });
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
}, []);
|
||||
const { user, isLoading, error, mutate } = useUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -38,15 +35,17 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
||||
/>
|
||||
</Head>
|
||||
<UserContext.Provider value={{ user, isLoading, error, mutate }}>
|
||||
<AuthProvider>
|
||||
<Layout>
|
||||
<CustomToast>
|
||||
<Component {...pageProps} />
|
||||
</CustomToast>
|
||||
</Layout>
|
||||
</UserContext.Provider>
|
||||
</AuthProvider>
|
||||
|
||||
<Analytics />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import processImageDataIntoDB from '@/services/BeerImage/processImageDataIntoDB';
|
||||
import addBeerImageToDB from '@/services/BeerImage/addBeerImageToDB';
|
||||
import ImageMetadataValidationSchema from '@/services/types/ImageSchema/ImageMetadataValidationSchema';
|
||||
|
||||
const { storage } = cloudinaryConfig;
|
||||
@@ -50,7 +50,7 @@ const processImageData = async (
|
||||
throw new ServerError('No images uploaded', 400);
|
||||
}
|
||||
|
||||
const beerImages = await processImageDataIntoDB({
|
||||
const beerImages = await addBeerImageToDB({
|
||||
alt: body.alt,
|
||||
caption: body.caption,
|
||||
beerPostId: req.query.id,
|
||||
|
||||
86
src/pages/api/breweries/[id]/images/index.ts
Normal file
86
src/pages/api/breweries/[id]/images/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import { createRouter, expressWrapper } from 'next-connect';
|
||||
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
|
||||
import multer from 'multer';
|
||||
|
||||
import cloudinaryConfig from '@/config/cloudinary';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import ImageMetadataValidationSchema from '@/services/types/ImageSchema/ImageMetadataValidationSchema';
|
||||
import addBreweryImageToDB from '@/services/BreweryImage/addBreweryImageToDB';
|
||||
|
||||
const { storage } = cloudinaryConfig;
|
||||
|
||||
const fileFilter: multer.Options['fileFilter'] = (req, file, cb) => {
|
||||
const { mimetype } = file;
|
||||
|
||||
const isImage = mimetype.startsWith('image/');
|
||||
|
||||
if (!isImage) {
|
||||
cb(null, false);
|
||||
}
|
||||
cb(null, true);
|
||||
};
|
||||
|
||||
const uploadMiddleware = expressWrapper(
|
||||
multer({ storage, fileFilter, limits: { files: 5, fileSize: 15 * 1024 * 1024 } }).array(
|
||||
'images',
|
||||
),
|
||||
);
|
||||
|
||||
interface UploadBreweryPostImagesRequest extends UserExtendedNextApiRequest {
|
||||
files?: Express.Multer.File[];
|
||||
query: { id: string };
|
||||
body: z.infer<typeof ImageMetadataValidationSchema>;
|
||||
}
|
||||
|
||||
const processImageData = async (
|
||||
req: UploadBreweryPostImagesRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { files, user, body } = req;
|
||||
|
||||
if (!files || !files.length) {
|
||||
throw new ServerError('No images uploaded', 400);
|
||||
}
|
||||
|
||||
const breweryImages = await addBreweryImageToDB({
|
||||
alt: body.alt,
|
||||
caption: body.caption,
|
||||
breweryPostId: req.query.id,
|
||||
userId: user!.id,
|
||||
files,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Successfully uploaded ${breweryImages.length} image${
|
||||
breweryImages.length > 1 ? 's' : ''
|
||||
}`,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
UploadBreweryPostImagesRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
getCurrentUser,
|
||||
// @ts-expect-error
|
||||
uploadMiddleware,
|
||||
validateRequest({ bodySchema: ImageMetadataValidationSchema }),
|
||||
processImageData,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
|
||||
export const config = { api: { bodyParser: false } };
|
||||
76
src/pages/api/breweries/create.ts
Normal file
76
src/pages/api/breweries/create.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import { createRouter } from 'next-connect';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { z } from 'zod';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import CreateBreweryPostSchema from '@/services/BreweryPost/types/CreateBreweryPostSchema';
|
||||
import createNewBreweryPost from '@/services/BreweryPost/createNewBreweryPost';
|
||||
import geocode from '@/config/mapbox/geocoder';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
|
||||
interface CreateBreweryPostRequest extends UserExtendedNextApiRequest {
|
||||
body: z.infer<typeof CreateBreweryPostSchema>;
|
||||
}
|
||||
|
||||
const createBreweryPost = async (
|
||||
req: CreateBreweryPostRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { name, description, dateEstablished, address, city, country, region } = req.body;
|
||||
const userId = req.user!.id;
|
||||
|
||||
const fullAddress = `${address}, ${city}, ${region}, ${country}`;
|
||||
|
||||
const geocoded = await geocode(fullAddress);
|
||||
|
||||
if (!geocoded) {
|
||||
throw new ServerError('Address is not valid', 400);
|
||||
}
|
||||
|
||||
const [latitude, longitude] = geocoded.center;
|
||||
|
||||
const location = await DBClient.instance.location.create({
|
||||
data: {
|
||||
address,
|
||||
city,
|
||||
country,
|
||||
stateOrProvince: region,
|
||||
coordinates: [latitude, longitude],
|
||||
postedBy: { connect: { id: userId } },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const newBreweryPost = await createNewBreweryPost({
|
||||
name,
|
||||
description,
|
||||
locationId: location.id,
|
||||
dateEstablished,
|
||||
userId,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Brewery post created successfully',
|
||||
statusCode: 201,
|
||||
payload: newBreweryPost,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
CreateBreweryPostRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.post(
|
||||
validateRequest({ bodySchema: CreateBreweryPostSchema }),
|
||||
getCurrentUser,
|
||||
createBreweryPost,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
export default handler;
|
||||
@@ -4,6 +4,7 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import deleteUserById from '@/services/User/deleteUserById';
|
||||
import findUserByEmail from '@/services/User/findUserByEmail';
|
||||
import findUserById from '@/services/User/findUserById';
|
||||
import findUserByUsername from '@/services/User/findUserByUsername';
|
||||
@@ -21,11 +22,12 @@ const EditUserSchema = BaseCreateUserSchema.pick({
|
||||
lastName: true,
|
||||
});
|
||||
|
||||
interface EditUserRequest extends UserExtendedNextApiRequest {
|
||||
interface UserRouteRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string };
|
||||
}
|
||||
|
||||
interface EditUserRequest extends UserRouteRequest {
|
||||
body: z.infer<typeof EditUserSchema>;
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const checkIfUserCanEditUser = async (
|
||||
@@ -41,7 +43,7 @@ const checkIfUserCanEditUser = async (
|
||||
}
|
||||
|
||||
if (authenticatedUser.id !== userToUpdate.id) {
|
||||
throw new ServerError('You are not permitted to edit this user', 403);
|
||||
throw new ServerError('You are not permitted to modify this user', 403);
|
||||
}
|
||||
|
||||
await next();
|
||||
@@ -88,6 +90,24 @@ const editUser = async (
|
||||
});
|
||||
};
|
||||
|
||||
const deleteUser = async (
|
||||
req: UserRouteRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const { id } = req.query;
|
||||
const deletedUser = await deleteUserById(id);
|
||||
|
||||
if (!deletedUser) {
|
||||
throw new ServerError('Could not find a user with that id.', 400);
|
||||
}
|
||||
|
||||
res.send({
|
||||
message: 'Successfully deleted user.',
|
||||
statusCode: 200,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
EditUserRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
@@ -103,6 +123,15 @@ router.put(
|
||||
editUser,
|
||||
);
|
||||
|
||||
router.delete(
|
||||
getCurrentUser,
|
||||
validateRequest({
|
||||
querySchema: z.object({ id: z.string().cuid() }),
|
||||
}),
|
||||
checkIfUserCanEditUser,
|
||||
deleteUser,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
@@ -5,7 +5,6 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
||||
import GetUserSchema from '@/services/User/schema/GetUserSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
@@ -23,26 +22,15 @@ const updatePassword = async (
|
||||
const hash = await hashPassword(password);
|
||||
|
||||
const user = req.user!;
|
||||
const updatedUser: z.infer<typeof GetUserSchema> = await DBClient.instance.user.update({
|
||||
await DBClient.instance.user.update({
|
||||
data: { hash },
|
||||
where: { id: user.id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
dateOfBirth: true,
|
||||
accountIsVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: 'Updated user password.',
|
||||
statusCode: 200,
|
||||
success: true,
|
||||
payload: updatedUser,
|
||||
});
|
||||
};
|
||||
const router = createRouter<
|
||||
|
||||
@@ -11,6 +11,7 @@ import findUserByEmail from '@/services/User/findUserByEmail';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
|
||||
import { NODE_ENV } from '@/config/env';
|
||||
import sendConfirmationEmail from '@/services/User/sendConfirmationEmail';
|
||||
|
||||
interface RegisterUserRequest extends NextApiRequest {
|
||||
@@ -44,7 +45,9 @@ const registerUser = async (req: RegisterUserRequest, res: NextApiResponse) => {
|
||||
username: user.username,
|
||||
});
|
||||
|
||||
await sendConfirmationEmail(user);
|
||||
if (NODE_ENV === 'production') {
|
||||
await sendConfirmationEmail(user);
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
|
||||
@@ -33,7 +33,6 @@ export const getServerSideProps = withPageAuthRequired<CreateBeerPageProps>(
|
||||
const id = context.params?.id as string;
|
||||
|
||||
const breweryPost = await getBreweryPostById(id);
|
||||
|
||||
const beerTypes = await DBClient.instance.beerType.findMany();
|
||||
|
||||
return {
|
||||
|
||||
279
src/pages/breweries/create.tsx
Normal file
279
src/pages/breweries/create.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import Button from '@/components/ui/forms/Button';
|
||||
import FormError from '@/components/ui/forms/FormError';
|
||||
import FormInfo from '@/components/ui/forms/FormInfo';
|
||||
import FormLabel from '@/components/ui/forms/FormLabel';
|
||||
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
|
||||
import FormSegment from '@/components/ui/forms/FormSegment';
|
||||
import FormTextArea from '@/components/ui/forms/FormTextArea';
|
||||
import FormTextInput from '@/components/ui/forms/FormTextInput';
|
||||
|
||||
import createErrorToast from '@/util/createErrorToast';
|
||||
import withPageAuthRequired from '@/util/withPageAuthRequired';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { AddressAutofillRetrieveResponse } from '@mapbox/search-js-core';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import Head from 'next/head';
|
||||
import { FieldError, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { FaBeer } from 'react-icons/fa';
|
||||
import { z } from 'zod';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import CreateBreweryPostSchema from '@/services/BreweryPost/types/CreateBreweryPostSchema';
|
||||
import UploadImageValidationSchema from '@/services/types/ImageSchema/UploadImageValidationSchema';
|
||||
import sendUploadBreweryImagesRequest from '@/requests/BreweryImage/sendUploadBreweryImageRequest';
|
||||
|
||||
const AddressAutofill = dynamic(
|
||||
() => import('@mapbox/search-js-react').then((mod) => mod.AddressAutofill),
|
||||
{ ssr: false },
|
||||
);
|
||||
const CreateBreweryPostWithImagesSchema = CreateBreweryPostSchema.merge(
|
||||
UploadImageValidationSchema,
|
||||
);
|
||||
|
||||
const sendCreateBreweryPostRequest = async (
|
||||
data: z.infer<typeof CreateBreweryPostSchema>,
|
||||
) => {
|
||||
const response = await fetch('/api/breweries/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
const parsed = APIResponseValidationSchema.safeParse(json);
|
||||
if (!parsed.success) {
|
||||
throw new Error('API response parsing failed');
|
||||
}
|
||||
|
||||
const parsedPayload = BreweryPostQueryResult.safeParse(parsed.data.payload);
|
||||
if (!parsedPayload.success) {
|
||||
throw new Error('API response payload parsing failed');
|
||||
}
|
||||
|
||||
return parsedPayload.data;
|
||||
};
|
||||
|
||||
const CreateBreweryPage: NextPage = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<z.infer<typeof CreateBreweryPostWithImagesSchema>>({
|
||||
resolver: zodResolver(CreateBreweryPostWithImagesSchema),
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onSubmit: SubmitHandler<
|
||||
z.infer<typeof CreateBreweryPostWithImagesSchema>
|
||||
> = async (data) => {
|
||||
const loadingToast = toast.loading('Creating brewery...');
|
||||
try {
|
||||
if (!(data.images instanceof FileList)) {
|
||||
return;
|
||||
}
|
||||
const breweryPost = await sendCreateBreweryPostRequest(data);
|
||||
await sendUploadBreweryImagesRequest({ breweryPost, images: data.images });
|
||||
await router.push(`/breweries/${breweryPost.id}`);
|
||||
toast.remove(loadingToast);
|
||||
toast.success('Created brewery.');
|
||||
} catch (error) {
|
||||
toast.remove(loadingToast);
|
||||
reset();
|
||||
createErrorToast(error);
|
||||
}
|
||||
};
|
||||
|
||||
const onAutoCompleteChange = (address: string) => {
|
||||
setValue('address', address);
|
||||
};
|
||||
|
||||
const onAutoCompleteRetrieve = (address: AddressAutofillRetrieveResponse) => {
|
||||
const { country, region, place } = address.features[0].properties as unknown as {
|
||||
country?: string;
|
||||
region?: string;
|
||||
place?: string;
|
||||
};
|
||||
|
||||
setValue('country', country);
|
||||
setValue('region', region);
|
||||
setValue('city', place!);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Brewery</title>
|
||||
</Head>
|
||||
<div className="flex w-full flex-col items-center justify-center">
|
||||
<div className="w-full">
|
||||
<FormPageLayout
|
||||
backLink="/breweries"
|
||||
backLinkText="Back to Breweries"
|
||||
headingText="Create Brewery"
|
||||
headingIcon={FaBeer}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="form-control"
|
||||
autoComplete="off"
|
||||
>
|
||||
<div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="name">Name</FormLabel>
|
||||
<FormError>{errors.name?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
placeholder="Lorem Ipsum Brewing Company"
|
||||
formValidationSchema={register('name')}
|
||||
error={!!errors.name}
|
||||
type="text"
|
||||
id="name"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="description">Description</FormLabel>
|
||||
<FormError>{errors.description?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextArea
|
||||
placeholder="We make beer, and we make it good."
|
||||
formValidationSchema={register('description')}
|
||||
error={!!errors.description}
|
||||
rows={4}
|
||||
id="description"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="dateEstablished">Date Established</FormLabel>
|
||||
<FormError>{errors.dateEstablished?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
placeholder="2021-01-01"
|
||||
formValidationSchema={register('dateEstablished')}
|
||||
error={!!errors.dateEstablished}
|
||||
type="date"
|
||||
id="dateEstablished"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="images">Images</FormLabel>
|
||||
<FormError>
|
||||
{(errors.images as FieldError | undefined)?.message}
|
||||
</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<input
|
||||
type="file"
|
||||
{...register('images')}
|
||||
multiple
|
||||
className="file-input-bordered file-input w-full"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="address">Address</FormLabel>
|
||||
<FormError>{errors.address?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<AddressAutofill
|
||||
accessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN!}
|
||||
onRetrieve={onAutoCompleteRetrieve}
|
||||
onChange={onAutoCompleteChange}
|
||||
>
|
||||
<input
|
||||
id="address"
|
||||
type="text"
|
||||
placeholder="1234 Main St"
|
||||
className={`input-bordered input w-full appearance-none rounded-lg transition ease-in-out ${
|
||||
errors.address?.message ? 'input-error' : ''
|
||||
}`}
|
||||
{...register('address')}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</AddressAutofill>
|
||||
</FormSegment>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<div className="w-1/2">
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="city">City</FormLabel>
|
||||
<FormError>{errors.city?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
placeholder="Toronto"
|
||||
formValidationSchema={register('city')}
|
||||
error={!!errors.city}
|
||||
type="text"
|
||||
id="city"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="region">Region</FormLabel>
|
||||
<FormError>{errors.region?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
placeholder="Ontario"
|
||||
formValidationSchema={register('region')}
|
||||
error={!!errors.region}
|
||||
type="text"
|
||||
id="region"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
</div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="country">Country</FormLabel>
|
||||
<FormError>{errors.country?.message}</FormError>
|
||||
</FormInfo>
|
||||
<FormSegment>
|
||||
<FormTextInput
|
||||
placeholder="Canada"
|
||||
formValidationSchema={register('country')}
|
||||
error={!!errors.country}
|
||||
type="text"
|
||||
id="country"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" isSubmitting={isSubmitting}>
|
||||
Create Brewery Post
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormPageLayout>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateBreweryPage;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = withPageAuthRequired();
|
||||
34
src/requests/BreweryImage/sendUploadBreweryImageRequest.ts
Normal file
34
src/requests/BreweryImage/sendUploadBreweryImageRequest.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface SendUploadBeerImagesRequestArgs {
|
||||
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
||||
images: FileList;
|
||||
}
|
||||
|
||||
const sendUploadBreweryImagesRequest = async ({
|
||||
breweryPost,
|
||||
images,
|
||||
}: SendUploadBeerImagesRequestArgs) => {
|
||||
const formData = new FormData();
|
||||
|
||||
[...images].forEach((file) => {
|
||||
formData.append('images', file);
|
||||
});
|
||||
|
||||
formData.append('caption', `Image of ${breweryPost.name}`);
|
||||
formData.append('alt', breweryPost.name);
|
||||
|
||||
const uploadResponse = await fetch(`/api/breweries/${breweryPost.id}/images`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
throw new Error('Failed to upload images');
|
||||
}
|
||||
|
||||
return uploadResponse.json();
|
||||
};
|
||||
|
||||
export default sendUploadBreweryImagesRequest;
|
||||
@@ -13,7 +13,7 @@ interface SendEditUserRequestArgs {
|
||||
}
|
||||
|
||||
const sendEditUserRequest = async ({ user, data }: SendEditUserRequestArgs) => {
|
||||
const response = await fetch(`/api/users/${user!.id}/edit`, {
|
||||
const response = await fetch(`/api/users/${user!.id}`, {
|
||||
body: JSON.stringify(data),
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
||||
import GetUserSchema from '@/services/User/schema/GetUserSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -21,13 +20,7 @@ const sendUpdatePasswordRequest = async (data: z.infer<typeof UpdatePasswordSche
|
||||
throw new Error('API response validation failed.');
|
||||
}
|
||||
|
||||
const parsedPayload = GetUserSchema.safeParse(parsed.data.payload);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
throw new Error('API payload validation failed.');
|
||||
}
|
||||
|
||||
return parsedPayload.data;
|
||||
return parsed.data;
|
||||
};
|
||||
|
||||
export default sendUpdatePasswordRequest;
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ProcessImageDataArgs {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const processImageDataIntoDB = ({
|
||||
const addBeerImageToDB = ({
|
||||
alt,
|
||||
caption,
|
||||
files,
|
||||
@@ -36,4 +36,4 @@ const processImageDataIntoDB = ({
|
||||
return Promise.all(beerImagePromises);
|
||||
};
|
||||
|
||||
export default processImageDataIntoDB;
|
||||
export default addBeerImageToDB;
|
||||
39
src/services/BreweryImage/addBreweryImageToDB.ts
Normal file
39
src/services/BreweryImage/addBreweryImageToDB.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { BreweryImage } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
import ImageMetadataValidationSchema from '../types/ImageSchema/ImageMetadataValidationSchema';
|
||||
|
||||
interface ProcessImageDataArgs {
|
||||
files: Express.Multer.File[];
|
||||
alt: z.infer<typeof ImageMetadataValidationSchema>['alt'];
|
||||
caption: z.infer<typeof ImageMetadataValidationSchema>['caption'];
|
||||
breweryPostId: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const addBreweryImageToDB = ({
|
||||
alt,
|
||||
caption,
|
||||
files,
|
||||
breweryPostId,
|
||||
userId,
|
||||
}: ProcessImageDataArgs) => {
|
||||
const breweryImagePromises: Promise<BreweryImage>[] = [];
|
||||
files.forEach((file) => {
|
||||
breweryImagePromises.push(
|
||||
DBClient.instance.breweryImage.create({
|
||||
data: {
|
||||
alt,
|
||||
caption,
|
||||
postedBy: { connect: { id: userId } },
|
||||
breweryPost: { connect: { id: breweryPostId } },
|
||||
path: file.path,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return Promise.all(breweryImagePromises);
|
||||
};
|
||||
|
||||
export default addBreweryImageToDB;
|
||||
55
src/services/BreweryPost/createNewBreweryPost.ts
Normal file
55
src/services/BreweryPost/createNewBreweryPost.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import CreateBreweryPostSchema from './types/CreateBreweryPostSchema';
|
||||
import BreweryPostQueryResult from './types/BreweryPostQueryResult';
|
||||
|
||||
const CreateNewBreweryPostWithUserAndLocationSchema = CreateBreweryPostSchema.omit({
|
||||
address: true,
|
||||
city: true,
|
||||
country: true,
|
||||
stateOrProvince: true,
|
||||
}).extend({
|
||||
userId: z.string().cuid(),
|
||||
locationId: z.string().cuid(),
|
||||
});
|
||||
|
||||
const createNewBreweryPost = async ({
|
||||
dateEstablished,
|
||||
description,
|
||||
locationId,
|
||||
name,
|
||||
userId,
|
||||
}: z.infer<typeof CreateNewBreweryPostWithUserAndLocationSchema>) => {
|
||||
const breweryPost: z.infer<typeof BreweryPostQueryResult> =
|
||||
await DBClient.instance.breweryPost.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
dateEstablished,
|
||||
location: { connect: { id: locationId } },
|
||||
postedBy: { connect: { id: userId } },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
createdAt: true,
|
||||
dateEstablished: true,
|
||||
postedBy: { select: { id: true, username: true } },
|
||||
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||
location: {
|
||||
select: {
|
||||
city: true,
|
||||
address: true,
|
||||
coordinates: true,
|
||||
country: true,
|
||||
stateOrProvince: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return breweryPost;
|
||||
};
|
||||
|
||||
export default createNewBreweryPost;
|
||||
55
src/services/BreweryPost/types/CreateBreweryPostSchema.ts
Normal file
55
src/services/BreweryPost/types/CreateBreweryPostSchema.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { isPast } from 'date-fns';
|
||||
import { z } from 'zod';
|
||||
|
||||
const CreateBreweryPostSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: 'Brewery name is required.',
|
||||
invalid_type_error: 'Brewery name must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Brewery name is required.' })
|
||||
.max(100, { message: 'Brewery name is too long.' }),
|
||||
description: z
|
||||
.string({
|
||||
required_error: 'Description is required.',
|
||||
invalid_type_error: 'Description must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Description is required.' })
|
||||
.max(500, { message: 'Description is too long.' }),
|
||||
address: z
|
||||
.string({
|
||||
required_error: 'Address is required.',
|
||||
invalid_type_error: 'Address must be a string.',
|
||||
})
|
||||
.min(1, { message: 'Address is required.' })
|
||||
.max(100, { message: 'Address is too long.' }),
|
||||
|
||||
city: z
|
||||
.string({
|
||||
required_error: 'City is required.',
|
||||
invalid_type_error: 'City must be a string.',
|
||||
})
|
||||
.min(1, { message: 'City is required.' })
|
||||
.max(100, { message: 'City is too long.' }),
|
||||
|
||||
region: z
|
||||
.string({ invalid_type_error: 'region must be a string.' })
|
||||
.min(1, { message: 'region is required.' })
|
||||
.max(100, { message: 'region is too long.' })
|
||||
.optional(),
|
||||
|
||||
country: z
|
||||
.string({ invalid_type_error: 'Country must be a string.' })
|
||||
.max(100, { message: 'Country is too long.' })
|
||||
.optional(),
|
||||
|
||||
dateEstablished: z.coerce
|
||||
.date({
|
||||
required_error: 'Date established is required.',
|
||||
invalid_type_error: 'Date established must be a date string.',
|
||||
})
|
||||
.refine((val) => !Number.isNaN(val.toString()), { message: 'Date is invalid.' })
|
||||
.refine((val) => isPast(new Date(val)), { message: 'Date must be in the past.' }),
|
||||
});
|
||||
|
||||
export default CreateBreweryPostSchema;
|
||||
25
src/services/User/deleteUserById.ts
Normal file
25
src/services/User/deleteUserById.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import GetUserSchema from './schema/GetUserSchema';
|
||||
|
||||
const deleteUserById = async (id: string) => {
|
||||
const deletedUser: z.infer<typeof GetUserSchema> | null =
|
||||
await DBClient.instance.user.delete({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
dateOfBirth: true,
|
||||
createdAt: true,
|
||||
accountIsVerified: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return deletedUser;
|
||||
};
|
||||
|
||||
export default deleteUserById;
|
||||
@@ -37,6 +37,9 @@ export const BaseCreateUserSchema = z.object({
|
||||
}),
|
||||
dateOfBirth: z
|
||||
.string()
|
||||
.refine((val) => !Number.isNaN(Date.parse(val)), {
|
||||
message: 'Date is invalid.',
|
||||
})
|
||||
.refine((dateOfBirth) => new Date(dateOfBirth) <= MINIMUM_DATE_OF_BIRTH, {
|
||||
message: 'You must be at least 19 years old to register.',
|
||||
}),
|
||||
|
||||
@@ -9,13 +9,13 @@ import { getLoginSession } from '../config/auth/session';
|
||||
* @template P - A generic type that represents an object with string keys and any values.
|
||||
* It defaults to an empty object.
|
||||
* @template Q - A generic type that represents a parsed URL query object. It defaults to
|
||||
* the ParsedUrlQuery type.
|
||||
* the `ParsedUrlQuery` type.
|
||||
* @template D - A generic type that represents preview data. It defaults to the
|
||||
* PreviewData type.
|
||||
* `PreviewData` type.
|
||||
* @param context - The context object containing information about the incoming HTTP
|
||||
* request.
|
||||
* @param session - An awaited value of the return type of the getLoginSession function.
|
||||
* @returns - A promise that resolves to the result of the server-side rendering process.
|
||||
* @param session - An awaited value of the return type of the `getLoginSession` function.
|
||||
* @returns A promise that resolves to the result of the server-side rendering process.
|
||||
*/
|
||||
export type ExtendedGetServerSideProps<
|
||||
P extends { [key: string]: any } = { [key: string]: any },
|
||||
@@ -30,14 +30,15 @@ export type ExtendedGetServerSideProps<
|
||||
* A Higher Order Function that adds an authentication requirement to a Next.js
|
||||
* server-side page component.
|
||||
*
|
||||
* @param fn An async function that receives the GetServerSidePropsContext and
|
||||
* authenticated session as arguments and returns a GetServerSidePropsResult with props
|
||||
* for the wrapped component.
|
||||
* @returns A promise that resolves to a GetServerSidePropsResult object with props for
|
||||
* the wrapped component. If authentication is successful, the GetServerSidePropsResult
|
||||
* will include props generated by the wrapped component's getServerSideProps method. If
|
||||
* authentication fails, the GetServerSidePropsResult will include a redirect to the
|
||||
* login page.
|
||||
* @param fn An async function that receives the `GetServerSidePropsContext` and
|
||||
* authenticated session as arguments and returns a `GetServerSidePropsResult` with
|
||||
* props for the wrapped component.
|
||||
* @returns A promise that resolves to a `GetServerSidePropsResult` object with props for
|
||||
* the wrapped component.
|
||||
*
|
||||
* If authentication is successful, the `GetServerSidePropsResult` will include props
|
||||
* generated by the wrapped component's `getServerSideProps` method. If authentication
|
||||
* fails, the `GetServerSidePropsResult` will include a redirect to the login page.
|
||||
*/
|
||||
|
||||
const withPageAuthRequired =
|
||||
|
||||
Reference in New Issue
Block a user