mirror of
https://github.com/Nezumi-2711/multitenant-ecommerce.git
synced 2026-09-22 13:38:34 +00:00
Merge pull request #5 from Nezumi-2711/feat/integration-with-trpc
Feature/ Integration with TRPC
This commit is contained in:
@@ -35,7 +35,12 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.6",
|
"@radix-ui/react-toggle": "^1.1.6",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.7",
|
"@radix-ui/react-toggle-group": "^1.1.7",
|
||||||
"@radix-ui/react-tooltip": "^1.2.4",
|
"@radix-ui/react-tooltip": "^1.2.4",
|
||||||
|
"@tanstack/react-query": "5.72.1",
|
||||||
|
"@trpc/client": "11.0.3",
|
||||||
|
"@trpc/server": "11.0.3",
|
||||||
|
"@trpc/tanstack-react-query": "11.0.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"client-only": "0.0.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -52,11 +57,13 @@
|
|||||||
"react-hook-form": "^7.56.1",
|
"react-hook-form": "^7.56.1",
|
||||||
"react-resizable-panels": "^2.1.9",
|
"react-resizable-panels": "^2.1.9",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
|
"server-only": "0.0.1",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.3",
|
||||||
|
"superjson": "2.2.2",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tw-animate-css": "1.2.5",
|
"tw-animate-css": "1.2.5",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^3.24.3",
|
"zod": "3.24.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
@@ -708,8 +715,18 @@
|
|||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.4", "@tailwindcss/oxide": "4.1.4", "postcss": "^8.4.41", "tailwindcss": "4.1.4" } }, "sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.4", "@tailwindcss/oxide": "4.1.4", "postcss": "^8.4.41", "tailwindcss": "4.1.4" } }, "sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw=="],
|
||||||
|
|
||||||
|
"@tanstack/query-core": ["@tanstack/query-core@5.72.1", "", {}, "sha512-nOu0EEkZuJ0BZnYgeaEfo44+psq1jBO7/zp3KudixD4dvgOVerrhAhDEKsWx2N7MxB59mjO4r0ddP/VqWGPK+Q=="],
|
||||||
|
|
||||||
|
"@tanstack/react-query": ["@tanstack/react-query@5.72.1", "", { "dependencies": { "@tanstack/query-core": "5.72.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-4UEMyRx54xj144D2nDvDIMiXSG5BrqyCJrmyNoGbymNS+VWODcBDFrmRk9p2fe12UGZ4JtKPTNuW2Jg0aisUgQ=="],
|
||||||
|
|
||||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||||
|
|
||||||
|
"@trpc/client": ["@trpc/client@11.0.3", "", { "peerDependencies": { "@trpc/server": "11.0.3", "typescript": ">=5.7.2" } }, "sha512-nUhRo/Ml3LQzs2USs5pfquNqETEQOMS6o9IR30cwn32oj4NuXXSqe9UqUc3hIBALtVzcfg6mLK43hGQA9O3X/Q=="],
|
||||||
|
|
||||||
|
"@trpc/server": ["@trpc/server@11.0.3", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-/qlaFITZ5a9OCI5mZiW94C8q9mr/wxCmb8GfP3k7VF9RuY4ql+M1UWrzS7xAj8bHekqG9PcR8qEWlOJ6NnXbQg=="],
|
||||||
|
|
||||||
|
"@trpc/tanstack-react-query": ["@trpc/tanstack-react-query@11.0.3", "", { "peerDependencies": { "@tanstack/react-query": "^5.67.1", "@trpc/client": "11.0.3", "@trpc/server": "11.0.3", "react": ">=18.2.0", "react-dom": ">=18.2.0", "typescript": ">=5.7.2" } }, "sha512-4U3oagLmWBQ1nxbfyvQInImWHoIGx8AQcSaB+wrynFN8u+VAk2AvYNXpSolsdbmkTH1IJlofp+NlsvU4kPW7wA=="],
|
||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
||||||
|
|
||||||
"@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="],
|
"@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="],
|
||||||
@@ -946,6 +963,8 @@
|
|||||||
|
|
||||||
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||||
|
|
||||||
|
"copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
|
||||||
|
|
||||||
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
||||||
|
|
||||||
"croner": ["croner@9.0.0", "", {}, "sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA=="],
|
"croner": ["croner@9.0.0", "", {}, "sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA=="],
|
||||||
@@ -1286,6 +1305,8 @@
|
|||||||
|
|
||||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||||
|
|
||||||
|
"is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
|
||||||
|
|
||||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||||
|
|
||||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
@@ -1656,6 +1677,8 @@
|
|||||||
|
|
||||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
|
||||||
|
|
||||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||||
|
|
||||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||||
@@ -1732,6 +1755,8 @@
|
|||||||
|
|
||||||
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||||
|
|
||||||
|
"superjson": ["superjson@2.2.2", "", { "dependencies": { "copy-anything": "^3.0.2" } }, "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q=="],
|
||||||
|
|
||||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||||
@@ -1854,7 +1879,7 @@
|
|||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="],
|
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -43,7 +43,12 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.6",
|
"@radix-ui/react-toggle": "^1.1.6",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.7",
|
"@radix-ui/react-toggle-group": "^1.1.7",
|
||||||
"@radix-ui/react-tooltip": "^1.2.4",
|
"@radix-ui/react-tooltip": "^1.2.4",
|
||||||
|
"@tanstack/react-query": "5.72.1",
|
||||||
|
"@trpc/client": "11.0.3",
|
||||||
|
"@trpc/server": "11.0.3",
|
||||||
|
"@trpc/tanstack-react-query": "11.0.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"client-only": "0.0.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -60,11 +65,13 @@
|
|||||||
"react-hook-form": "^7.56.1",
|
"react-hook-form": "^7.56.1",
|
||||||
"react-resizable-panels": "^2.1.9",
|
"react-resizable-panels": "^2.1.9",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
|
"server-only": "0.0.1",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.3",
|
||||||
|
"superjson": "2.2.2",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tw-animate-css": "1.2.5",
|
"tw-animate-css": "1.2.5",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^3.24.3"
|
"zod": "3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|||||||
@@ -1,46 +1,29 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode, Suspense } from 'react';
|
||||||
import configPromise from '@payload-config';
|
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
||||||
import { getPayload } from 'payload';
|
|
||||||
|
import { getQueryClient, trpc } from '@/trpc/server';
|
||||||
|
|
||||||
import Navbar from './navbar';
|
import Navbar from './navbar';
|
||||||
import Footer from './footer';
|
import Footer from './footer';
|
||||||
import SearchFilters from './search-filters';
|
import SearchFilters, { SearchFiltersSkeleton } from './search-filters';
|
||||||
import { Category } from '@/payload-types';
|
|
||||||
import { CustomCategory } from './type';
|
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = async ({ children }: LayoutProps) => {
|
const Layout = async ({ children }: LayoutProps) => {
|
||||||
const payload = await getPayload({
|
const queryClient = getQueryClient();
|
||||||
config: configPromise,
|
void queryClient.prefetchQuery(trpc.categories.getMany.queryOptions());
|
||||||
});
|
|
||||||
|
|
||||||
const data = await payload.find({
|
|
||||||
collection: 'categories',
|
|
||||||
depth: 1,
|
|
||||||
where: {
|
|
||||||
parent: {
|
|
||||||
exists: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: 'name',
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedData: CustomCategory[] = data.docs.map((category) => ({
|
|
||||||
...category,
|
|
||||||
subcategories: (category.subcategories?.docs ?? []).map((doc) => ({
|
|
||||||
...(doc as Category),
|
|
||||||
subcategories: undefined,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<SearchFilters data={formattedData} />
|
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||||
|
<Suspense fallback={<SearchFiltersSkeleton />}>
|
||||||
|
<SearchFilters />
|
||||||
|
</Suspense>
|
||||||
|
</HydrationBoundary>
|
||||||
|
|
||||||
<div className="flex-1 bg-[#F4F4F0]">{children}</div>
|
<div className="flex-1 bg-[#F4F4F0]">{children}</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
import { CustomCategory } from '../type';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
import { useTRPC } from '@/trpc/client';
|
||||||
import { useRouter } from 'next/navigation';
|
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
||||||
|
|
||||||
interface CategoriesSidebarProps {
|
interface CategoriesSidebarProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
data: CustomCategory[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CategoriesSidebar = ({
|
const CategoriesSidebar = ({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
data,
|
|
||||||
}: CategoriesSidebarProps) => {
|
}: CategoriesSidebarProps) => {
|
||||||
|
const trpc = useTRPC();
|
||||||
|
const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [parentCategories, setParentCategories] = useState<
|
const [parentCategories, setParentCategories] = useState<
|
||||||
CustomCategory[] | null
|
CategoriesGetManyOutput | null
|
||||||
>(null);
|
>(null);
|
||||||
const [selectedCategory, setSelectedCategory] =
|
const [selectedCategory, setSelectedCategory] =
|
||||||
useState<CustomCategory | null>(null);
|
useState<CategoriesGetManyOutput[1] | null>(null);
|
||||||
|
|
||||||
// If we have parent categories, show those, otherwise show root categories
|
// If we have parent categories, show those, otherwise show root categories
|
||||||
const currentCategories = parentCategories ?? data ?? [];
|
const currentCategories = parentCategories ?? data ?? [];
|
||||||
@@ -37,9 +41,9 @@ const CategoriesSidebar = ({
|
|||||||
onOpenChange(open);
|
onOpenChange(open);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategoryClick = (category: CustomCategory) => {
|
const handleCategoryClick = (category: CategoriesGetManyOutput[1]) => {
|
||||||
if (category.subcategories && category.subcategories.length > 0) {
|
if (category.subcategories && category.subcategories.length > 0) {
|
||||||
setParentCategories(category.subcategories as CustomCategory[]);
|
setParentCategories(category.subcategories as CategoriesGetManyOutput);
|
||||||
setSelectedCategory(category);
|
setSelectedCategory(category);
|
||||||
} else {
|
} else {
|
||||||
// This is a leaf category (no subcategories)
|
// This is a leaf category (no subcategories)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import { cn } from '@/lib/utils';
|
|||||||
|
|
||||||
import CategoryDropdown from './category-dropdown';
|
import CategoryDropdown from './category-dropdown';
|
||||||
|
|
||||||
import { CustomCategory } from '../type';
|
|
||||||
import CategoriesSidebar from './categories-sidebar';
|
import CategoriesSidebar from './categories-sidebar';
|
||||||
|
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
||||||
|
|
||||||
interface CategoriesProps {
|
interface CategoriesProps {
|
||||||
data: CustomCategory[];
|
data: CategoriesGetManyOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Categories = ({ data }: CategoriesProps) => {
|
const Categories = ({ data }: CategoriesProps) => {
|
||||||
@@ -69,7 +69,6 @@ const Categories = ({ data }: CategoriesProps) => {
|
|||||||
<CategoriesSidebar
|
<CategoriesSidebar
|
||||||
open={isSidebarOpen}
|
open={isSidebarOpen}
|
||||||
onOpenChange={setIsSidebarOpen}
|
onOpenChange={setIsSidebarOpen}
|
||||||
data={data}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Hidden div to measure all items */}
|
{/* Hidden div to measure all items */}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import { cn } from '@/lib/utils';
|
|||||||
import useDropdownPosition from './use-dropdown-position';
|
import useDropdownPosition from './use-dropdown-position';
|
||||||
import SubCategoryMenu from './subcategory-menu';
|
import SubCategoryMenu from './subcategory-menu';
|
||||||
|
|
||||||
import { CustomCategory } from '../type';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
||||||
|
|
||||||
interface CategoryDropdownProps {
|
interface CategoryDropdownProps {
|
||||||
category: CustomCategory;
|
category: CategoriesGetManyOutput[1];
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
isNavigationHovered?: boolean;
|
isNavigationHovered?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { CustomCategory } from '../type';
|
'use client';
|
||||||
|
|
||||||
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useTRPC } from '@/trpc/client';
|
||||||
|
|
||||||
import Categories from './categories';
|
import Categories from './categories';
|
||||||
import SearchInput from './search-input';
|
import SearchInput from './search-input';
|
||||||
|
|
||||||
interface SearchFiltersProps {
|
const SearchFilters = () => {
|
||||||
data: CustomCategory[];
|
const trpc = useTRPC();
|
||||||
}
|
const { data } = useSuspenseQuery(trpc.categories.getMany.queryOptions());
|
||||||
|
|
||||||
const SearchFilters = ({ data }: SearchFiltersProps) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full">
|
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]">
|
||||||
<SearchInput data={data} />
|
<SearchInput />
|
||||||
|
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<Categories data={data} />
|
<Categories data={data} />
|
||||||
@@ -18,4 +22,16 @@ const SearchFilters = ({ data }: SearchFiltersProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SearchFiltersSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="px-4 lg:px-12 py-8 border-b flex flex-col gap-4 w-full bg-[#F5F5F5]">
|
||||||
|
<SearchInput disabled />
|
||||||
|
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<div className="h-11" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default SearchFilters;
|
export default SearchFilters;
|
||||||
|
|||||||
@@ -8,21 +8,17 @@ import { Button } from '@/components/ui/button';
|
|||||||
|
|
||||||
import CategoriesSidebar from './categories-sidebar';
|
import CategoriesSidebar from './categories-sidebar';
|
||||||
|
|
||||||
import { CustomCategory } from '../type';
|
|
||||||
|
|
||||||
interface SearchInputProps {
|
interface SearchInputProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
data: CustomCategory[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchInput = ({ disabled, data }: SearchInputProps) => {
|
const SearchInput = ({ disabled }: SearchInputProps) => {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<CategoriesSidebar
|
<CategoriesSidebar
|
||||||
data={data}
|
|
||||||
open={isSidebarOpen}
|
open={isSidebarOpen}
|
||||||
onOpenChange={setIsSidebarOpen}
|
onOpenChange={setIsSidebarOpen}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Category } from '@/payload-types';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { CustomCategory } from '../type';
|
import { Category } from '@/payload-types';
|
||||||
|
import { CategoriesGetManyOutput } from '@/modules/categories/type';
|
||||||
|
|
||||||
interface SubCategoryMenuProps {
|
interface SubCategoryMenuProps {
|
||||||
category: CustomCategory;
|
category: CategoriesGetManyOutput[1];
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
position: {
|
position: {
|
||||||
top: number;
|
top: number;
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Category } from "@/payload-types";
|
|
||||||
|
|
||||||
export type CustomCategory = Category & {
|
|
||||||
subcategories: Category[];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createTRPCContext } from '@/trpc/init';
|
||||||
|
import { appRouter } from '@/trpc/routers/_app';
|
||||||
|
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
||||||
|
const handler = (req: Request) =>
|
||||||
|
fetchRequestHandler({
|
||||||
|
endpoint: '/api/trpc',
|
||||||
|
req,
|
||||||
|
router: appRouter,
|
||||||
|
createContext: createTRPCContext,
|
||||||
|
});
|
||||||
|
export { handler as GET, handler as POST };
|
||||||
+18
-17
@@ -1,28 +1,29 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from 'next';
|
||||||
import { DM_Sans } from "next/font/google";
|
import { DM_Sans } from 'next/font/google';
|
||||||
import "./globals.css";
|
|
||||||
|
import { TRPCReactProvider } from '@/trpc/client';
|
||||||
|
|
||||||
|
import './globals.css';
|
||||||
|
|
||||||
const dmSans = DM_Sans({
|
const dmSans = DM_Sans({
|
||||||
subsets: ["latin"],
|
subsets: ['latin'],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: 'Create Next App',
|
||||||
description: "Generated by create next app",
|
description: 'Generated by create next app',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body className={`${dmSans.className} antialiased`}>
|
||||||
className={`${dmSans.className} antialiased`}
|
<TRPCReactProvider>{children}</TRPCReactProvider>
|
||||||
>
|
</body>
|
||||||
{children}
|
</html>
|
||||||
</body>
|
);
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { baseProcedure, createTRPCRouter } from "@/trpc/init";
|
||||||
|
import { Category } from '@/payload-types';
|
||||||
|
|
||||||
|
export const categoriesRouter = createTRPCRouter({
|
||||||
|
getMany: baseProcedure.query(async ({ ctx }) => {
|
||||||
|
|
||||||
|
const data = await ctx.db.find({
|
||||||
|
collection: 'categories',
|
||||||
|
depth: 1,
|
||||||
|
where: {
|
||||||
|
parent: {
|
||||||
|
exists: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: 'name',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedData = data.docs.map((category) => ({
|
||||||
|
...category,
|
||||||
|
subcategories: (category.subcategories?.docs ?? []).map((doc) => ({
|
||||||
|
...(doc as Category),
|
||||||
|
subcategories: undefined,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return formattedData;
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { inferRouterOutputs } from "@trpc/server";
|
||||||
|
|
||||||
|
import { AppRouter } from "@/trpc/routers/_app";
|
||||||
|
|
||||||
|
export type CategoriesGetManyOutput = inferRouterOutputs<AppRouter>["categories"]["getMany"];
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
'use client';
|
||||||
|
// ^-- to make sure we can mount the Provider from a server component
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { QueryClient } from '@tanstack/react-query';
|
||||||
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { createTRPCClient, httpBatchLink } from '@trpc/client';
|
||||||
|
import { createTRPCContext } from '@trpc/tanstack-react-query';
|
||||||
|
import superjson from 'superjson';
|
||||||
|
|
||||||
|
import { makeQueryClient } from './query-client';
|
||||||
|
import type { AppRouter } from './routers/_app';
|
||||||
|
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();
|
||||||
|
let browserQueryClient: QueryClient;
|
||||||
|
function getQueryClient() {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
// Server: always make a new query client
|
||||||
|
return makeQueryClient();
|
||||||
|
}
|
||||||
|
// Browser: make a new query client if we don't already have one
|
||||||
|
// This is very important, so we don't re-make a new client if React
|
||||||
|
// suspends during the initial render. This may not be needed if we
|
||||||
|
// have a suspense boundary BELOW the creation of the query client
|
||||||
|
if (!browserQueryClient) browserQueryClient = makeQueryClient();
|
||||||
|
return browserQueryClient;
|
||||||
|
}
|
||||||
|
function getUrl() {
|
||||||
|
const base = (() => {
|
||||||
|
if (typeof window !== 'undefined') return '';
|
||||||
|
|
||||||
|
return process.env.NEXT_PUBLIC_APP_URL;
|
||||||
|
})();
|
||||||
|
return `${base}/api/trpc`;
|
||||||
|
}
|
||||||
|
export function TRPCReactProvider(
|
||||||
|
props: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>
|
||||||
|
) {
|
||||||
|
// NOTE: Avoid useState when initializing the query client if you don't
|
||||||
|
// have a suspense boundary between this and the code that may
|
||||||
|
// suspend because React will throw away the client on the initial
|
||||||
|
// render if it suspends and there is no boundary
|
||||||
|
const queryClient = getQueryClient();
|
||||||
|
const [trpcClient] = useState(() =>
|
||||||
|
createTRPCClient<AppRouter>({
|
||||||
|
links: [
|
||||||
|
httpBatchLink({
|
||||||
|
transformer: superjson,
|
||||||
|
url: getUrl(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
|
||||||
|
{props.children}
|
||||||
|
</TRPCProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { cache } from 'react';
|
||||||
|
import { initTRPC } from '@trpc/server';
|
||||||
|
import { getPayload } from 'payload';
|
||||||
|
import config from '@payload-config';
|
||||||
|
import superjson from 'superjson';
|
||||||
|
|
||||||
|
export const createTRPCContext = cache(async () => {
|
||||||
|
/**
|
||||||
|
* @see: https://trpc.io/docs/server/context
|
||||||
|
*/
|
||||||
|
return { userId: 'user_123' };
|
||||||
|
});
|
||||||
|
// 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({
|
||||||
|
/**
|
||||||
|
* @see https://trpc.io/docs/server/data-transformers
|
||||||
|
*/
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
// Base router and procedure helpers
|
||||||
|
export const createTRPCRouter = t.router;
|
||||||
|
export const createCallerFactory = t.createCallerFactory;
|
||||||
|
export const baseProcedure = t.procedure.use(async ({ next }) => {
|
||||||
|
const payload = await getPayload({ config });
|
||||||
|
|
||||||
|
return next({ ctx: { db: payload } });
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
defaultShouldDehydrateQuery,
|
||||||
|
QueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
|
import superjson from 'superjson';
|
||||||
|
|
||||||
|
export function makeQueryClient() {
|
||||||
|
return new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: 30 * 1000,
|
||||||
|
},
|
||||||
|
dehydrate: {
|
||||||
|
serializeData: superjson.serialize,
|
||||||
|
shouldDehydrateQuery: (query) =>
|
||||||
|
defaultShouldDehydrateQuery(query) ||
|
||||||
|
query.state.status === 'pending',
|
||||||
|
},
|
||||||
|
hydrate: {
|
||||||
|
deserializeData: superjson.deserialize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { categoriesRouter } from '@/modules/categories/server/procedures';
|
||||||
|
import { createTRPCRouter } from '../init';
|
||||||
|
|
||||||
|
export const appRouter = createTRPCRouter({
|
||||||
|
categories: categoriesRouter,
|
||||||
|
});
|
||||||
|
// export type definition of API
|
||||||
|
export type AppRouter = typeof appRouter;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'server-only'; // <-- ensure this file cannot be imported from the client
|
||||||
|
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
|
||||||
|
import { cache } from 'react';
|
||||||
|
import { createTRPCContext } from './init';
|
||||||
|
import { makeQueryClient } from './query-client';
|
||||||
|
import { appRouter } from './routers/_app';
|
||||||
|
// IMPORTANT: Create a stable getter for the query client that
|
||||||
|
// will return the same client during the same request.
|
||||||
|
export const getQueryClient = cache(makeQueryClient);
|
||||||
|
export const trpc = createTRPCOptionsProxy({
|
||||||
|
ctx: createTRPCContext,
|
||||||
|
router: appRouter,
|
||||||
|
queryClient: getQueryClient,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user