diff --git a/package-lock.json b/package-lock.json index a6f9515e..37697006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,9 +30,9 @@ "next": "^14.2.2", "next-auth": "^4.23.0", "postgres": "^3.3.5", - "react": "18.2.0", + "react": "^18.2.0", "react-day-picker": "^8.9.1", - "react-dom": "18.2.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.48.2", "superjson": "^1.13.1", "zod": "^3.22.4" @@ -44,8 +44,8 @@ "@types/eslint": "^8.44.2", "@types/jest": "^29.5.5", "@types/node": "^18.16.0", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "autoprefixer": "^10.4.14", @@ -54,7 +54,7 @@ "dotenv-cli": "^7.3.0", "drizzle-kit": "^0.19.13", "eslint": "^8.47.0", - "eslint-config-next": "^13.5.4", + "eslint-config-next": "^14.2.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.29.4", @@ -1284,6 +1284,96 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1724,54 +1814,49 @@ "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.5.5.tgz", - "integrity": "sha512-S/32s4S+SCOpW58lHKdmILAAPRdnsSei7Y3L1oZSoe5Eh0QSlzbG1nYyIpnpwWgz3T7qe3imdq7cJ6Hf29epRA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.1.tgz", + "integrity": "sha512-Fp+mthEBjkn8r9qd6o4JgxKp0IDEzW0VYHD8ZC05xS5/lFNwHKuOdr2kVhWG7BQCO9L6eeepshM1Wbs2T+LgSg==", "dev": true, "dependencies": { - "glob": "7.1.7" - } - }, - "node_modules/@next/eslint-plugin-next/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "glob": "10.3.10" } }, "node_modules/@next/eslint-plugin-next/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@next/eslint-plugin-next/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@next/swc-darwin-arm64": { @@ -1952,6 +2037,16 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", @@ -2980,31 +3075,24 @@ "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.28.tgz", - "integrity": "sha512-ad4aa/RaaJS3hyGz0BGegdnSRXQBkd1CCYDCdNjBPg90UUpLgo+WlJqb9fMYUxtehmzF3PJaTWqRZjko6BRzBg==", + "version": "18.2.79", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", + "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", "devOptional": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.13.tgz", - "integrity": "sha512-eJIUv7rPP+EC45uNYp/ThhSpE16k22VJUknt5OLoH9tbXoi8bMhwLf5xRuWMywamNbWzhrSmU7IBJfPup1+3fw==", + "version": "18.2.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", + "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", "devOptional": true, "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", - "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", - "devOptional": true - }, "node_modules/@types/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -5488,14 +5576,14 @@ } }, "node_modules/eslint-config-next": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.5.5.tgz", - "integrity": "sha512-kQr/eevFyzeVt0yCKTchQp3MTIx8ZmBsAKLW+7bzmAXHcf2vvxIqAt2N/afb9SZpuXXhSb/8yrKQGVUVpYmafQ==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.1.tgz", + "integrity": "sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "13.5.5", + "@next/eslint-plugin-next": "14.2.1", "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.28.1", @@ -6260,6 +6348,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -7569,6 +7685,24 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -8871,6 +9005,15 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -9498,6 +9641,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -10919,6 +11087,36 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11011,6 +11209,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -11971,6 +12182,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 012bd751..d48fa4d3 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "next": "^14.2.2", "next-auth": "^4.23.0", "postgres": "^3.3.5", - "react": "18.2.0", + "react": "^18.2.0", "react-day-picker": "^8.9.1", - "react-dom": "18.2.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.48.2", "superjson": "^1.13.1", "zod": "^3.22.4" @@ -49,8 +49,8 @@ "@types/eslint": "^8.44.2", "@types/jest": "^29.5.5", "@types/node": "^18.16.0", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "autoprefixer": "^10.4.14", @@ -59,7 +59,7 @@ "dotenv-cli": "^7.3.0", "drizzle-kit": "^0.19.13", "eslint": "^8.47.0", - "eslint-config-next": "^13.5.4", + "eslint-config-next": "^14.2.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.29.4", diff --git a/src/app/events/eventView.tsx b/src/app/events/eventView.tsx index dfaed368..49493e86 100644 --- a/src/app/events/eventView.tsx +++ b/src/app/events/eventView.tsx @@ -1,75 +1,33 @@ -'use server'; -import { GridIcon, ListIcon } from '@src/icons/Icons'; -import EventCard from '@src/components/events/EventCard'; -import EventSidebar from '@src/components/events/EventSidebar'; -import { type RouterOutputs } from '@src/trpc/shared'; -import Link from 'next/link'; +'use client'; +import DateBrowser from '@src/components/events/DateBrowser'; +import { type eventParamsSchema } from '@src/utils/eventFilter'; +import useSyncedSearchParams from '@src/utils/useSyncedSearchParams'; +import { type ReactNode } from 'react'; type Props = { - events: RouterOutputs['event']['findByFilters']['events']; - params: Record; + children: ReactNode; + searchParams: eventParamsSchema; }; -const EventView = ({ events, params }: Props) => { - const view = (params?.view ?? 'list') as 'list' | 'grid'; - +const EventView = ({ children, searchParams }: Props) => { + const [params, setParams] = useSyncedSearchParams(searchParams, '/events'); return (
-
-

Events

-
- -
- -
-

List view

- - -
- -
-

Grid view

- +
+

+ Events +

+
+
-
- +
- {events.map((event) => { - return ; - })} + {children}
diff --git a/src/app/events/page.tsx b/src/app/events/page.tsx index bb5935e2..8036676e 100644 --- a/src/app/events/page.tsx +++ b/src/app/events/page.tsx @@ -1,27 +1,10 @@ import { EventHeader } from '@src/components/BaseHeader'; -import { type filterState } from '@src/components/events/EventSidebar'; import { api } from '@src/trpc/server'; -import { type z } from 'zod'; -import { type findByFilterSchema } from '@src/server/api/routers/event'; import EventView from './eventView'; import { type Metadata } from 'next'; +import { eventParamsSchema } from '@src/utils/eventFilter'; +import EventCard from '@src/components/events/EventCard'; -function getStartTime( - filterState: filterState, -): z.infer['startTime'] { - switch (filterState.filter) { - case 'Upcoming Events': - return { type: 'now' }; - case 'Last weeks events': - return { type: 'distance', options: { days: -7 } }; - case 'Last month events': - return { type: 'distance', options: { days: -30 } }; - case 'pick': - return filterState.date - ? { type: 'range', options: filterState.date } - : { type: 'now' }; - } -} export const metadata: Metadata = { title: 'Events - Jupiter', description: 'Get connected on campus.', @@ -33,48 +16,23 @@ export const metadata: Metadata = { description: 'Get connected on campus.', }, }; -type searchPams = Partial< - Omit & { - date: string | string[] | undefined; - clubs: string | string[] | undefined; - view: 'list' | 'grid' | undefined; - } ->; -const Events = async ({ searchParams }: { searchParams: searchPams }) => { - const filters = { - filter: searchParams.filter ?? 'Upcoming Events', - date: - searchParams.filter === 'pick' - ? searchParams.date - ? Array.isArray(searchParams.date) - ? { - from: searchParams.date[0] - ? new Date(Number.parseInt(searchParams.date[0])) - : undefined, - to: searchParams.date[1] - ? new Date(Number.parseInt(searchParams.date[1])) - : undefined, - } - : { from: new Date(Number.parseInt(searchParams.date)) } - : undefined - : undefined, - clubs: searchParams.clubs - ? Array.isArray(searchParams.clubs) - ? searchParams.clubs - : [searchParams.clubs] - : [], - order: searchParams.order ?? 'soon', - types: searchParams.types ?? [], - }; - const { events } = await api.event.findByFilters({ - startTime: getStartTime(filters), - club: filters.clubs, - order: filters.order, +const Events = async ({ + searchParams, +}: { + searchParams: (typeof eventParamsSchema)['_input']; +}) => { + const parsed = eventParamsSchema.parse(searchParams); + const { events } = await api.event.findByDate({ + date: parsed.date, }); return (
- + + {events.map((event) => { + return ; + })} +
); }; diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index e8675c4a..fb219eb2 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -134,6 +134,7 @@ type EventClubSearchBarProps = { export const EventClubSearchBar = ({ addClub }: EventClubSearchBarProps) => { const [search, setSearch] = useState(''); const [res, setRes] = useState([]); + const utils = api.useUtils(); const clubQuery = api.club.byName.useQuery( { name: search }, { @@ -152,6 +153,7 @@ export const EventClubSearchBar = ({ addClub }: EventClubSearchBarProps) => { value={search} searchResults={res} onClick={(club) => { + void utils.club.byId.prefetch({ id: club.id }); addClub(club.id); setSearch(''); }} diff --git a/src/components/events/DateBrowser.tsx b/src/components/events/DateBrowser.tsx new file mode 100644 index 00000000..952dcabb --- /dev/null +++ b/src/components/events/DateBrowser.tsx @@ -0,0 +1,69 @@ +'use client'; + +import 'react-day-picker/dist/style.css'; +import { LeftArrowIcon, RightArrowIcon } from '@src/icons/Icons'; +import { DayPicker, useInput } from 'react-day-picker'; +import { + Popover, + PopoverClose, + PopoverContent, + PopoverPortal, + PopoverTrigger, +} from '@radix-ui/react-popover'; +import { addDays, subDays } from 'date-fns'; +import { useEffect } from 'react'; +import { type eventParamsSchema } from '@src/utils/eventFilter'; +import { type useSyncedSearchParamsDispatch } from '@src/utils/useSyncedSearchParams'; +type DateBrowserProps = { + filterState: eventParamsSchema; + setParams: useSyncedSearchParamsDispatch; +}; +const DateBrowser = ({ filterState, setParams }: DateBrowserProps) => { + const { inputProps, dayPickerProps, setSelected } = useInput({ + defaultSelected: filterState.date, + }); + useEffect(() => { + if (dayPickerProps.selected != undefined) { + setParams({ date: dayPickerProps.selected }); + } + }, [dayPickerProps.selected]); + return ( +
+ {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */} + + + + + + + + + + + + + +
+ ); +}; +export default DateBrowser; diff --git a/src/components/events/DatePopover.tsx b/src/components/events/DatePopover.tsx deleted file mode 100644 index d5a170f2..00000000 --- a/src/components/events/DatePopover.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; -import { PopoverClose, PopoverContent } from '@radix-ui/react-popover'; -import { type Dispatch } from 'react'; -import { DayPicker, type DateRange } from 'react-day-picker'; -import 'react-day-picker/dist/style.css'; -type DatePickerPopoverProps = { - range: DateRange | undefined; - setRange: Dispatch; -}; -const DatePickerPopover = ({ range, setRange }: DatePickerPopoverProps) => { - return ( - - - - - ); -}; -export default DatePickerPopover; diff --git a/src/components/events/EventCard.tsx b/src/components/events/EventCard.tsx index 0f16625f..75008289 100644 --- a/src/components/events/EventCard.tsx +++ b/src/components/events/EventCard.tsx @@ -3,10 +3,14 @@ import { format, isSameDay } from 'date-fns'; import Image from 'next/image'; import Link from 'next/link'; import { MoreIcon } from '../../icons/Icons'; -import EventTimeAlert from './EventTimeAlert'; import { type RouterOutputs } from '@src/trpc/shared'; import EventLikeButton from '../EventLikeButton'; import { getServerAuthSession } from '@src/server/auth'; +import dynamic from 'next/dynamic'; + +const EventTimeAlert = dynamic(() => import('./EventTimeAlert'), { + ssr: false, +}); type EventCardProps = { event: RouterOutputs['event']['findByFilters']['events'][number]; diff --git a/src/components/events/EventSidebar.tsx b/src/components/events/EventSidebar.tsx deleted file mode 100644 index 2ab70424..00000000 --- a/src/components/events/EventSidebar.tsx +++ /dev/null @@ -1,332 +0,0 @@ -'use client'; -import { - RadioGroup, - RadioGroupIndicator, - RadioGroupItem, -} from '@radix-ui/react-radio-group'; -import { EventClubSearchBar } from '../SearchBar'; -import { - Popover, - PopoverPortal, - PopoverTrigger, -} from '@radix-ui/react-popover'; -import DatePopover from './DatePopover'; -import { - useRouter, - useSearchParams, - usePathname, - type ReadonlyURLSearchParams, -} from 'next/navigation'; -import { type DateRange } from 'react-day-picker'; -import { type Dispatch, type SetStateAction, useEffect, useState } from 'react'; -import { api } from '@src/trpc/react'; -import { format, isEqual } from 'date-fns'; -import { ExpandLess, ExpandMore } from '@src/icons/Icons'; - -export const filters = [ - 'Upcoming Events', - 'Last weeks events', - 'Last month events', -] as const; -const order = [ - 'soon', - 'later', - 'shortest duration', - 'longest duration', -] as const; -const types = ['In-Person', 'Virtual', 'Multi-Day', 'Hybrid'] as const; -export type filterState = { - filter: (typeof filters)[number] | 'pick'; - date?: DateRange; - clubs: Array; - order: (typeof order)[number]; - types: (typeof types)[number][]; -}; - -function createSearchParams( - oldSearchParams: URLSearchParams, - filters: filterState, -) { - const params = new URLSearchParams(oldSearchParams); - params.set('filter', filters.filter); - if (filters.filter == 'pick' && filters.date) { - if (filters.date.from) - params.set('date', filters.date.from.getTime().toString()); - if (filters.date.to) - params.append('date', filters.date.to.getTime().toString()); - } - params.set('order', filters.order); - params.delete('clubs'); - filters.clubs.forEach((val) => params.append('clubs', val)); - - return params.toString(); -} -function createFilterState(searchParams: ReadonlyURLSearchParams): filterState { - const filter = searchParams.get('filter') as filterState['filter'] | null; - const order = searchParams.get('order') as filterState['order'] | null; - const clubs = (searchParams.getAll('clubs') ?? []) as - | filterState['clubs'] - | null; - if (filter === 'pick') { - const dates = searchParams.getAll('date'); - return { - filter: filter, - date: { - from: dates[0] ? new Date(Number.parseInt(dates[0])) : undefined, - to: dates[1] ? new Date(Number.parseInt(dates[1])) : undefined, - }, - order: order ?? 'soon', - clubs: clubs ?? [], - types: [], - }; - } else { - return { - filter: filter ?? 'Upcoming Events', - order: order ?? 'soon', - clubs: clubs ?? [], - types: [], - }; - } -} - -const Filters = () => { - const searchParams = useSearchParams(); - const router = useRouter(); - const pathname = usePathname(); - const [filterState, setFilterState] = useState( - createFilterState(searchParams) ?? { - filter: 'Upcoming Events', - clubs: [], - order: 'soon', - types: [], - }, - ); - useEffect(() => { - router.push(pathname + '?' + createSearchParams(searchParams, filterState)); - router.refresh(); - }, [filterState, router, pathname, searchParams]); - return ( -
-
-

Search for Club

-
- { - setFilterState((old) => { - if (!old.clubs.includes(val)) { - return { - filter: old.filter, - clubs: [...old.clubs, val], - order: old.order, - types: old.types, - }; - } - return old; - }); - }} - /> -
- {filterState.clubs.map((value) => ( - - ))} -
-
-
-
-

Filters

- { - setFilterState((old) => { - return { - filter: value as (typeof filters)[number], - date: old.date, - clubs: old.clubs, - order: old.order, - types: old.types, - }; - }); - }} - > - {filters.map((value) => ( - -
-
- -
-

- {value} -

-
-
- ))} - - - -
-
- -
-

- {filterState.date?.from - ? filterState.date.to - ? isEqual(filterState.date.from, filterState.date.to) - ? format(filterState.date.from, 'PPP') - : format(filterState.date.from, 'MMM do') + - ' - ' + - format(filterState.date.to, 'MMM do') - : format(filterState.date.from, 'PPP') - : 'Pick a date range'} -

-
-
- - {filterState.filter === 'pick' && ( - { - setFilterState((old) => { - return { - filter: 'pick', - clubs: old.clubs, - date: val, - order: old.order, - types: old.types, - }; - }); - }} - /> - )} - -
-
-
-
-
-

Arrange Events

- { - setFilterState((old) => { - return { - filter: old.filter, - clubs: old.clubs, - order: value as (typeof order)[number], - types: old.types, - }; - }); - }} - > - {order.map((value) => ( - -
-
- -
-

- {value} -

-
-
- ))} -
-
- {/* for future when we add event types */} -
-

Event Types

-
- {types.map((value) => ( -
- -
- ))} -
-
-
- ); -}; -const SelectedClub = ({ - clubId, - setFilterState, -}: { - clubId: string; - setFilterState: Dispatch>; -}) => { - const query = api.club.byId.useQuery({ id: clubId }); - return ( -
- - {query.data?.name} - - -
- ); -}; -const EventSidebar = () => { - const [open, setOpen] = useState(true); - return ( -
- -
-
- -
-
-
- ); -}; -export default EventSidebar; diff --git a/src/components/events/EventTimeAlert.tsx b/src/components/events/EventTimeAlert.tsx index 0748f121..c027f020 100644 --- a/src/components/events/EventTimeAlert.tsx +++ b/src/components/events/EventTimeAlert.tsx @@ -1,3 +1,4 @@ +'use client'; import { type SelectEvent } from '@src/server/db/models'; import { differenceInDays, diff --git a/src/server/api/routers/event.ts b/src/server/api/routers/event.ts index 68d0c842..e883f40e 100644 --- a/src/server/api/routers/event.ts +++ b/src/server/api/routers/event.ts @@ -13,17 +13,15 @@ import { import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc'; import { z } from 'zod'; import { selectEvent } from '@src/server/db/models'; -import { type DateRange } from 'react-day-picker'; -import { add } from 'date-fns'; -import { userMetadataToClubs, userMetadataToEvents } from '@src/server/db/schema/users'; +import { add, startOfDay } from 'date-fns'; +import { + userMetadataToClubs, + userMetadataToEvents, +} from '@src/server/db/schema/users'; import { createEventSchema } from '@src/utils/formSchemas'; import { TRPCError } from '@trpc/server'; import { events } from '@src/server/db/schema/events'; -function isDateRange(value: unknown): value is DateRange { - return Boolean(value && typeof value === 'object' && 'from' in value); -} - const byClubIdSchema = z.object({ clubId: z.string().default(''), currentTime: z.optional(z.date()), @@ -35,22 +33,13 @@ const byDateRangeSchema = z.object({ endTime: z.date(), }); export const findByFilterSchema = z.object({ - startTime: z.union([ - z.object({ - type: z.literal('now'), - }), - z.object({ - type: z.literal('distance'), - options: z.object({ days: z.number().int() }), - }), - z.object({ - type: z.literal('range'), - options: z.custom((val) => isDateRange(val)), - }), - ]), + date: z.date(), order: z.enum(['soon', 'later', 'shortest duration', 'longest duration']), club: z.string().array(), }); +export const findByDateSchema = z.object({ + date: z.date(), +}); const byIdSchema = z.object({ id: z.string().default(''), @@ -110,46 +99,67 @@ export const eventRouter = createTRPCRouter({ throw e; } }), + findByDate: publicProcedure + .input(findByDateSchema) + .query(async ({ input, ctx }) => { + const startTime = startOfDay(input.date); + const endTime = add(startTime, { days: 1 }); + const events = await ctx.db.query.events.findMany({ + where: (event) => { + return or( + between(event.startTime, startTime, endTime), + between(event.endTime, startTime, endTime), + and(lte(event.startTime, startTime), gte(event.endTime, startTime)), + and(lte(event.startTime, endTime), gte(event.endTime, endTime)), + ); + }, + + with: { + club: true, + }, + limit: 20, + }); + if (ctx.session) { + const user = ctx.session.user; + const eventsWithLike = await Promise.all( + events.map(async (ev) => { + const liked = !!(await ctx.db.query.userMetadataToEvents.findFirst({ + where: (userMetadataToEvents) => + and( + eq(userMetadataToEvents.userId, user.id), + eq(userMetadataToEvents.eventId, ev.id), + ), + })); + return { ...ev, liked: liked }; + }), + ); + return { events: eventsWithLike }; + } + return { + events: events.map((event) => { + return { ...event, liked: false }; + }), + }; + }), findByFilters: publicProcedure .input(findByFilterSchema) .query(async ({ input, ctx }) => { - const startTime = - input.startTime.type === 'now' - ? new Date() - : input.startTime.type === 'distance' - ? add(new Date(), input.startTime.options) - : input.startTime.options.from ?? new Date(); - const endTime = - input.startTime.type === 'distance' - ? new Date() - : input.startTime.type === 'range' - ? input.startTime.options.to - ? add(input.startTime.options.to, { days: 1 }) - : add(startTime, { days: 1 }) - : undefined; + const startTime = startOfDay(input.date); + const endTime = add(startTime, { days: 1 }); const events = await ctx.db.query.events.findMany({ where: (event) => { const whereElements: Array | undefined> = []; - if (!endTime) { - whereElements.push( - or( - gte(event.startTime, startTime), + whereElements.push( + or( + between(event.startTime, startTime, endTime), + between(event.endTime, startTime, endTime), + and( + lte(event.startTime, startTime), gte(event.endTime, startTime), ), - ); - } else { - whereElements.push( - or( - between(event.startTime, startTime, endTime), - between(event.endTime, startTime, endTime), - and( - lte(event.startTime, startTime), - gte(event.endTime, startTime), - ), - and(lte(event.startTime, endTime), gte(event.endTime, endTime)), - ), - ); - } + and(lte(event.startTime, endTime), gte(event.endTime, endTime)), + ), + ); if (input.club.length !== 0) { whereElements.push(inArray(event.clubId, input.club)); @@ -238,18 +248,18 @@ export const eventRouter = createTRPCRouter({ create: protectedProcedure .input(createEventSchema) .mutation(async ({ input, ctx }) => { - const { clubId } = input + const { clubId } = input; const userId = ctx.session.user.id; const isOfficer = await ctx.db.query.userMetadataToClubs.findFirst({ where: and( eq(userMetadataToClubs.userId, userId), eq(userMetadataToClubs.clubId, clubId), - inArray(userMetadataToClubs.memberType, ["Officer", "President"]) - ) + inArray(userMetadataToClubs.memberType, ['Officer', 'President']), + ), }); if (!isOfficer) { - throw new TRPCError({ code: "UNAUTHORIZED" }); + throw new TRPCError({ code: 'UNAUTHORIZED' }); } await ctx.db.insert(events).values({ ...input }); diff --git a/src/styles/globals.css b/src/styles/globals.css index a398ab29..30607d4b 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -92,3 +92,9 @@ a { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } + +@layer utilities { + .items-normal { + align-items: normal; + } +} diff --git a/src/utils/encodeParams.ts b/src/utils/encodeParams.ts new file mode 100644 index 00000000..816131f9 --- /dev/null +++ b/src/utils/encodeParams.ts @@ -0,0 +1,14 @@ +export default function EncodeParams< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Record, +>(obj: T): string { + return Object.entries(obj) + .map((value) => + typeof value[1] === 'string' || + typeof value[1] === 'number' || + typeof value[1] === 'boolean' + ? `${value[0]}=${encodeURIComponent(value[1])}` + : `${value[0]}=${encodeURIComponent(JSON.stringify(value[1]))}`, + ) + .join('&'); +} diff --git a/src/utils/eventFilter.ts b/src/utils/eventFilter.ts new file mode 100644 index 00000000..53b2cdab --- /dev/null +++ b/src/utils/eventFilter.ts @@ -0,0 +1,17 @@ +import { parseJSON, startOfToday } from 'date-fns'; +import { z } from 'zod'; +export const order = [ + 'soon', + 'later', + 'shortest duration', + 'longest duration', +] as const; +export const eventParamsSchema = z.object({ + date: z + .string() + .default('') + .transform((s) => parseJSON(decodeURIComponent(s))) + .pipe(z.date().catch(startOfToday())), +}); + +export type eventParamsSchema = z.infer; diff --git a/src/utils/useSyncedSearchParams.ts b/src/utils/useSyncedSearchParams.ts new file mode 100644 index 00000000..1ae3c109 --- /dev/null +++ b/src/utils/useSyncedSearchParams.ts @@ -0,0 +1,22 @@ +'use client'; +import { useRouter } from 'next/navigation'; +import { useReducer } from 'react'; +import EncodeParams from './encodeParams'; + +// not sure about the name, but basically makes updates to searchparams not overwrite themselves +const useSyncedSearchParams = >( + searchParams: T, + route: string, +) => { + const router = useRouter(); + const [params, setParams] = useReducer((state: T, action: Partial) => { + const temp = { ...state, ...action }; + router.push(`${route}?${EncodeParams(temp)}`); + return temp; + }, searchParams); + return [params, setParams] as const; +}; +export default useSyncedSearchParams; + +export type useSyncedSearchParamsDispatch> = + ReturnType>[1];