diff --git a/package.json b/package.json index 5e8b249..ffafa2b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", + "db:seed": "tsx ./src/server/db/seed.ts", "dev": "next dev --turbo", "preview": "next build && next start", "start": "next start", @@ -43,6 +44,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "dotenv": "^16.5.0", "drizzle-orm": "^0.41.0", "lucide-react": "^0.487.0", "next": "^15.2.3", @@ -57,6 +59,7 @@ "superjson": "^2.2.1", "svix": "^1.64.0", "tailwind-merge": "^3.1.0", + "tsx": "^4.19.3", "tw-animate-css": "^1.2.5", "use-debounce": "^10.0.4", "vaul": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f775ca..c4a18bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: cmdk: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + dotenv: + specifier: ^16.5.0 + version: 16.5.0 drizzle-orm: specifier: ^0.41.0 version: 0.41.0(gel@2.0.1)(postgres@3.4.5) @@ -119,6 +122,9 @@ importers: tailwind-merge: specifier: ^3.1.0 version: 3.1.0 + tsx: + specifier: ^4.19.3 + version: 4.19.3 tw-animate-css: specifier: ^1.2.5 version: 1.2.5 @@ -798,6 +804,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -810,6 +822,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -822,6 +840,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -834,6 +858,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -846,6 +876,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -858,6 +894,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -870,6 +912,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -882,6 +930,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -894,6 +948,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -906,6 +966,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -918,6 +984,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -930,6 +1002,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -942,6 +1020,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -954,6 +1038,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -966,6 +1056,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -978,6 +1074,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -990,6 +1092,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -1002,6 +1116,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -1014,6 +1140,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -1026,6 +1158,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -1038,6 +1176,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -1050,6 +1194,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -1062,6 +1212,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@floating-ui/core@1.6.9': resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} @@ -2262,6 +2418,10 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dotenv@16.5.0: + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} + drizzle-kit@0.30.6: resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==} hasBin: true @@ -2424,6 +2584,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3538,6 +3703,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + tw-animate-css@1.2.5: resolution: {integrity: sha512-ABzjfgVo+fDbhRREGL4KQZUqqdPgvc5zVrLyeW9/6mVqvaDepXc7EvedA+pYmMnIOsUAQMwcWzNvom26J2qYvQ==} @@ -4584,138 +4754,213 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true + '@esbuild/aix-ppc64@0.25.2': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true '@esbuild/android-arm64@0.19.12': optional: true + '@esbuild/android-arm64@0.25.2': + optional: true + '@esbuild/android-arm@0.18.20': optional: true '@esbuild/android-arm@0.19.12': optional: true + '@esbuild/android-arm@0.25.2': + optional: true + '@esbuild/android-x64@0.18.20': optional: true '@esbuild/android-x64@0.19.12': optional: true + '@esbuild/android-x64@0.25.2': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true '@esbuild/darwin-arm64@0.19.12': optional: true + '@esbuild/darwin-arm64@0.25.2': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true '@esbuild/darwin-x64@0.19.12': optional: true + '@esbuild/darwin-x64@0.25.2': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true '@esbuild/freebsd-arm64@0.19.12': optional: true + '@esbuild/freebsd-arm64@0.25.2': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true '@esbuild/freebsd-x64@0.19.12': optional: true + '@esbuild/freebsd-x64@0.25.2': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true '@esbuild/linux-arm64@0.19.12': optional: true + '@esbuild/linux-arm64@0.25.2': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true '@esbuild/linux-arm@0.19.12': optional: true + '@esbuild/linux-arm@0.25.2': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true '@esbuild/linux-ia32@0.19.12': optional: true + '@esbuild/linux-ia32@0.25.2': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true '@esbuild/linux-loong64@0.19.12': optional: true + '@esbuild/linux-loong64@0.25.2': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true '@esbuild/linux-mips64el@0.19.12': optional: true + '@esbuild/linux-mips64el@0.25.2': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true '@esbuild/linux-ppc64@0.19.12': optional: true + '@esbuild/linux-ppc64@0.25.2': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true '@esbuild/linux-riscv64@0.19.12': optional: true + '@esbuild/linux-riscv64@0.25.2': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true '@esbuild/linux-s390x@0.19.12': optional: true + '@esbuild/linux-s390x@0.25.2': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true '@esbuild/linux-x64@0.19.12': optional: true + '@esbuild/linux-x64@0.25.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.2': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true '@esbuild/netbsd-x64@0.19.12': optional: true + '@esbuild/netbsd-x64@0.25.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.2': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true '@esbuild/openbsd-x64@0.19.12': optional: true + '@esbuild/openbsd-x64@0.25.2': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true '@esbuild/sunos-x64@0.19.12': optional: true + '@esbuild/sunos-x64@0.25.2': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true '@esbuild/win32-arm64@0.19.12': optional: true + '@esbuild/win32-arm64@0.25.2': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true '@esbuild/win32-ia32@0.19.12': optional: true + '@esbuild/win32-ia32@0.25.2': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true '@esbuild/win32-x64@0.19.12': optional: true + '@esbuild/win32-x64@0.25.2': + optional: true + '@floating-ui/core@1.6.9': dependencies: '@floating-ui/utils': 0.2.9 @@ -5864,6 +6109,8 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + dotenv@16.5.0: {} + drizzle-kit@0.30.6: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -6037,6 +6284,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + esbuild@0.25.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 + escalade@3.2.0: {} eslint-scope@5.1.1: @@ -7173,6 +7448,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.19.3: + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + tw-animate-css@1.2.5: {} type-fest@0.16.0: {} diff --git a/src/app/(router)/add/page.tsx b/src/app/(router)/add/page.tsx index 3fd3421..2995d59 100644 --- a/src/app/(router)/add/page.tsx +++ b/src/app/(router)/add/page.tsx @@ -8,7 +8,6 @@ import React from "react"; export default async function Page() { const user = await currentUser(); - if (user) void api.friend.getAll.prefetch(); const sessionUser = await api.user.getSessionUser(); return ( diff --git a/src/app/(router)/group/page.tsx b/src/app/(router)/group/page.tsx index 906abe0..1c764da 100644 --- a/src/app/(router)/group/page.tsx +++ b/src/app/(router)/group/page.tsx @@ -7,7 +7,9 @@ import Link from "next/link"; import GroupCard from "@/app/_components/group/group-card"; export default async function Page() { - const groups = await api.group.getAll(); + const groups = await api.group.getAll({ + withMembers: true, + }); return ( <>
diff --git a/src/app/(router)/page.tsx b/src/app/(router)/page.tsx index 24f18a1..4d29abb 100644 --- a/src/app/(router)/page.tsx +++ b/src/app/(router)/page.tsx @@ -5,6 +5,10 @@ import Section from "@/components/section"; import { ModeToggle } from "@/components/mode-toggle"; import { UserButton } from "@clerk/nextjs"; import { Icons } from "@/components/icons"; +import { Card, CardHeader, CardTitle } from "@/components/ui/card"; +import { getAmount } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; export default async function Home() { return ( @@ -26,7 +30,35 @@ export default async function Home() {
-
+ +
+ + + +

+ Your Balance{" "} + + (You Owe) + +

+ + {getAmount(812.47)} + +
+ + Add + + +
+
+
+
+
+
+
); } diff --git a/src/app/_components/expense/add-participants.tsx b/src/app/_components/expense/add-participants.tsx new file mode 100644 index 0000000..8ada839 --- /dev/null +++ b/src/app/_components/expense/add-participants.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { Combobox } from "@/components/combobox"; +import { api } from "@/trpc/react"; +import Avatar from "@/components/avatar"; +import type { User } from "@/server/db/schema"; +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command"; +import { useDebounce } from "use-debounce"; +import { Icons } from "@/components/icons"; +import { useExpenseStore } from "@/lib/store/expense-store"; +export default function AddParticipants({ + excludedIds, +}: { + initialValue?: string; + className?: string; + excludedIds: Array; +}) { + const [open, setOpen] = React.useState(false); + const [value, setValue] = React.useState(""); + const [search] = useDebounce(value, 1000); + + const { data } = api.expense.searchParticipants.useQuery({ + search, + excludedIds, + }); + + const addParticipants = useExpenseStore((state) => state.addParticipants); + const addGroupBadges = useExpenseStore((state) => state.addGroupBadges); + + const select = (users: Array) => { + addParticipants(users); + setOpen(false); + }; + return ( + <> +
setOpen(true)} + > + Add Friends or Groups +
+ + + + No results found. + + {data?.groups?.length ? ( + + {data?.groups.map(({ group }) => ( + { + if (group.members) select(group.members.map((m) => m.user)); + addGroupBadges([group]); + }} + > + + {group.name} + + ))} + + ) : null} + + {data?.friends?.length ? ( + + {data?.friends.map(({ user, id }) => ( + { + select([user]); + }} + > + + {user.name} + + ))} + + ) : null} + + + + ); +} diff --git a/src/app/_components/expense/expense-form.tsx b/src/app/_components/expense/expense-form.tsx index 6e52512..1cc03a1 100644 --- a/src/app/_components/expense/expense-form.tsx +++ b/src/app/_components/expense/expense-form.tsx @@ -45,7 +45,7 @@ function ExpenseForm({ }); const participants = useExpenseStore((state) => state.participants); - const addParticipant = useExpenseStore((state) => state.addParticipant); + const addParticipants = useExpenseStore((state) => state.addParticipants); const setAmount = useExpenseStore((state) => state.setAmount); const splitType = useExpenseStore((state) => state.splitType); const setPayments = useExpenseStore((state) => state.setPayments); @@ -59,14 +59,14 @@ function ExpenseForm({ onSuccess(expense) { form.reset(); resetExpenseStore(); - addParticipant(sessionUser); + addParticipants([sessionUser]); setPayments([{ amount: amount, userId: sessionUser.id }]); toast.message("Expense Created", { position: "bottom-center", action: { children: "test", onClick: () => { - router.push(`/expense/${expense.id}`); + router.push(`/expense`); }, label: "View Expense", }, @@ -93,7 +93,7 @@ function ExpenseForm({ } React.useEffect(() => { - addParticipant(sessionUser); + addParticipants([sessionUser]); setPayments([{ amount, userId: sessionUser.id }]); }, []); diff --git a/src/app/_components/expense/expense-participants.tsx b/src/app/_components/expense/expense-participants.tsx index 3d4352a..49d4d3d 100644 --- a/src/app/_components/expense/expense-participants.tsx +++ b/src/app/_components/expense/expense-participants.tsx @@ -8,7 +8,7 @@ import { api } from "@/trpc/react"; import { Button } from "@/components/ui/button"; import { XIcon } from "lucide-react"; import type { User } from "@/server/db/schema"; -import AddParticipants from "./select-participants"; +import AddParticipants from "./add-participants"; export const UserBadge = ({ user, @@ -33,18 +33,44 @@ export default function ExpenseParticipants({ sessionUserId: string; }) { const participants = useExpenseStore((state) => state.participants); - const addParticipant = useExpenseStore((state) => state.addParticipant); const removeParticipant = useExpenseStore((state) => state.removeParticipant); - const excludeIds = participants.map((user) => user.id!); - + const groupsBadges = useExpenseStore((state) => state.groupBadges); + const removeGroupBadge = useExpenseStore((state) => state.removeGroupBadge); + const excludeIds = [ + ...participants.map((user) => user.id!), + ...groupsBadges.map((g) => g.id), + ]; return (
- {participants.map((user) => ( -
    - +
      + {groupsBadges.map((group) => ( + + + + ))} +
    +
      + {participants.map((user) => ( + {sessionUserId !== user.id && ( )} -
    - ))} - { - // addParticipant( - // friends.find((friend) => friend.user.id === userId)!.user - // ); - }} - /> + ))} +
+
); } diff --git a/src/app/_components/expense/select-participants.tsx b/src/app/_components/expense/select-participants.tsx deleted file mode 100644 index a6813c2..0000000 --- a/src/app/_components/expense/select-participants.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { Combobox } from "@/components/combobox"; -import { api } from "@/trpc/react"; -import Avatar from "@/components/avatar"; -import type { User } from "@/server/db/schema"; - -export default function AddParticipants({ - onAdd, - initialValue, - className, - excludeIds, -}: { - onAdd: (users: Array) => void; - initialValue?: string; - className?: string; - excludeIds?: Array; -}) { - const [friends] = api.friend.getAll.useSuspenseQuery(); - - const friendsData = friends - .filter((friend) => !excludeIds?.includes(friend.user.id)) - .map(({ user }) => ({ - label: user.name!, - value: user.id, - children: , - })); - - return ( - // Custom Combobox needed mby go with command dialog - onAdd([])} - initialValue={initialValue} - messageUi={{ - select: "Add Friends or Groups", - empty: "No friends or groups found", - placeholder: "Search friends or groups", - }} - /> - ); -} diff --git a/src/app/_components/group/add-to-group-drawer.tsx b/src/app/_components/group/add-to-group-drawer.tsx index de26afd..f7d781a 100644 --- a/src/app/_components/group/add-to-group-drawer.tsx +++ b/src/app/_components/group/add-to-group-drawer.tsx @@ -46,9 +46,11 @@ const SearchResultCard = ({ export default function AddToGroupDrawer({ groupId, className, + excludedIds, }: { groupId: string; className?: string; + excludedIds: Array; }) { const utils = api.useUtils(); const [open, setOpen] = React.useState(false); @@ -58,6 +60,7 @@ export default function AddToGroupDrawer({ const { data: searchResult, isFetching } = api.user.search.useQuery( { search: searchValue, + excludedIds, }, { enabled: !!searchValue, diff --git a/src/app/_components/group/group-card.tsx b/src/app/_components/group/group-card.tsx index 355873c..c6eb4f1 100644 --- a/src/app/_components/group/group-card.tsx +++ b/src/app/_components/group/group-card.tsx @@ -1,12 +1,64 @@ -import { Card, CardHeader, CardTitle } from "@/components/ui/card"; +import Avatar from "@/components/avatar"; +import { Icons } from "@/components/icons"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import type { Group } from "@/server/db/schema"; +import type { User } from "@clerk/nextjs/server"; +import Link from "next/link"; import React from "react"; function GroupCard({ group }: { group: Group }) { return ( - - {group.name} + +
+
+ + + {group.name} + +
    + {group?.members?.slice(0, 4)?.map(({ user }, idx) => ( +
  • + +
  • + ))} + {Number(group?.members?.length) > 4 && ( +
  • + +{Number(group?.members?.length) - 4} +
  • + )} +
+
+ +

{group.description}

+
+
+ + Add + +
); diff --git a/src/app/_components/group/group-page.tsx b/src/app/_components/group/group-page.tsx index b1c6f20..47f1b7f 100644 --- a/src/app/_components/group/group-page.tsx +++ b/src/app/_components/group/group-page.tsx @@ -26,7 +26,11 @@ function GroupPage({ groupId }: { groupId: string }) {

Members ({group.members.length})

- + m.user.id)} + />
    {group.members.map((member) => ( diff --git a/src/lib/store/expense-store.ts b/src/lib/store/expense-store.ts index 178d394..9b01ad6 100644 --- a/src/lib/store/expense-store.ts +++ b/src/lib/store/expense-store.ts @@ -1,8 +1,7 @@ "use client"; import { create } from "zustand"; import { calculateSplits, type Payment } from "@/lib/utils/expense"; -import type { Session } from "next-auth"; -import type { PublicUser } from "next-auth"; +import type { Group, User } from "@/server/db/schema"; export enum SplitTypeEnum { Fixed = "fixed", @@ -14,18 +13,21 @@ export const splitTypeKeys = Object.keys(SplitTypeEnum); interface ExpenseStore { amount: number; - session: Session | null; - participants: PublicUser[]; + participants: User[]; + groupBadges: Group[]; + friendTarget?: string; splitType: SplitType; payments: Payment[]; splits: Record; setAmount: (amount: number) => void; - setSession: (session: Session) => void; - addParticipant: (user: PublicUser) => void; + addGroupBadges: (groupIds: Group[]) => void; + removeGroupBadge: (groupIds: string[]) => void; + + addParticipants: (user: User[]) => void; removeParticipant: (userId: string) => void; setFriendTarget: (id?: string) => void; @@ -43,9 +45,11 @@ const defaultValues: Pick< | "splitType" | "payments" | "splits" + | "groupBadges" > = { amount: 0, participants: [], + groupBadges: [], friendTarget: undefined, splitType: "Equal", payments: [], @@ -54,25 +58,14 @@ const defaultValues: Pick< export const useExpenseStore = create((set, get) => ({ ...defaultValues, - session: null, setAmount: (amount) => set({ amount }), - setSession: (session) => - set( - { - session, - participants: [session.user], - payments: [{ amount: get().amount, userId: session.user.id }], - }, - false - ), - - addParticipant: (newUser) => { + addParticipants: (newUsers) => { const { participants } = get(); - if (!participants.find((user) => user.id === newUser.id)) { - set({ participants: [...participants, newUser] }); - } + const uniqueUsers = newUsers.filter( + (user) => !participants.find((u) => u.id === user.id) + ); + set({ participants: [...participants, ...uniqueUsers] }); }, - removeParticipant: (userId) => { const filtered = get().participants.filter((user) => user.id !== userId); set({ participants: filtered }); @@ -92,5 +85,17 @@ export const useExpenseStore = create((set, get) => ({ set({ splits }); }, + addGroupBadges: (newGroups) => { + const { groupBadges } = get(); + const uniqueGroups = newGroups.filter( + (group) => !groupBadges.find((g) => g.id === group.id) + ); + set({ groupBadges: [...groupBadges, ...uniqueGroups] }); + }, + removeGroupBadge: (groupIds) => + set({ + groupBadges: get().groupBadges.filter(({ id }) => !groupIds.includes(id)), + }), + resetExpenseStore: () => set(defaultValues), })); diff --git a/src/server/api/routers/expense.ts b/src/server/api/routers/expense.ts index ea5bf8e..24c4514 100644 --- a/src/server/api/routers/expense.ts +++ b/src/server/api/routers/expense.ts @@ -5,10 +5,13 @@ import { expenses, expenseSplits, friendships, + groupMembers, type ExpenseSplit, } from "@/server/db/schema"; import { expenseSchema, expenseSplitSchema } from "@/lib/validations/expense"; -import { and, eq, inArray, not, notInArray, or } from "drizzle-orm"; +import { and, desc, eq, inArray, not, notInArray, or } from "drizzle-orm"; +import { api } from "@/trpc/server"; +import { getAllFriends } from "./friend"; function restructureExpenseSplits(expenseSplits: Array) { const expensesMap = new Map(); @@ -42,6 +45,7 @@ export const expenseRouter = createTRPCRouter({ owedTo: true, expense: true, }, + orderBy: desc(expenseSplits.createdAt), }); const expenses = restructureExpenseSplits(splits); return expenses; @@ -50,28 +54,31 @@ export const expenseRouter = createTRPCRouter({ searchParticipants: protectedProcedure .input(z.object({ search: z.string(), excludedIds: z.array(z.string()) })) .query(async ({ ctx, input }) => { - const userId = ctx.auth.userId; - const friendResult = await ctx.db.query.friendships.findMany({ + const friends = await getAllFriends(ctx, { + pending: true, + ids: input.excludedIds, + }); + const groups = await ctx.db.query.groupMembers.findMany({ where: and( - or( - eq(friendships.userOneId, userId), - eq(friendships.userTwoId, userId) - ), - notInArray(friendships.userOneId, input.excludedIds), - notInArray(friendships.userTwoId, input.excludedIds) + eq(groupMembers.userId, ctx.auth.userId), + notInArray(groupMembers.groupId, input.excludedIds) ), with: { - userOne: true, - userTwo: true, + group: { + with: { + members: { + with: { + user: true, + }, + }, + }, + }, }, }); - const friends = - friendResult?.map(({ userOne, userTwo }) => - ctx.auth.userId === userOne.id ? userTwo : userOne - ) ?? []; + return { friends, - groups: [], + groups, }; }), diff --git a/src/server/api/routers/friend.ts b/src/server/api/routers/friend.ts index 5c16541..3d9921a 100644 --- a/src/server/api/routers/friend.ts +++ b/src/server/api/routers/friend.ts @@ -1,30 +1,60 @@ import { z } from "zod"; -import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { + createTRPCRouter, + protectedProcedure, + type TRPCContext, +} from "@/server/api/trpc"; import { friendships, users } from "@/server/db/schema"; -import { and, eq, ilike, inArray, not, or } from "drizzle-orm"; +import { and, eq, ilike, inArray, not, notInArray, or } from "drizzle-orm"; import { revalidatePath } from "next/cache"; +export async function getAllFriends( + ctx: TRPCContext, + exluded?: { pending?: boolean; ids?: Array } +) { + const friends = await ctx.db.query.friendships.findMany({ + where: and( + or( + eq(friendships.userOneId, ctx.auth.userId!), + eq(friendships.userTwoId, ctx.auth.userId!) + ), + exluded?.pending ? eq(friendships.status, "accepted") : undefined, + exluded?.ids?.length + ? or( + notInArray(friendships.userOneId, exluded.ids), + notInArray(friendships.userTwoId, exluded.ids) + ) + : undefined + ), + with: { + userOne: true, + userTwo: true, + }, + }); + return friends.map((f) => ({ + id: f.id, + status: f.status, + requestedBy: ctx.auth.userId === f.userOneId ? "me" : "them", + user: ctx.auth.userId === f.userOneId ? f.userTwo : f.userOne, + })); +} + export const friendRouter = createTRPCRouter({ // queries - getAll: protectedProcedure.query(async ({ ctx }) => { - const friends = await ctx.db.query.friendships.findMany({ - where: or( - eq(friendships.userOneId, ctx.auth.userId), - eq(friendships.userTwoId, ctx.auth.userId) - ), - with: { - userOne: true, - userTwo: true, - }, - }); - return friends.map((f) => ({ - id: f.id, - status: f.status, - requestedBy: ctx.auth.userId === f.userOneId ? "me" : "them", - user: ctx.auth.userId === f.userOneId ? f.userTwo : f.userOne, - })); - }), + getAll: protectedProcedure + .input( + z + .object({ + excludePending: z.boolean().optional(), + }) + .optional() + ) + .query(async ({ ctx, input }) => { + return await getAllFriends(ctx, { + pending: input?.excludePending ?? false, + }); + }), getPendingFriendRequests: protectedProcedure.query( async ({ ctx }) => await ctx.db.query.friendships.findMany({ diff --git a/src/server/api/routers/group.ts b/src/server/api/routers/group.ts index dc6dbad..ca1d694 100644 --- a/src/server/api/routers/group.ts +++ b/src/server/api/routers/group.ts @@ -23,15 +23,33 @@ export const groupRouter = createTRPCRouter({ return group; return undefined; }), - getAll: protectedProcedure.query(async ({ ctx }) => { - const membersShips = await ctx.db.query.groupMembers.findMany({ - where: eq(groupMembers.userId, ctx.auth.userId), - with: { - group: true, - }, - }); - return membersShips?.map(({ group }) => group!) ?? []; - }), + getAll: protectedProcedure + .input( + z + .object({ + withMembers: z.boolean().optional().default(false), + }) + .optional() + ) + .query(async ({ ctx, input }) => { + const membersShips = await ctx.db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, ctx.auth.userId), + with: { + group: input?.withMembers + ? { + with: { + members: { + with: { + user: true, + }, + }, + }, + } + : true, + }, + }); + return membersShips?.map(({ group }) => group) ?? []; + }), // mutations create: protectedProcedure diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index ce434e6..12b56c8 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -36,6 +36,8 @@ export const createTRPCContext = async (opts: { headers: Headers }) => { }; }; +export type TRPCContext = Awaited>; + /** * 2. INITIALIZATION * diff --git a/src/server/db/index.ts b/src/server/db/index.ts index 011e0af..2d359f2 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -16,3 +16,5 @@ const conn = globalForDb.conn ?? postgres(env.DATABASE_URL); if (env.NODE_ENV !== "production") globalForDb.conn = conn; export const db = drizzle(conn, { schema }); + +export type DbType = typeof db; diff --git a/src/server/db/seed.ts b/src/server/db/seed.ts new file mode 100644 index 0000000..137d836 --- /dev/null +++ b/src/server/db/seed.ts @@ -0,0 +1,25 @@ +import "dotenv/config"; +import { db } from "."; +import { users } from "./schema"; + +const usersData = Array.from({ length: 10 }, (_, i) => ({ + id: `fake-user-${i + 1}`, + name: `User ${i + 1}`, + image: `https://github.com/mr-shortman.png`, +})); + +async function seed() { + try { + await db.insert(users).values(usersData); + console.log(`Seeded users (${usersData.length})`); + } catch (e) { + console.log(e); + } +} + +(() => { + seed().then(() => { + console.log("Seeding complete"); + process.exit(0); + }); +})(); diff --git a/src/trpc/server.ts b/src/trpc/server.ts index 5f2fc58..ef64003 100644 --- a/src/trpc/server.ts +++ b/src/trpc/server.ts @@ -13,18 +13,18 @@ import { createQueryClient } from "./query-client"; * handling a tRPC call from a React Server Component. */ const createContext = cache(async () => { - const heads = new Headers(await headers()); - heads.set("x-trpc-source", "rsc"); + const heads = new Headers(await headers()); + heads.set("x-trpc-source", "rsc"); - return createTRPCContext({ - headers: heads, - }); + return createTRPCContext({ + headers: heads, + }); }); const getQueryClient = cache(createQueryClient); const caller = createCaller(createContext); export const { trpc: api, HydrateClient } = createHydrationHelpers( - caller, - getQueryClient, + caller, + getQueryClient );