diff --git a/package-lock.json b/package-lock.json
index efb4b15..1a81ebc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,12 +11,18 @@
"dependencies": {
"@headlessui/react": "^1.7.17",
"@prisma/client": "^5.7.0",
- "next": "14.1.1",
+ "@tanstack/react-query": "^5.52.3",
+ "@trpc/client": "^11.0.0-rc.490",
+ "@trpc/next": "^11.0.0-rc.490",
+ "@trpc/react-query": "^11.0.0-rc.490",
+ "@trpc/server": "^11.0.0-rc.490",
+ "next": "^14.2.7",
+ "nuqs": "^1.17.8",
"prisma": "^5.7.0",
"react": "^18",
"react-dom": "^18",
"seedrandom": "^3.0.5",
- "zod": "^3.22.4"
+ "zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
@@ -253,9 +259,10 @@
}
},
"node_modules/@next/env": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz",
- "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA=="
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.7.tgz",
+ "integrity": "sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==",
+ "license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "14.0.4",
@@ -267,12 +274,13 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz",
- "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.7.tgz",
+ "integrity": "sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -282,12 +290,13 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz",
- "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.7.tgz",
+ "integrity": "sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -297,12 +306,13 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz",
- "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.7.tgz",
+ "integrity": "sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -312,12 +322,13 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz",
- "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.7.tgz",
+ "integrity": "sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -327,12 +338,13 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz",
- "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.7.tgz",
+ "integrity": "sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -342,12 +354,13 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz",
- "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.7.tgz",
+ "integrity": "sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -357,12 +370,13 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz",
- "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.7.tgz",
+ "integrity": "sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -372,12 +386,13 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz",
- "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.7.tgz",
+ "integrity": "sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==",
"cpu": [
"ia32"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -387,12 +402,13 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz",
- "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.7.tgz",
+ "integrity": "sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -509,14 +525,48 @@
"integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==",
"dev": true
},
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@swc/helpers": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
- "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "license": "Apache-2.0",
"dependencies": {
+ "@swc/counter": "^0.1.3",
"tslib": "^2.4.0"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.52.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.52.3.tgz",
+ "integrity": "sha512-+Gh7lXn+eoAsarvvnndgqBeJ5lOjup8qgQnrTsFuhNTEAo0H934DxEPro4s3TlmvITfDTJ3UDCy7kY8Azm0qsA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.52.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.52.3.tgz",
+ "integrity": "sha512-1K7l2hkqlWuh5SdaTYPSwMmHJF5dDk5INK+EtiEwUZW4+usWTXZx7QeHuk078oSzTzaVkEFyT3VquK7F0hYkUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.52.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@tanstack/react-virtual": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.5.0.tgz",
@@ -542,6 +592,69 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@trpc/client": {
+ "version": "11.0.0-rc.490",
+ "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.0.0-rc.490.tgz",
+ "integrity": "sha512-rfAzHF27osuP36JWGzq9TgXMnNc5kk1oAIoMafVHkCu3XlEZH3Ym2BpucHBWp/b5JYrtvzM91jskzvE0yO3mYw==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@trpc/server": "11.0.0-rc.490+b6a851870"
+ }
+ },
+ "node_modules/@trpc/next": {
+ "version": "11.0.0-rc.490",
+ "resolved": "https://registry.npmjs.org/@trpc/next/-/next-11.0.0-rc.490.tgz",
+ "integrity": "sha512-ie0adjNr78ZvuBCaRL5Yr3u98TUo61ASVPBOgdYNx6/S2XLEUpL7nhY0VHNjSrnh3Ng8IzpdKM4uJsqHvnCFVg==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.49.2",
+ "@trpc/client": "11.0.0-rc.490+b6a851870",
+ "@trpc/react-query": "11.0.0-rc.490+b6a851870",
+ "@trpc/server": "11.0.0-rc.490+b6a851870",
+ "next": "*",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@tanstack/react-query": {
+ "optional": true
+ },
+ "@trpc/react-query": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@trpc/react-query": {
+ "version": "11.0.0-rc.490",
+ "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.0.0-rc.490.tgz",
+ "integrity": "sha512-sM1AlLA5JPjsTRZJmXieAcbxHQXx+TGg1WTpyCAlBAue9rXGu9FoydfYxhpVcSrSbzdZ5HRmg/cXnC77/O7nBg==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.49.2",
+ "@trpc/client": "11.0.0-rc.490+b6a851870",
+ "@trpc/server": "11.0.0-rc.490+b6a851870",
+ "react": ">=18.2.0",
+ "react-dom": ">=18.2.0"
+ }
+ },
+ "node_modules/@trpc/server": {
+ "version": "11.0.0-rc.490",
+ "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.0.0-rc.490.tgz",
+ "integrity": "sha512-JkKLvA0QIErqslsgU6uFvpv9xtxe78C59zqUdZFmUI0V9pIAJlxmODV4I9hTDcX8P+B30rP3z+q2+bPozN2MaQ==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT"
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -3247,6 +3360,12 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -3288,12 +3407,13 @@
"dev": true
},
"node_modules/next": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz",
- "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==",
+ "version": "14.2.7",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.7.tgz",
+ "integrity": "sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==",
+ "license": "MIT",
"dependencies": {
- "@next/env": "14.1.1",
- "@swc/helpers": "0.5.2",
+ "@next/env": "14.2.7",
+ "@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
"graceful-fs": "^4.2.11",
@@ -3307,18 +3427,19 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "14.1.1",
- "@next/swc-darwin-x64": "14.1.1",
- "@next/swc-linux-arm64-gnu": "14.1.1",
- "@next/swc-linux-arm64-musl": "14.1.1",
- "@next/swc-linux-x64-gnu": "14.1.1",
- "@next/swc-linux-x64-musl": "14.1.1",
- "@next/swc-win32-arm64-msvc": "14.1.1",
- "@next/swc-win32-ia32-msvc": "14.1.1",
- "@next/swc-win32-x64-msvc": "14.1.1"
+ "@next/swc-darwin-arm64": "14.2.7",
+ "@next/swc-darwin-x64": "14.2.7",
+ "@next/swc-linux-arm64-gnu": "14.2.7",
+ "@next/swc-linux-arm64-musl": "14.2.7",
+ "@next/swc-linux-x64-gnu": "14.2.7",
+ "@next/swc-linux-x64-musl": "14.2.7",
+ "@next/swc-win32-arm64-msvc": "14.2.7",
+ "@next/swc-win32-ia32-msvc": "14.2.7",
+ "@next/swc-win32-x64-msvc": "14.2.7"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.3.0"
@@ -3327,6 +3448,9 @@
"@opentelemetry/api": {
"optional": true
},
+ "@playwright/test": {
+ "optional": true
+ },
"sass": {
"optional": true
}
@@ -3383,6 +3507,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/nuqs": {
+ "version": "1.17.8",
+ "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.17.8.tgz",
+ "integrity": "sha512-JqsnzO+hJyjJE7ebuhpHMLA2iGY48e2xr0oJQFhj7kjUmDABL2XOup47rxF5TL/5b9jEsmU2t0lAKin1VdK1/A==",
+ "license": "MIT",
+ "dependencies": {
+ "mitt": "^3.0.1"
+ },
+ "peerDependencies": {
+ "next": ">=13.4 <14.0.2 || ^14.0.3"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -4663,9 +4799,10 @@
}
},
"node_modules/tslib": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
+ "license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -5074,6 +5211,7 @@
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index b800890..16fff39 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
@@ -14,12 +14,18 @@
"dependencies": {
"@headlessui/react": "^1.7.17",
"@prisma/client": "^5.7.0",
- "next": "14.1.1",
+ "@tanstack/react-query": "^5.52.3",
+ "@trpc/client": "^11.0.0-rc.490",
+ "@trpc/next": "^11.0.0-rc.490",
+ "@trpc/react-query": "^11.0.0-rc.490",
+ "@trpc/server": "^11.0.0-rc.490",
+ "next": "^14.2.7",
+ "nuqs": "^1.17.8",
"prisma": "^5.7.0",
"react": "^18",
"react-dom": "^18",
"seedrandom": "^3.0.5",
- "zod": "^3.22.4"
+ "zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/context/Global/index.tsx b/src/context/Global/index.tsx
index 21e2c15..1ff6183 100644
--- a/src/context/Global/index.tsx
+++ b/src/context/Global/index.tsx
@@ -1,5 +1,5 @@
import { CACHE_VERSION, DEFAULT_OPTIONS, PUBLIC_DIR } from "@/constants";
-import { compareTables, getVariantsWithReflections } from "@/lib/recipe";
+import { compareTables, getVariantsWithReflections } from "@/utils/recipe";
import {
ColorTable,
GameState,
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 21adc96..cd0683c 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -4,50 +4,50 @@ import type { AppProps } from "next/app";
import Head from "next/head";
import Layout from "./layout";
-export default function App({ Component, pageProps }: AppProps) {
- return (
-
-
-
- Minecraftle
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+import { trpc } from "../utils/trpc";
+
+function App({ Component, pageProps }: AppProps) {
+ const layout = (
+
+
+ Minecraftle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
+ return {layout};
}
+
+export default trpc.withTRPC(App);
diff --git a/src/pages/api/scoreboard/[userId].ts b/src/pages/api/scoreboard/[userId].ts
deleted file mode 100644
index fb587eb..0000000
--- a/src/pages/api/scoreboard/[userId].ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import prisma from "@/lib/prisma";
-import { GenericApiError, ScoreboardRow } from "@/types";
-import { Prisma } from "@prisma/client";
-import type { NextApiRequest, NextApiResponse } from "next";
-
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse<{ localLeaderboard: ScoreboardRow[] } | GenericApiError>
-) {
- switch (req.method) {
- case "GET":
- if (!req.query.userId) {
- return res.status(400).json({
- error: "Missing user_id",
- });
- }
- const userId = req.query.userId;
- let localLeaderboard: ScoreboardRow[] = [];
- try {
- localLeaderboard = await prisma.$queryRaw`
- WITH user_rank AS (
- SELECT dense_rank_number FROM scoreboard WHERE user_id = ${userId}
- )
-
- SELECT *
- FROM scoreboard
- WHERE user_id = ${userId}
- OR user_id IN (
- SELECT user_id
- FROM scoreboard
- WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) - 1
- LIMIT 1
- )
- OR user_id IN (
- SELECT user_id
- FROM scoreboard
- WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) + 1
- LIMIT 1
- );
- `;
- } catch (error) {
- console.error(error);
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- return res.status(500).json({
- error: "Internal server error",
- details: error,
- });
- }
-
- return res.status(500).json({
- error: "Internal server error",
- });
- }
-
- localLeaderboard = JSON.parse(
- JSON.stringify(localLeaderboard, (_key, value) =>
- typeof value === "bigint" ? Number(value) : value
- )
- );
-
- localLeaderboard = localLeaderboard.map((r) => ({
- ...r,
- user_id: r.user_id === userId ? userId : null,
- }));
- console.log({ localLeaderboard: localLeaderboard });
- res.status(200).json({ localLeaderboard: localLeaderboard });
-
- break;
- default:
- return res.status(405).json({ error: "Invalid method" });
- }
-}
diff --git a/src/pages/api/submitgame.ts b/src/pages/api/submitgame.ts
deleted file mode 100644
index b6d6329..0000000
--- a/src/pages/api/submitgame.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import prisma from "@/lib/prisma";
-import { GenericApiError } from "@/types";
-import { Prisma } from "@prisma/client";
-import type { NextApiRequest, NextApiResponse } from "next";
-import { z } from "zod";
-
-const schema = z.object({
- user_id: z.string().refine((s) => !Number.isNaN(parseFloat(s))),
- attempts: z.number().min(1).max(11),
- date: z
- .string()
- .datetime()
- .refine(
- (date) => {
- const threeDaysAgo = new Date();
- threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
- return new Date(date) >= threeDaysAgo;
- },
- {
- message: "Date must be within the last 3 days",
- }
- ),
-});
-type SubmitGameSchema = z.infer;
-
-type Data = {
- success: boolean;
-};
-
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- switch (req.method) {
- case "POST":
- const response = schema.safeParse(req.body);
- console.log(JSON.stringify(response));
- if (!response.success) {
- return res.status(400).json({
- error: response.error,
- });
- }
- const { user_id, attempts, date } = req.body as SubmitGameSchema;
- const flatDate = new Date(date.slice(0, 10) + "T00:00:00.000Z");
-
- try {
- const user = await prisma.user.findUnique({
- where: {
- user_id: user_id,
- },
- select: {
- last_game_date: true,
- },
- });
- console.log(
- user?.last_game_date,
- flatDate,
- user?.last_game_date?.getTime() === flatDate.getTime()
- );
- if (user?.last_game_date?.getTime() === flatDate.getTime()) {
- return res.status(409).json({
- error: "Already submitted a game today!",
- });
- } else {
- await prisma.$transaction([
- prisma.user.upsert({
- where: {
- user_id: user_id.toString(),
- },
- update: {
- last_game_date: flatDate,
- },
- create: {
- user_id: user_id.toString(),
- last_game_date: flatDate,
- },
- }),
-
- prisma.game_count.upsert({
- where: {
- user_id_attempts: {
- user_id: user_id.toString(),
- attempts: attempts,
- },
- },
- update: {
- game_count: {
- increment: 1,
- },
- },
- create: {
- user_id: user_id.toString(),
- attempts: attempts,
- game_count: 1,
- },
- }),
- ]);
- }
- } catch (error: any) {
- console.error(error);
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- return res.status(500).json({
- error: "Game record insertion failed",
- details: error,
- });
- }
-
- return res.status(500).json({
- error: "Game record insertion failed",
- });
- }
- return res.status(200).json({ success: true });
- break;
- case "OPTIONS":
- console.log("OPTIONS");
- return res.status(200).json({ success: true });
- break;
- default:
- return res.status(405).json({ error: "Invalid method" });
- }
-}
diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts
new file mode 100644
index 0000000..5540cfe
--- /dev/null
+++ b/src/pages/api/trpc/[trpc].ts
@@ -0,0 +1,9 @@
+import * as trpcNext from "@trpc/server/adapters/next";
+import { appRouter } from "../../../server/routers/_app";
+
+// export API handler
+// @link https://trpc.io/docs/v11/server/adapters
+export default trpcNext.createNextApiHandler({
+ router: appRouter,
+ createContext: () => ({}),
+});
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 4f1e400..6838e05 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -5,6 +5,7 @@ import LoadingSpinner from "@/components/LoadingSpinner.component";
import MCButton from "@/components/MCButton.component";
import Popup from "@/components/Popup.component";
import { useGlobal } from "@/context/Global/context";
+import { trpc } from "@/utils/trpc";
import { Inter } from "next/font/google";
import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
@@ -24,6 +25,8 @@ export default function Home() {
} = useGlobal();
const [popupVisible, setPopupVisible] = useState(false);
+ const submitGame = trpc.game.submitGame.useMutation();
+
const divRef = useRef(null);
useEffect(() => {
@@ -44,39 +47,15 @@ export default function Home() {
localStorage.getItem("lastGameDate") !== gameDate.toDateString() &&
!random
) {
- if (gameState === "won") {
- fetch("/api/submitgame", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- user_id: userId,
- attempts: craftingTables.length,
- date: gameDate.toISOString(), // TODO make this based on the start time
- }),
- }).then((res) => {
- if (res.ok) {
- localStorage.setItem("lastGameDate", gameDate.toDateString());
- }
- });
- } else if (gameState === "lost") {
- fetch("/api/submitgame", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- user_id: userId,
- attempts: 11,
- date: gameDate.toISOString(), // make this based on the start time
- }),
- }).then((res) => {
- if (res.ok) {
- localStorage.setItem("lastGameDate", gameDate.toDateString());
- }
+ submitGame
+ .mutateAsync({
+ user_id: userId,
+ attempts: gameState === "won" ? craftingTables.length : 11,
+ date: gameDate.toISOString(), // TODO make this based on the start time
+ })
+ .then((res) => {
+ localStorage.setItem("lastGameDate", gameDate.toDateString());
});
- }
}
}, [gameState]);
diff --git a/src/pages/stats/[userId].tsx b/src/pages/stats/[userId].tsx
index e80d4f7..b018472 100644
--- a/src/pages/stats/[userId].tsx
+++ b/src/pages/stats/[userId].tsx
@@ -1,34 +1,30 @@
import Row from "@/components/StatRow.component";
-import prisma from "@/lib/prisma";
import { ScoreboardRow } from "@/types";
+import prisma from "@/utils/prisma";
+import { trpc } from "@/utils/trpc";
import { GetServerSideProps } from "next";
import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
+import { useQueryState } from "nuqs";
export default function Stats({
liveUserScores, // actual user scores
- // localLeaderboard, // materialized view scoreboard position and neighbours
totalPlayerCount,
}: {
liveUserScores: ScoreboardRow;
- // localLeaderboard: ScoreboardRow[];
totalPlayerCount: number;
}) {
const router = useRouter();
- const [localLeaderboard, setLocalLeaderboard] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- console.log(localLeaderboard);
- useEffect(() => {
- const fetchLocalLeaderboard = async () => {
- const res = await fetch(`/api/scoreboard/${router.query.userId}`);
- const data = await res.json();
- console.log(data);
- setLocalLeaderboard(data.localLeaderboard);
- setIsLoading(false);
- };
+ const userId = router.query.userId as string;
- fetchLocalLeaderboard();
- }, []);
+ const { data, isLoading } = trpc.game.scoreboard.useQuery(
+ {
+ userId: userId ?? "",
+ },
+ {
+ enabled: !!userId,
+ }
+ );
+ const localLeaderboard = data?.localLeaderboard;
const allAttempts = Object.keys(liveUserScores)
.filter((key) => key.match(/^total_\d+$/))
@@ -45,7 +41,7 @@ export default function Stats({
);
const ranking = () => {
- if (isLoading) {
+ if (!localLeaderboard || isLoading) {
return row("Global Rank", "Loading...");
}
if (localLeaderboard.length === 0) {
diff --git a/src/pages/stats/index.tsx b/src/pages/stats/index.tsx
index 7dba32a..88e0ca2 100644
--- a/src/pages/stats/index.tsx
+++ b/src/pages/stats/index.tsx
@@ -1,5 +1,5 @@
import Row from "@/components/StatRow.component";
-import prisma from "@/lib/prisma";
+import prisma from "@/utils/prisma";
import { GetServerSideProps } from "next";
export default function Stats({
diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts
new file mode 100644
index 0000000..8fc1c4c
--- /dev/null
+++ b/src/server/routers/_app.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { publicProcedure, createRouter } from "../trpc";
+import { gameRouter } from "./game";
+
+export const appRouter = createRouter({
+ hello: publicProcedure
+ .input(
+ z.object({
+ text: z.string(),
+ })
+ )
+ .query((opts) => {
+ return {
+ greeting: `hello ${opts.input.text}`,
+ };
+ }),
+ game: gameRouter,
+});
+
+// export type definition of API
+export type AppRouter = typeof appRouter;
diff --git a/src/server/routers/game.ts b/src/server/routers/game.ts
new file mode 100644
index 0000000..c84130e
--- /dev/null
+++ b/src/server/routers/game.ts
@@ -0,0 +1,146 @@
+import { ScoreboardRow } from "@/types";
+import prisma from "@/utils/prisma";
+import { TRPCError } from "@trpc/server";
+import { z } from "zod";
+import { createRouter, publicProcedure } from "../trpc";
+
+export const gameRouter = createRouter({
+ scoreboard: publicProcedure
+ .input(z.object({ userId: z.string() }))
+ .query(async ({ ctx, input }) => {
+ const userId = input.userId;
+ let localLeaderboard: ScoreboardRow[] = [];
+ try {
+ localLeaderboard = await prisma.$queryRaw`
+ WITH user_rank AS (
+ SELECT dense_rank_number FROM scoreboard WHERE user_id = ${userId}
+ )
+
+ SELECT *
+ FROM scoreboard
+ WHERE user_id = ${userId}
+ OR user_id IN (
+ SELECT user_id
+ FROM scoreboard
+ WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) - 1
+ LIMIT 1
+ )
+ OR user_id IN (
+ SELECT user_id
+ FROM scoreboard
+ WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) + 1
+ LIMIT 1
+ );
+ `;
+ } catch (error) {
+ console.error(error);
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "error",
+ });
+ }
+
+ localLeaderboard = JSON.parse(
+ JSON.stringify(localLeaderboard, (_key, value) =>
+ typeof value === "bigint" ? Number(value) : value
+ )
+ );
+
+ localLeaderboard = localLeaderboard.map((r) => ({
+ ...r,
+ user_id: r.user_id === userId ? userId : null,
+ }));
+ console.log({ localLeaderboard: localLeaderboard });
+ return { localLeaderboard: localLeaderboard };
+ }),
+ submitGame: publicProcedure
+ .input(
+ z.object({
+ user_id: z.string().refine((s) => !Number.isNaN(parseFloat(s))),
+ attempts: z.number().min(1).max(11),
+ date: z
+ .string()
+ .datetime()
+ .refine(
+ (date) => {
+ const threeDaysAgo = new Date();
+ threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
+ return new Date(date) >= threeDaysAgo;
+ },
+ {
+ message: "Date must be within the last 3 days",
+ }
+ ),
+ })
+ )
+ .mutation(async ({ ctx, input }) => {
+ const { user_id, attempts, date } = input;
+ const flatDate = new Date(date.slice(0, 10) + "T00:00:00.000Z");
+
+ try {
+ const user = await prisma.user.findUnique({
+ where: {
+ user_id: user_id,
+ },
+ select: {
+ last_game_date: true,
+ },
+ });
+ console.log(
+ user?.last_game_date,
+ flatDate,
+ user?.last_game_date?.getTime() === flatDate.getTime()
+ );
+ if (user?.last_game_date?.getTime() === flatDate.getTime()) {
+ return new TRPCError({
+ code: "CONFLICT",
+ message: "Already submitted a game today!",
+ });
+ } else {
+ await prisma.$transaction([
+ prisma.user.upsert({
+ where: {
+ user_id: user_id.toString(),
+ },
+ update: {
+ last_game_date: flatDate,
+ },
+ create: {
+ user_id: user_id.toString(),
+ last_game_date: flatDate,
+ },
+ }),
+
+ prisma.game_count.upsert({
+ where: {
+ user_id_attempts: {
+ user_id: user_id.toString(),
+ attempts: attempts,
+ },
+ },
+ update: {
+ game_count: {
+ increment: 1,
+ },
+ },
+ create: {
+ user_id: user_id.toString(),
+ attempts: attempts,
+ game_count: 1,
+ },
+ }),
+ ]);
+ }
+ } catch (error: any) {
+ console.error(error);
+
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Game record insertion failed" + error.toString(),
+ });
+ }
+ return { success: true };
+ }),
+});
+
+export type GameRouter = typeof gameRouter;
diff --git a/src/server/trpc.ts b/src/server/trpc.ts
new file mode 100644
index 0000000..30e81be
--- /dev/null
+++ b/src/server/trpc.ts
@@ -0,0 +1,11 @@
+import { initTRPC } from "@trpc/server";
+
+// Avoid exporting the entire t-object
+// since it's not very descriptive.
+// For instance, the use of a t variable
+// is common in i18n libraries.
+const t = initTRPC.create();
+
+// Base router and procedure helpers
+export const createRouter = t.router;
+export const publicProcedure = t.procedure;
diff --git a/src/lib/prisma.ts b/src/utils/prisma.ts
similarity index 100%
rename from src/lib/prisma.ts
rename to src/utils/prisma.ts
diff --git a/src/lib/recipe.ts b/src/utils/recipe.ts
similarity index 100%
rename from src/lib/recipe.ts
rename to src/utils/recipe.ts
diff --git a/src/utils/trpc.ts b/src/utils/trpc.ts
new file mode 100644
index 0000000..b34dd24
--- /dev/null
+++ b/src/utils/trpc.ts
@@ -0,0 +1,47 @@
+import { httpBatchLink } from "@trpc/client";
+import { createTRPCNext } from "@trpc/next";
+import type { AppRouter } from "../server/routers/_app";
+
+function getBaseUrl() {
+ if (typeof window !== "undefined")
+ // browser should use relative path
+ return "";
+
+ if (process.env.VERCEL_URL)
+ // reference for vercel.com
+ return `https://${process.env.VERCEL_URL}`;
+
+ if (process.env.RENDER_INTERNAL_HOSTNAME)
+ // reference for render.com
+ return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
+
+ // assume localhost
+ return `http://localhost:${process.env.PORT ?? 3000}`;
+}
+
+export const trpc = createTRPCNext({
+ config(opts) {
+ return {
+ links: [
+ httpBatchLink({
+ /**
+ * If you want to use SSR, you need to use the server's full URL
+ * @link https://trpc.io/docs/v11/ssr
+ **/
+ url: `${getBaseUrl()}/api/trpc`,
+
+ // You can pass any HTTP headers you wish here
+ async headers() {
+ return {
+ // authorization: getAuthCookie(),
+ };
+ },
+ }),
+ ],
+ };
+ },
+ /**
+ * @link https://trpc.io/docs/v11/ssr
+ **/
+ ssr: false,
+});
diff --git a/tsconfig.json b/tsconfig.json
index 6049381..07d3117 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -18,6 +18,6 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/lib/crafting.js"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/utils/crafting.js"],
"exclude": ["node_modules"]
}