From 5c09688f1aea1ddefe9f27be268b306d4498907d Mon Sep 17 00:00:00 2001 From: shrt Date: Fri, 7 Mar 2025 20:18:00 +0100 Subject: [PATCH] admin dashbaord; started member managment ui --- package.json | 4 + pnpm-lock.yaml | 430 +++++++++++++++- seed-data/fake-users.json | 502 +++++++++++++++++++ seed-data/seed.ts | 12 + src/app.config.ts | 7 + src/app/(PAGES)/artikel/[slug]/edit/page.tsx | 2 +- src/app/(PAGES)/artikel/[slug]/page.tsx | 2 +- src/app/admin/_components/admin-navbar.tsx | 15 + src/app/admin/_components/admin-sidebar.tsx | 76 +++ src/app/admin/layout.tsx | 26 + src/app/admin/page.tsx | 8 + src/app/admin/team/columns.tsx | 50 ++ src/app/admin/team/page.tsx | 22 + src/components/data-table.tsx | 80 +++ src/components/layout/app-sidebar.tsx | 3 +- src/components/layout/navbar.tsx | 10 +- src/components/ui/badge.tsx | 36 ++ src/components/ui/table.tsx | 120 +++++ src/lib/validation/has-permission.tsx | 16 - src/lib/validation/permissions.tsx | 58 +++ src/server/api/root.ts | 2 + src/server/api/routers/article.ts | 2 +- src/server/api/routers/category.ts | 2 +- src/server/api/routers/users.ts | 10 + 24 files changed, 1466 insertions(+), 29 deletions(-) create mode 100644 seed-data/fake-users.json create mode 100644 seed-data/seed.ts create mode 100644 src/app.config.ts create mode 100644 src/app/admin/_components/admin-navbar.tsx create mode 100644 src/app/admin/_components/admin-sidebar.tsx create mode 100644 src/app/admin/layout.tsx create mode 100644 src/app/admin/page.tsx create mode 100644 src/app/admin/team/columns.tsx create mode 100644 src/app/admin/team/page.tsx create mode 100644 src/components/data-table.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/table.tsx delete mode 100644 src/lib/validation/has-permission.tsx create mode 100644 src/lib/validation/permissions.tsx create mode 100644 src/server/api/routers/users.ts diff --git a/package.json b/package.json index d65125d..c5d77a6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-tooltip": "^1.1.8", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", + "@tanstack/react-table": "^8.21.2", "@tiptap/extension-color": "^2.11.5", "@tiptap/extension-image": "^2.11.5", "@tiptap/extension-list-item": "^2.11.5", @@ -67,6 +68,7 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", + "dotenv": "^16.4.7", "drizzle-kit": "^0.24.0", "eslint": "^8.57.0", "eslint-config-next": "^15.0.1", @@ -75,6 +77,8 @@ "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", + "ts-node": "^10.9.2", + "tsx": "^4.19.3", "typescript": "^5.5.3" }, "ct3aMetadata": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfb433d..440415b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@tanstack/react-query': specifier: ^5.50.0 version: 5.67.1(react@18.3.1) + '@tanstack/react-table': + specifier: ^8.21.2 + version: 8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tiptap/extension-color': specifier: ^2.11.5 version: 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))) @@ -121,7 +124,7 @@ importers: version: 3.0.2 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2))) zod: specifier: ^3.24.2 version: 3.24.2 @@ -144,6 +147,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.1.0 version: 8.26.0(eslint@8.57.1)(typescript@5.8.2) + dotenv: + specifier: ^16.4.7 + version: 16.4.7 drizzle-kit: specifier: ^0.24.0 version: 0.24.2 @@ -167,7 +173,13 @@ importers: version: 0.6.11(prettier@3.5.3) tailwindcss: specifier: ^3.4.3 - version: 3.4.17 + version: 3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.17.23)(typescript@5.8.2) + tsx: + specifier: ^4.19.3 + version: 4.19.3 typescript: specifier: ^5.5.3 version: 5.8.2 @@ -213,6 +225,10 @@ packages: resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} engines: {node: '>=6.9.0'} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -233,6 +249,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -245,6 +267,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -257,6 +285,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -269,6 +303,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -281,6 +321,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -293,6 +339,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -305,6 +357,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -317,6 +375,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -329,6 +393,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -341,6 +411,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -353,6 +429,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -365,6 +447,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -377,6 +465,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -389,6 +483,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -401,6 +501,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -413,6 +519,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -425,6 +537,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -437,6 +561,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -449,6 +585,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -461,6 +603,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -473,6 +621,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -485,6 +639,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -497,6 +657,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -675,6 +841,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@next/env@15.2.1': resolution: {integrity: sha512-JmY0qvnPuS2NCWOz2bbby3Pe0VzdAQ7XpEB6uLIHmtXNfAsAO0KLQLkuAoc42Bxbo3/jMC3dcn9cdf+piCcG2Q==} @@ -1093,6 +1262,17 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-table@8.21.2': + resolution: {integrity: sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.21.2': + resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==} + engines: {node: '>=12'} + '@tiptap/core@2.11.5': resolution: {integrity: sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==} peerDependencies: @@ -1256,6 +1436,18 @@ packages: peerDependencies: typescript: '>=5.7.2' + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -1352,6 +1544,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} @@ -1383,6 +1579,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -1542,6 +1741,9 @@ packages: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -1610,6 +1812,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -1621,6 +1827,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + drizzle-kit@0.24.2: resolution: {integrity: sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ==} hasBin: true @@ -1782,6 +1992,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2292,6 +2507,9 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -3013,12 +3231,31 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} 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 + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3116,6 +3353,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -3160,6 +3400,10 @@ packages: engines: {node: '>= 14'} hasBin: true + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3201,6 +3445,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@drizzle-team/brocli@0.10.2': {} '@emnapi/runtime@1.3.1': @@ -3221,138 +3469,213 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true + '@esbuild/aix-ppc64@0.25.0': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true '@esbuild/android-arm64@0.19.12': optional: true + '@esbuild/android-arm64@0.25.0': + optional: true + '@esbuild/android-arm@0.18.20': optional: true '@esbuild/android-arm@0.19.12': optional: true + '@esbuild/android-arm@0.25.0': + optional: true + '@esbuild/android-x64@0.18.20': optional: true '@esbuild/android-x64@0.19.12': optional: true + '@esbuild/android-x64@0.25.0': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true '@esbuild/darwin-arm64@0.19.12': optional: true + '@esbuild/darwin-arm64@0.25.0': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true '@esbuild/darwin-x64@0.19.12': optional: true + '@esbuild/darwin-x64@0.25.0': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true '@esbuild/freebsd-arm64@0.19.12': optional: true + '@esbuild/freebsd-arm64@0.25.0': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true '@esbuild/freebsd-x64@0.19.12': optional: true + '@esbuild/freebsd-x64@0.25.0': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true '@esbuild/linux-arm64@0.19.12': optional: true + '@esbuild/linux-arm64@0.25.0': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true '@esbuild/linux-arm@0.19.12': optional: true + '@esbuild/linux-arm@0.25.0': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true '@esbuild/linux-ia32@0.19.12': optional: true + '@esbuild/linux-ia32@0.25.0': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true '@esbuild/linux-loong64@0.19.12': optional: true + '@esbuild/linux-loong64@0.25.0': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true '@esbuild/linux-mips64el@0.19.12': optional: true + '@esbuild/linux-mips64el@0.25.0': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true '@esbuild/linux-ppc64@0.19.12': optional: true + '@esbuild/linux-ppc64@0.25.0': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true '@esbuild/linux-riscv64@0.19.12': optional: true + '@esbuild/linux-riscv64@0.25.0': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true '@esbuild/linux-s390x@0.19.12': optional: true + '@esbuild/linux-s390x@0.25.0': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true '@esbuild/linux-x64@0.19.12': optional: true + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true '@esbuild/netbsd-x64@0.19.12': optional: true + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true '@esbuild/openbsd-x64@0.19.12': optional: true + '@esbuild/openbsd-x64@0.25.0': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true '@esbuild/sunos-x64@0.19.12': optional: true + '@esbuild/sunos-x64@0.25.0': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true '@esbuild/win32-arm64@0.19.12': optional: true + '@esbuild/win32-arm64@0.25.0': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true '@esbuild/win32-ia32@0.19.12': optional: true + '@esbuild/win32-ia32@0.25.0': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true '@esbuild/win32-x64@0.19.12': optional: true + '@esbuild/win32-x64@0.25.0': + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -3511,6 +3834,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@next/env@15.2.1': {} '@next/eslint-plugin-next@15.2.1': @@ -3862,6 +4190,14 @@ snapshots: '@tanstack/query-core': 5.67.1 react: 18.3.1 + '@tanstack/react-table@8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/table-core': 8.21.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/table-core@8.21.2': {} + '@tiptap/core@2.11.5(@tiptap/pm@2.11.5)': dependencies: '@tiptap/pm': 2.11.5 @@ -4043,6 +4379,14 @@ snapshots: dependencies: typescript: 5.8.2 + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@types/cookie@0.6.0': {} '@types/eslint@8.56.12': @@ -4165,6 +4509,10 @@ snapshots: dependencies: acorn: 8.14.1 + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.1 + acorn@8.14.1: {} ajv@6.12.6: @@ -4191,6 +4539,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@4.1.3: {} + arg@5.0.2: {} argparse@2.0.1: {} @@ -4376,6 +4726,8 @@ snapshots: dependencies: is-what: 4.1.16 + create-require@1.1.1: {} + crelt@1.0.6: {} cross-spawn@7.0.6: @@ -4437,6 +4789,8 @@ snapshots: didyoumean@1.2.2: {} + diff@4.0.2: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -4447,6 +4801,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.4.7: {} + drizzle-kit@0.24.2: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -4637,6 +4993,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + escape-string-regexp@4.0.0: {} eslint-config-next@15.2.1(eslint@8.57.1)(typescript@5.8.2): @@ -5247,6 +5631,8 @@ snapshots: dependencies: react: 18.3.1 + make-error@1.3.6: {} + markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -5441,12 +5827,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.3 - postcss-load-config@4.0.2(postcss@8.5.3): + postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)): dependencies: lilconfig: 3.1.3 yaml: 2.7.0 optionalDependencies: postcss: 8.5.3 + ts-node: 10.9.2(@types/node@20.17.23)(typescript@5.8.2) postcss-nested@6.2.0(postcss@8.5.3): dependencies: @@ -5955,11 +6342,11 @@ snapshots: tailwind-merge@3.0.2: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.17): + tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2))): dependencies: - tailwindcss: 3.4.17 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)) - tailwindcss@3.4.17: + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -5978,7 +6365,7 @@ snapshots: postcss: 8.5.3 postcss-import: 15.1.0(postcss@8.5.3) postcss-js: 4.0.1(postcss@8.5.3) - postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)) postcss-nested: 6.2.0(postcss@8.5.3) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -6017,6 +6404,24 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.23 + acorn: 8.14.1 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -6026,6 +6431,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.19.3: + dependencies: + esbuild: 0.25.0 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -6122,6 +6534,8 @@ snapshots: util-deprecate@1.0.2: {} + v8-compile-cache-lib@3.0.1: {} + w3c-keyname@2.2.8: {} which-boxed-primitive@1.1.1: @@ -6186,6 +6600,8 @@ snapshots: yaml@2.7.0: {} + yn@3.1.1: {} + yocto-queue@0.1.0: {} zod@3.24.2: {} diff --git a/seed-data/fake-users.json b/seed-data/fake-users.json new file mode 100644 index 0000000..4d4e496 --- /dev/null +++ b/seed-data/fake-users.json @@ -0,0 +1,502 @@ +[ + { + "name": "Léandre", + "email": "amant0@hc360.com", + "image": "https://robohash.org/autnihilquasi.png?size=50x50&set=set1" + }, + { + "name": "Céline", + "email": "aedsall1@zdnet.com", + "image": "https://robohash.org/temporibusfacerevoluptatem.png?size=50x50&set=set1" + }, + { + "name": "Vénus", + "email": "bcovington2@dion.ne.jp", + "image": "https://robohash.org/architectoconsecteturconsequatur.png?size=50x50&set=set1" + }, + { + "name": "Loïca", + "email": "msteinhammer3@columbia.edu", + "image": "https://robohash.org/pariaturullamaut.png?size=50x50&set=set1" + }, + { + "name": "Táng", + "email": "fmichurin4@is.gd", + "image": "https://robohash.org/eaquevoluptasfugit.png?size=50x50&set=set1" + }, + { + "name": "Andréa", + "email": "kbygrave5@pen.io", + "image": "https://robohash.org/eligendinatusa.png?size=50x50&set=set1" + }, + { + "name": "Léandre", + "email": "bbargery6@ocn.ne.jp", + "image": "https://robohash.org/estidquod.png?size=50x50&set=set1" + }, + { + "name": "Chloé", + "email": "mharnor7@ameblo.jp", + "image": "https://robohash.org/facilisducimuset.png?size=50x50&set=set1" + }, + { + "name": "Cunégonde", + "email": "lgiovanardi8@liveinternet.ru", + "image": "https://robohash.org/perspiciatisaliquidsit.png?size=50x50&set=set1" + }, + { + "name": "Dafnée", + "email": "ebleiman9@reuters.com", + "image": "https://robohash.org/quiquiaquaerat.png?size=50x50&set=set1" + }, + { + "name": "Laurène", + "email": "ibostona@tuttocitta.it", + "image": "https://robohash.org/quossolutaenim.png?size=50x50&set=set1" + }, + { + "name": "Pénélope", + "email": "kpedrollob@nyu.edu", + "image": "https://robohash.org/molestiasnullaomnis.png?size=50x50&set=set1" + }, + { + "name": "Laïla", + "email": "noransc@symantec.com", + "image": "https://robohash.org/rerumharumut.png?size=50x50&set=set1" + }, + { + "name": "Yáo", + "email": "hsived@oaic.gov.au", + "image": "https://robohash.org/etiuremolestias.png?size=50x50&set=set1" + }, + { + "name": "Judicaël", + "email": "nfockee@wordpress.org", + "image": "https://robohash.org/quisquamestaccusamus.png?size=50x50&set=set1" + }, + { + "name": "Léane", + "email": "nalwenf@hexun.com", + "image": "https://robohash.org/quasimagnamab.png?size=50x50&set=set1" + }, + { + "name": "Publicité", + "email": "bgeorgeg@howstuffworks.com", + "image": "https://robohash.org/repellendusquiculpa.png?size=50x50&set=set1" + }, + { + "name": "Kuí", + "email": "celdonh@blinklist.com", + "image": "https://robohash.org/doloremundequo.png?size=50x50&set=set1" + }, + { + "name": "Clémence", + "email": "mcommussoi@scribd.com", + "image": "https://robohash.org/quidoloresat.png?size=50x50&set=set1" + }, + { + "name": "Agnès", + "email": "nzambonj@nymag.com", + "image": "https://robohash.org/etnemoconsequatur.png?size=50x50&set=set1" + }, + { + "name": "Nadège", + "email": "mwhithamk@bbb.org", + "image": "https://robohash.org/accusamushicest.png?size=50x50&set=set1" + }, + { + "name": "Miléna", + "email": "glisettl@merriam-webster.com", + "image": "https://robohash.org/veniamplaceatadipisci.png?size=50x50&set=set1" + }, + { + "name": "Séréna", + "email": "wpoundsm@yellowpages.com", + "image": "https://robohash.org/nihiladea.png?size=50x50&set=set1" + }, + { + "name": "Chloé", + "email": "sescalen@imgur.com", + "image": "https://robohash.org/optiovoluptatibusaut.png?size=50x50&set=set1" + }, + { + "name": "Maëlyss", + "email": "nyoskowitzo@omniture.com", + "image": "https://robohash.org/expeditanobismaxime.png?size=50x50&set=set1" + }, + { + "name": "Cunégonde", + "email": "awellenp@de.vu", + "image": "https://robohash.org/commodiutest.png?size=50x50&set=set1" + }, + { + "name": "Valérie", + "email": "zduchatelq@imageshack.us", + "image": "https://robohash.org/omnisdoloremtemporibus.png?size=50x50&set=set1" + }, + { + "name": "Aloïs", + "email": "foxburyr@github.io", + "image": "https://robohash.org/cumnemoquia.png?size=50x50&set=set1" + }, + { + "name": "Eliès", + "email": "bmactimpanys@moonfruit.com", + "image": "https://robohash.org/eosvitaeveritatis.png?size=50x50&set=set1" + }, + { + "name": "Maëline", + "email": "abaget@gnu.org", + "image": "https://robohash.org/magnivoluptatibussoluta.png?size=50x50&set=set1" + }, + { + "name": "Pål", + "email": "ssunocku@discuz.net", + "image": "https://robohash.org/accusamusquaedelectus.png?size=50x50&set=set1" + }, + { + "name": "Irène", + "email": "reneasv@sphinn.com", + "image": "https://robohash.org/assumendahicperferendis.png?size=50x50&set=set1" + }, + { + "name": "Renée", + "email": "dmclarenw@skyrock.com", + "image": "https://robohash.org/voluptatemrecusandaeest.png?size=50x50&set=set1" + }, + { + "name": "Léonore", + "email": "breimsx@tmall.com", + "image": "https://robohash.org/natusporropariatur.png?size=50x50&set=set1" + }, + { + "name": "Táng", + "email": "tcolty@usnews.com", + "image": "https://robohash.org/autenimlabore.png?size=50x50&set=set1" + }, + { + "name": "Ruì", + "email": "bblazaz@mac.com", + "image": "https://robohash.org/quosiuremaiores.png?size=50x50&set=set1" + }, + { + "name": "Anaëlle", + "email": "vsimon10@npr.org", + "image": "https://robohash.org/velmolestiasullam.png?size=50x50&set=set1" + }, + { + "name": "Clémence", + "email": "bedmonds11@economist.com", + "image": "https://robohash.org/consequaturimpeditmollitia.png?size=50x50&set=set1" + }, + { + "name": "Eloïse", + "email": "jmaster12@guardian.co.uk", + "image": "https://robohash.org/autdolormolestiae.png?size=50x50&set=set1" + }, + { + "name": "Adélaïde", + "email": "mchazier13@slate.com", + "image": "https://robohash.org/laborereprehenderitsequi.png?size=50x50&set=set1" + }, + { + "name": "Séverine", + "email": "dgisborne14@rediff.com", + "image": "https://robohash.org/quiavoluptatemet.png?size=50x50&set=set1" + }, + { + "name": "Publicité", + "email": "gjobson15@vk.com", + "image": "https://robohash.org/eteaet.png?size=50x50&set=set1" + }, + { + "name": "Miléna", + "email": "bskarin16@buzzfeed.com", + "image": "https://robohash.org/facerenecessitatibussuscipit.png?size=50x50&set=set1" + }, + { + "name": "Méryl", + "email": "jcurrington17@dropbox.com", + "image": "https://robohash.org/molestiasasperioresmollitia.png?size=50x50&set=set1" + }, + { + "name": "Dù", + "email": "aandroli18@infoseek.co.jp", + "image": "https://robohash.org/namillumquo.png?size=50x50&set=set1" + }, + { + "name": "Liè", + "email": "hcornwall19@mozilla.com", + "image": "https://robohash.org/dignissimosconsequaturanimi.png?size=50x50&set=set1" + }, + { + "name": "Estève", + "email": "mshoutt1a@amazonaws.com", + "image": "https://robohash.org/velitsolutatotam.png?size=50x50&set=set1" + }, + { + "name": "Kuí", + "email": "cdrysdall1b@51.la", + "image": "https://robohash.org/utquodeos.png?size=50x50&set=set1" + }, + { + "name": "Rébecca", + "email": "ldavall1c@vimeo.com", + "image": "https://robohash.org/delenitiquasiid.png?size=50x50&set=set1" + }, + { + "name": "Eléonore", + "email": "brickhuss1d@ustream.tv", + "image": "https://robohash.org/consecteturcommodiiure.png?size=50x50&set=set1" + }, + { + "name": "Crééz", + "email": "jaggott1e@is.gd", + "image": "https://robohash.org/minimanisidelectus.png?size=50x50&set=set1" + }, + { + "name": "Célia", + "email": "kspuffard1f@ca.gov", + "image": "https://robohash.org/ipsumaliquidcumque.png?size=50x50&set=set1" + }, + { + "name": "Marie-thérèse", + "email": "cmaharg1g@psu.edu", + "image": "https://robohash.org/liberoquaerataut.png?size=50x50&set=set1" + }, + { + "name": "Publicité", + "email": "ineeds1h@example.com", + "image": "https://robohash.org/aliquidrepudiandaeeum.png?size=50x50&set=set1" + }, + { + "name": "Maïlis", + "email": "lalliston1i@miibeian.gov.cn", + "image": "https://robohash.org/estoptioquia.png?size=50x50&set=set1" + }, + { + "name": "Bérengère", + "email": "tcullingford1j@squidoo.com", + "image": "https://robohash.org/saepeetvel.png?size=50x50&set=set1" + }, + { + "name": "Styrbjörn", + "email": "skalewe1k@archive.org", + "image": "https://robohash.org/sednequevoluptatem.png?size=50x50&set=set1" + }, + { + "name": "Bérénice", + "email": "kgoudard1l@dell.com", + "image": "https://robohash.org/necessitatibusvelrerum.png?size=50x50&set=set1" + }, + { + "name": "Nuó", + "email": "lpenticost1m@angelfire.com", + "image": "https://robohash.org/autemsitsaepe.png?size=50x50&set=set1" + }, + { + "name": "Cloé", + "email": "idomnin1n@bbc.co.uk", + "image": "https://robohash.org/utetut.png?size=50x50&set=set1" + }, + { + "name": "Irène", + "email": "ddutchburn1o@npr.org", + "image": "https://robohash.org/etculpased.png?size=50x50&set=set1" + }, + { + "name": "Stéphanie", + "email": "achafney1p@dagondesign.com", + "image": "https://robohash.org/laudantiumilloconsequatur.png?size=50x50&set=set1" + }, + { + "name": "Eloïse", + "email": "gholttom1q@salon.com", + "image": "https://robohash.org/voluptatemestaut.png?size=50x50&set=set1" + }, + { + "name": "Märta", + "email": "agierek1r@rediff.com", + "image": "https://robohash.org/sintcommodiid.png?size=50x50&set=set1" + }, + { + "name": "Mélinda", + "email": "dkeoghan1s@java.com", + "image": "https://robohash.org/erroripsumdoloribus.png?size=50x50&set=set1" + }, + { + "name": "Adélie", + "email": "bsenior1t@ca.gov", + "image": "https://robohash.org/possimusistelabore.png?size=50x50&set=set1" + }, + { + "name": "Célia", + "email": "lstenner1u@drupal.org", + "image": "https://robohash.org/nemonihilquas.png?size=50x50&set=set1" + }, + { + "name": "Géraldine", + "email": "mstephens1v@csmonitor.com", + "image": "https://robohash.org/providentillumlibero.png?size=50x50&set=set1" + }, + { + "name": "Inès", + "email": "mskacel1w@nationalgeographic.com", + "image": "https://robohash.org/quiutvoluptatem.png?size=50x50&set=set1" + }, + { + "name": "Táng", + "email": "swilber1x@reverbnation.com", + "image": "https://robohash.org/rerumrepellatdolor.png?size=50x50&set=set1" + }, + { + "name": "Maëlyss", + "email": "ncamings1y@rediff.com", + "image": "https://robohash.org/quamminimanon.png?size=50x50&set=set1" + }, + { + "name": "Jú", + "email": "jcard1z@instagram.com", + "image": "https://robohash.org/voluptasinciduntrerum.png?size=50x50&set=set1" + }, + { + "name": "Lauréna", + "email": "lstruttman20@msu.edu", + "image": "https://robohash.org/exercitationemquiea.png?size=50x50&set=set1" + }, + { + "name": "Nadège", + "email": "mcreber21@accuweather.com", + "image": "https://robohash.org/officiisdelenitia.png?size=50x50&set=set1" + }, + { + "name": "Aloïs", + "email": "lsquires22@va.gov", + "image": "https://robohash.org/quiquasdolorum.png?size=50x50&set=set1" + }, + { + "name": "Loïs", + "email": "btempest23@taobao.com", + "image": "https://robohash.org/quisminimaaccusantium.png?size=50x50&set=set1" + }, + { + "name": "Åke", + "email": "mmullan24@forbes.com", + "image": "https://robohash.org/molestiaerepellenduseos.png?size=50x50&set=set1" + }, + { + "name": "Mélia", + "email": "afarnhill25@163.com", + "image": "https://robohash.org/quasdoloresquam.png?size=50x50&set=set1" + }, + { + "name": "Maëlyss", + "email": "wpache26@sakura.ne.jp", + "image": "https://robohash.org/doloresintrecusandae.png?size=50x50&set=set1" + }, + { + "name": "Loïca", + "email": "tsoal27@taobao.com", + "image": "https://robohash.org/accusamusilloaut.png?size=50x50&set=set1" + }, + { + "name": "Léana", + "email": "gparkisson28@jiathis.com", + "image": "https://robohash.org/omnisarchitectoaut.png?size=50x50&set=set1" + }, + { + "name": "Anaël", + "email": "nslegg29@canalblog.com", + "image": "https://robohash.org/quiaidadipisci.png?size=50x50&set=set1" + }, + { + "name": "Marie-josée", + "email": "salford2a@hubpages.com", + "image": "https://robohash.org/nihilestillo.png?size=50x50&set=set1" + }, + { + "name": "Maëlle", + "email": "lbaggs2b@deviantart.com", + "image": "https://robohash.org/blanditiisetvoluptate.png?size=50x50&set=set1" + }, + { + "name": "Publicité", + "email": "vpurdy2c@bravesites.com", + "image": "https://robohash.org/eumenimipsa.png?size=50x50&set=set1" + }, + { + "name": "Lén", + "email": "cstraun2d@youtube.com", + "image": "https://robohash.org/temporaquisofficia.png?size=50x50&set=set1" + }, + { + "name": "Maëline", + "email": "rcorney2e@blog.com", + "image": "https://robohash.org/nisimodimagnam.png?size=50x50&set=set1" + }, + { + "name": "Lucrèce", + "email": "florey2f@weather.com", + "image": "https://robohash.org/nequepariaturconsequatur.png?size=50x50&set=set1" + }, + { + "name": "Dorothée", + "email": "briddles2g@ucoz.ru", + "image": "https://robohash.org/voluptaserroriure.png?size=50x50&set=set1" + }, + { + "name": "Irène", + "email": "kscotsbrook2h@salon.com", + "image": "https://robohash.org/ullamutearum.png?size=50x50&set=set1" + }, + { + "name": "Håkan", + "email": "sshipp2i@cnbc.com", + "image": "https://robohash.org/etautillum.png?size=50x50&set=set1" + }, + { + "name": "Mylène", + "email": "bbanishevitz2j@biglobe.ne.jp", + "image": "https://robohash.org/consectetureosullam.png?size=50x50&set=set1" + }, + { + "name": "Styrbjörn", + "email": "mjeffries2k@wsj.com", + "image": "https://robohash.org/expeditaautemvoluptatum.png?size=50x50&set=set1" + }, + { + "name": "Sòng", + "email": "mpanks2l@tripadvisor.com", + "image": "https://robohash.org/eiussintnihil.png?size=50x50&set=set1" + }, + { + "name": "Faîtes", + "email": "nstewartson2m@themeforest.net", + "image": "https://robohash.org/errornumquamanimi.png?size=50x50&set=set1" + }, + { + "name": "Clémence", + "email": "bslany2n@naver.com", + "image": "https://robohash.org/laboriosamconsequaturdolore.png?size=50x50&set=set1" + }, + { + "name": "Maëlys", + "email": "dshubotham2o@who.int", + "image": "https://robohash.org/nobisnecessitatibusipsa.png?size=50x50&set=set1" + }, + { + "name": "Inès", + "email": "akeiling2p@ycombinator.com", + "image": "https://robohash.org/etvoluptatedeserunt.png?size=50x50&set=set1" + }, + { + "name": "Maëlla", + "email": "jfolonin2q@livejournal.com", + "image": "https://robohash.org/voluptatibusatqueut.png?size=50x50&set=set1" + }, + { + "name": "Clémentine", + "email": "cstammirs2r@a8.net", + "image": "https://robohash.org/nihilquaesint.png?size=50x50&set=set1" + } +] diff --git a/seed-data/seed.ts b/seed-data/seed.ts new file mode 100644 index 0000000..810b01d --- /dev/null +++ b/seed-data/seed.ts @@ -0,0 +1,12 @@ +import "dotenv/config"; +import { db } from "../src/server/db"; +import { users } from "../src/server/db/schema"; +import fakeUsers from "./fake-users.json"; + +async function seed() { + await db.insert(users).values(fakeUsers); +} + +seed() + .catch(console.error) + .finally(() => process.exit()); diff --git a/src/app.config.ts b/src/app.config.ts new file mode 100644 index 0000000..5692913 --- /dev/null +++ b/src/app.config.ts @@ -0,0 +1,7 @@ +export type AppConfig = { + name: string; +}; + +export const appConfig: AppConfig = { + name: "Wiki Antifa", +}; diff --git a/src/app/(PAGES)/artikel/[slug]/edit/page.tsx b/src/app/(PAGES)/artikel/[slug]/edit/page.tsx index c96e774..b1d6e40 100644 --- a/src/app/(PAGES)/artikel/[slug]/edit/page.tsx +++ b/src/app/(PAGES)/artikel/[slug]/edit/page.tsx @@ -1,4 +1,4 @@ -import { hasPermission, Role } from "@/lib/validation/has-permission"; +import { hasPermission, Role } from "@/lib/validation/permissions"; import { auth } from "@/server/auth"; import { api } from "@/trpc/server"; import { notFound } from "next/navigation"; diff --git a/src/app/(PAGES)/artikel/[slug]/page.tsx b/src/app/(PAGES)/artikel/[slug]/page.tsx index 811da7d..fc6efb1 100644 --- a/src/app/(PAGES)/artikel/[slug]/page.tsx +++ b/src/app/(PAGES)/artikel/[slug]/page.tsx @@ -1,5 +1,5 @@ import { Button } from "@/components/ui/button"; -import { hasPermission, Role } from "@/lib/validation/has-permission"; +import { hasPermission, Role } from "@/lib/validation/permissions"; import { auth } from "@/server/auth"; import { api } from "@/trpc/server"; import { Edit, Edit2Icon, Edit3, MoreVertical, Trash } from "lucide-react"; diff --git a/src/app/admin/_components/admin-navbar.tsx b/src/app/admin/_components/admin-navbar.tsx new file mode 100644 index 0000000..12da1e1 --- /dev/null +++ b/src/app/admin/_components/admin-navbar.tsx @@ -0,0 +1,15 @@ +import { Button } from "@/components/ui/button"; +import { auth } from "@/server/auth"; +import Link from "next/link"; +import React from "react"; + +async function AdminNavbar() { + const session = await auth(); + return ( +
+ Admin Dashboard +
+ ); +} + +export default AdminNavbar; diff --git a/src/app/admin/_components/admin-sidebar.tsx b/src/app/admin/_components/admin-sidebar.tsx new file mode 100644 index 0000000..d43509d --- /dev/null +++ b/src/app/admin/_components/admin-sidebar.tsx @@ -0,0 +1,76 @@ +import * as React from "react"; + +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, +} from "@/components/ui/sidebar"; +import Link from "next/link"; +import SidebarLink from "@/components/layout/sidebar-link"; +import { Button } from "@/components/ui/button"; +import { LogOutIcon } from "lucide-react"; +import { appConfig } from "@/app.config"; + +const data = { + navMain: [ + { + title: "Benutzer", + url: "/admin", + items: [ + { + title: "Team Mitglieder", + url: "/admin/team", + }, + { + title: "Alle Benutzer", + url: "/admin/benutzer", + }, + ], + }, + ], +}; + +export function AdminSidebar({ + ...props +}: React.ComponentProps) { + return ( + + + + {appConfig.name} + + + + {/* We create a SidebarGroup for each parent. */} + {data.navMain.map((item) => ( + + {item.title} + + + {item.items.map((item) => ( + + ))} + + + + ))} + + + + + + ); +} diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx new file mode 100644 index 0000000..026cab7 --- /dev/null +++ b/src/app/admin/layout.tsx @@ -0,0 +1,26 @@ +import { hasPermission, Role } from "@/lib/validation/permissions"; +import { auth } from "@/server/auth"; +import { notFound } from "next/navigation"; +import React from "react"; +import AdminNavbar from "./_components/admin-navbar"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { AdminSidebar } from "./_components/admin-sidebar"; + +async function Layout({ children }: { children: React.ReactNode }) { + const session = await auth(); + const isAdmin = session?.user + ? hasPermission(session.user.role, Role.ADMIN) + : false; + if (!isAdmin) return notFound(); + return ( + + +
+ {/* */} +
{children}
+
+
+ ); +} + +export default Layout; diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..e293d62 --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,8 @@ +import { api } from "@/trpc/server"; +import React from "react"; + +async function Page() { + return
Admin Dashboard
; +} + +export default Page; diff --git a/src/app/admin/team/columns.tsx b/src/app/admin/team/columns.tsx new file mode 100644 index 0000000..9817334 --- /dev/null +++ b/src/app/admin/team/columns.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { getUserPermissions } from "@/lib/validation/permissions"; +import { ColumnDef } from "@tanstack/react-table"; +import { Plus } from "lucide-react"; +import { User } from "next-auth"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "name", + header: "Name", + }, + { + accessorKey: "email", + header: "Email", + }, + + { + accessorKey: "role", + header: "Rollen", + cell: ({ row }) => { + const roles = getUserPermissions(Number(row.getValue("role"))); + + return ( +
+ {roles + .filter(({ name }) => name !== "User") + .map((role) => ( + + {role.name} + + ))} + +
+ ); + }, + }, +]; diff --git a/src/app/admin/team/page.tsx b/src/app/admin/team/page.tsx new file mode 100644 index 0000000..51cac84 --- /dev/null +++ b/src/app/admin/team/page.tsx @@ -0,0 +1,22 @@ +import { api } from "@/trpc/server"; +import React from "react"; +import { DataTable } from "@/components/data-table"; +import { columns } from "./columns"; +import { Button } from "@/components/ui/button"; + +async function Page() { + const users = await api.users.getAll(); + + return ( + <> +
+

Team Mitglieder

+ +
+ + + + ); +} + +export default Page; diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx new file mode 100644 index 0000000..447378d --- /dev/null +++ b/src/components/data-table.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ); +} diff --git a/src/components/layout/app-sidebar.tsx b/src/components/layout/app-sidebar.tsx index 7a93eb8..002cca3 100644 --- a/src/components/layout/app-sidebar.tsx +++ b/src/components/layout/app-sidebar.tsx @@ -16,6 +16,7 @@ import SidebarLink from "./sidebar-link"; import { Button } from "../ui/button"; import { HomeIcon } from "lucide-react"; import { api } from "@/trpc/server"; +import { appConfig } from "@/app.config"; export async function AppSidebar({ ...props @@ -25,7 +26,7 @@ export async function AppSidebar({ - Wiki + {appConfig.name} diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index c36785a..16864b8 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -4,7 +4,7 @@ import { auth } from "@/server/auth"; import { Button } from "../ui/button"; import Link from "next/link"; import Avatar from "../avatar"; -import { hasPermission, Role } from "@/lib/validation/has-permission"; +import { hasPermission, Role } from "@/lib/validation/permissions"; import { Popover, PopoverContent, @@ -17,11 +17,19 @@ async function Navbar() { const isEditor = session?.user ? hasPermission(session.user.role, Role.EDITOR) : false; + const isAdmin = session?.user + ? hasPermission(session.user.role, Role.ADMIN) + : false; return (
+ {isAdmin && ( + + )} {isEditor && ( diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..e87d62b --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..c0df655 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/lib/validation/has-permission.tsx b/src/lib/validation/has-permission.tsx deleted file mode 100644 index c5b6360..0000000 --- a/src/lib/validation/has-permission.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* Bitmap Based Roles - * Admin and editor role === 6 - * User role === 1 - * Editor role === 2 - * Admin role === 4 - * Highest role is 7 - */ -export enum Role { - USER = 1, // 001 - EDITOR = 2, // 010 - ADMIN = 4, // 100 -} - -export function hasPermission(userRole: number, requiredRole: Role): boolean { - return (userRole & requiredRole) !== 0; -} diff --git a/src/lib/validation/permissions.tsx b/src/lib/validation/permissions.tsx new file mode 100644 index 0000000..e443a2c --- /dev/null +++ b/src/lib/validation/permissions.tsx @@ -0,0 +1,58 @@ +/* Bitmap Based Roles + * User role === 1 + * Editor role === 2 + * Admin role === 4 + * Admin and editor role === 6 + * Highest role is 7 + */ +export enum Role { + USER = 1, // 001 + EDITOR = 2, // 010 + ADMIN = 4, // 100 +} + +export function hasPermission(userRole: number, requiredRole: Role): boolean { + return (userRole & requiredRole) !== 0; +} + +type RoleDefinition = { + name: string; + color: { + bg: string; + fg: string; + border: string; + }; +}; + +const roles: { [key: number]: RoleDefinition } = { + [Role.USER]: { + name: "User", + color: { + bg: "bg-muted", + fg: "text-muted-foreground", + border: "border-muted-foreground", + }, + }, + [Role.EDITOR]: { + name: "Editor", + color: { + bg: "bg-blue-300", + fg: "text-blue-700", + border: "border-blue-700", + }, + }, + [Role.ADMIN]: { + name: "Admin", + color: { + bg: "bg-destructive", + fg: "text-destructive-foreground", + border: "border-destructive", + }, + }, +}; +export function getUserPermissions(userRole: number): RoleDefinition[] { + return Object.keys(roles) + .map(Number) // Convert keys to numbers + .filter((role) => (userRole & role) !== 0) // Check if role is set + .map((role) => roles[role]!); // Convert back to role name +} diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 85b6bae..f6ef06f 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -1,6 +1,7 @@ import { articleRouter } from "./routers/article"; import { categoryRouter } from "@/server/api/routers/category"; import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc"; +import { usersRouter } from "./routers/users"; /** * This is the primary router for your server. @@ -10,6 +11,7 @@ import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc"; export const appRouter = createTRPCRouter({ article: articleRouter, category: categoryRouter, + users: usersRouter, }); // export type definition of API diff --git a/src/server/api/routers/article.ts b/src/server/api/routers/article.ts index 05ef550..4c39c40 100644 --- a/src/server/api/routers/article.ts +++ b/src/server/api/routers/article.ts @@ -8,7 +8,7 @@ import { import { articles } from "@/server/db/schema"; import { articleSchema } from "@/lib/validation/zod/article"; import { eq } from "drizzle-orm"; -import { hasPermission, Role } from "@/lib/validation/has-permission"; +import { hasPermission, Role } from "@/lib/validation/permissions"; import { generateSlug } from "@/lib/utils"; export const articleRouter = createTRPCRouter({ diff --git a/src/server/api/routers/category.ts b/src/server/api/routers/category.ts index 42ddbbc..9535353 100644 --- a/src/server/api/routers/category.ts +++ b/src/server/api/routers/category.ts @@ -7,7 +7,7 @@ import { import { categories } from "@/server/db/schema"; import { eq } from "drizzle-orm"; -import { hasPermission, Role } from "@/lib/validation/has-permission"; +import { hasPermission, Role } from "@/lib/validation/permissions"; import { categorySchema } from "@/lib/validation/zod/category"; import { generateSlug } from "@/lib/utils"; diff --git a/src/server/api/routers/users.ts b/src/server/api/routers/users.ts new file mode 100644 index 0000000..e9aec79 --- /dev/null +++ b/src/server/api/routers/users.ts @@ -0,0 +1,10 @@ +import { hasPermission, Role } from "@/lib/validation/permissions"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; + +export const usersRouter = createTRPCRouter({ + getAll: protectedProcedure.query(async ({ ctx }) => { + const isAdmin = hasPermission(ctx.session.user.role, Role.ADMIN); + if (!isAdmin) throw new Error("You are not allowed to get all users"); + return await ctx.db.query.users.findMany(); + }), +});