admin dashbaord; started member managment ui

This commit is contained in:
shrt 2025-03-07 20:18:00 +01:00
parent 4a1397651f
commit 5c09688f1a
24 changed files with 1466 additions and 29 deletions

View File

@ -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": {

430
pnpm-lock.yaml generated
View File

@ -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: {}

502
seed-data/fake-users.json Normal file
View File

@ -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"
}
]

12
seed-data/seed.ts Normal file
View File

@ -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());

7
src/app.config.ts Normal file
View File

@ -0,0 +1,7 @@
export type AppConfig = {
name: string;
};
export const appConfig: AppConfig = {
name: "Wiki Antifa",
};

View File

@ -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";

View File

@ -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";

View File

@ -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 (
<div className="flex h-14 w-full items-center justify-between gap-4 border-b bg-sidebar px-4">
<span>Admin Dashboard</span>
</div>
);
}
export default AdminNavbar;

View File

@ -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<typeof Sidebar>) {
return (
<Sidebar {...props}>
<SidebarHeader className="flex h-14 items-center justify-center border-b">
<Link href={"/admin"}>
<span className="text-2xl font-bold">{appConfig.name}</span>
</Link>
</SidebarHeader>
<SidebarContent>
{/* We create a SidebarGroup for each parent. */}
{data.navMain.map((item) => (
<SidebarGroup key={item.title}>
<SidebarGroupLabel>{item.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{item.items.map((item) => (
<SidebarLink key={item.title} {...item} />
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarFooter>
<Button
asChild
variant={"destructive"}
className="border-2 bg-sidebar text-foreground hover:text-destructive-foreground"
>
<Link href={"/"}>
<LogOutIcon className="size-4 rotate-180" /> Verlassen
</Link>
</Button>
</SidebarFooter>
</Sidebar>
);
}

26
src/app/admin/layout.tsx Normal file
View File

@ -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 (
<SidebarProvider>
<AdminSidebar />
<div className="w-full">
{/* <AdminNavbar /> */}
<main className="space-y-4 p-4"> {children}</main>
</div>
</SidebarProvider>
);
}
export default Layout;

8
src/app/admin/page.tsx Normal file
View File

@ -0,0 +1,8 @@
import { api } from "@/trpc/server";
import React from "react";
async function Page() {
return <div>Admin Dashboard</div>;
}
export default Page;

View File

@ -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<User>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "role",
header: "Rollen",
cell: ({ row }) => {
const roles = getUserPermissions(Number(row.getValue("role")));
return (
<div className="flex items-center gap-2">
{roles
.filter(({ name }) => name !== "User")
.map((role) => (
<Badge
key={role.name}
variant={"outline"}
className={`${Object.values(role.color).join(" ")}`}
>
{role.name}
</Badge>
))}
<Button
size={"icon"}
variant={"outline"}
className="size-max gap-1 bg-muted p-px text-muted-foreground"
>
<Plus className="size-3" />
</Button>
</div>
);
},
},
];

View File

@ -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 (
<>
<div className="flex w-full items-center justify-between">
<h1 className="text-2xl font-bold">Team Mitglieder</h1>
<Button variant={"outline"}>Mitglied einladen</Button>
</div>
<DataTable columns={columns} data={users} />
</>
);
}
export default Page;

View File

@ -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<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}

View File

@ -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({
<Sidebar {...props}>
<SidebarHeader className="flex h-14 items-center justify-center border-b">
<Link href={"/"}>
<span className="text-2xl font-bold">Wiki</span>
<span className="text-2xl font-bold">{appConfig.name}</span>
</Link>
</SidebarHeader>
<SidebarContent>

View File

@ -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 (
<div className="flex h-14 items-center justify-between border-b bg-sidebar px-4">
<Input className="w-full max-w-xs" placeholder="Suche..." />
<div className="flex items-center gap-4">
{isAdmin && (
<Button asChild>
<Link href={"/admin"}>Admin Dashboard</Link>
</Button>
)}
{isEditor && (
<Popover>
<PopoverTrigger asChild>

View File

@ -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<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

120
src/components/ui/table.tsx Normal file
View File

@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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({

View File

@ -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";

View File

@ -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();
}),
});