diff --git a/package.json b/package.json index 8091d8cb..a635e9f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "query", "private": true, "version": "1.0.0", - "engines": { + "packageManager": "pnpm@10.33.2", + "engines": { "node": ">=20.16.0 <23" }, "scripts": { @@ -26,6 +27,7 @@ "autoprefixer": "10.4.22", "chart.js": "4.5.1", "csv-parse": "6.1.0", + "lucide-react": "^1.14.0", "minimatch": "10.2.3", "next": "16.2.3", "postcss": "8.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8530ddfc..aa33bea8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: csv-parse: specifier: 6.1.0 version: 6.1.0 + lucide-react: + specifier: ^1.14.0 + version: 1.14.0(react@19.0.0) minimatch: specifier: '>=10.2.3' version: 10.2.4 @@ -1082,89 +1085,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1262,24 +1281,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.2.3': resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.2.3': resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.2.3': resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.2.3': resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} @@ -1337,36 +1360,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -1818,66 +1847,79 @@ packages: resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.0': resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.0': resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.0': resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.0': resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.0': resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.0': resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.0': resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.0': resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.0': resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.0': resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.0': resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.0': resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.0': resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} @@ -2040,48 +2082,56 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.8': resolution: {integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.8': resolution: {integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.8': resolution: {integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.8': resolution: {integrity: sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==} @@ -3855,48 +3905,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -3983,6 +4041,11 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lucide-react@1.14.0: + resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-bytes.js@1.13.0: resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} @@ -8712,6 +8775,10 @@ snapshots: lru-cache@7.18.3: {} + lucide-react@1.14.0(react@19.0.0): + dependencies: + react: 19.0.0 + magic-bytes.js@1.13.0: {} magic-string@0.30.21: diff --git a/sites/mainweb/app/(portal)/admin/analytics/page.tsx b/sites/mainweb/app/(portal)/admin/analytics/page.tsx new file mode 100644 index 00000000..2c0ff073 --- /dev/null +++ b/sites/mainweb/app/(portal)/admin/analytics/page.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { useSession } from 'next-auth/react'; +import { trpc } from '@/lib/trpc'; +import { useRouter } from 'next/navigation'; +import AdminLayout from '@/components/portal/AdminLayout'; +import { LiquidGlass } from '@/components/portal/LiquidGlass'; +import { Users, Trophy, Calendar, Clock, TrendingUp, Activity } from 'lucide-react'; + +export default function AnalyticsPage() { + const { data: session, status } = useSession(); + const router = useRouter(); + + // TODO: Replace with actual trpc analytics.overview when implemented + // const { data: stats, isLoading } = trpc.analytics.overview.useQuery(undefined, { enabled: !!session }); + const stats = { totalParticipants: 0, totalEvents: 0, totalHackathons: 0, checkinsToday: 0 }; + const isLoading = false; + + if (status === 'unauthenticated') { + router.push('/login'); + return null; + } + + const StatCard = ({ icon: Icon, title, value, subtitle, trend }: any) => ( + +
+
+ +
+
+

{title}

+

{value}

+ {subtitle && ( +
+ {subtitle} + {trend?.positive && ( + + + {trend.percent}% + + )} + {trend?.negative && ( + + + {trend.percent}% + + )} +
+ )} +
+
+
+ ); + + return ( + +
+ {/* Page Header */} +
+

+ Analytics Dashboard +

+

+ View comprehensive statistics across all events, hackathons, and user engagement. +

+
+ + {/* Stats Grid */} +
+ {isLoading ? ( + [1, 2, 3, 4].map((i) => ( + +
+
+
+
+
+ + )) + ) : ( + <> + + + + + + )} +
+ + {/* Charts Section */} +
+ {/* Registration Trend */} + +

Registration Trend

+
+

Chart visualization for registration trends

+
+
+ + {/* Event Types */} + +

Event Distribution

+
+

Pie chart for event type breakdown

+
+
+
+ + {/* Recent Activity */} + +

Recent Activity

+
+ {isLoading ? ( + [1, 2, 3].map((i) => ( +
+
+
+
+
+
+
+ )) + ) : ( +
+ No recent activity to display +
+ )} +
+ +
+ + ); +} diff --git a/sites/mainweb/app/(portal)/admin/attendees/page.tsx b/sites/mainweb/app/(portal)/admin/attendees/page.tsx new file mode 100644 index 00000000..f6c4d120 --- /dev/null +++ b/sites/mainweb/app/(portal)/admin/attendees/page.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useSession } from 'next-auth/react'; +import { trpc } from '@/lib/trpc'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { skipToken } from '@tanstack/react-query'; +import AdminLayout from '@/components/portal/AdminLayout'; +import { LiquidGlass } from '@/components/portal/LiquidGlass'; +import { Download } from 'lucide-react'; + +export default function AttendeesPage() { + const { data: session, status } = useSession(); + const router = useRouter(); + const utils = trpc.useUtils(); + + const [filter, setFilter] = useState<'all' | 'registered' | 'pending' | 'cancelled'>('all'); + + // Fetch all hackathons for the dropdown + const { data: hackathons } = trpc.hackathon.listAll.useQuery(undefined, { enabled: !!session }); + const [selectedHackathon, setSelectedHackathon] = useState(null); + + // Fetch attendees for selected hackathon + const { data: attendees, isLoading } = trpc.hackathon.adminGetAttendees.useQuery( + selectedHackathon ? { hackathonId: selectedHackathon } : skipToken, + { enabled: !!session && !!selectedHackathon } + ); + + if (status === 'unauthenticated') { + router.push('/login'); + return null; + } + + const handleDownloadCSV = () => { + if (selectedHackathon) { + // CSV export logic would go here + console.log('Downloading CSV for hackathon:', selectedHackathon); + } + }; + + return ( + +
+ {/* Page Header */} +
+

+ Attendees Registry +

+

+ View and manage attendee registrations for hackathon events. +

+
+ +
+ {/* Hackathon Selector */} +
+
+ +
+ {selectedHackathon && ( + + )} +
+ + {/* Filters */} +
+ + + + +
+ + {/* Attendees List */} +
+ {isLoading ? ( +
+

Loading...

+
+ ) : !attendees || attendees.length === 0 ? ( + +
+ + + +
+

No attendees yet

+

Select a hackathon to view registrations.

+
+ ) : ( +
+ + + + + + + + + + + + {attendees + .filter((a) => { + if (filter === 'all') return true; + if (filter === 'registered') return a.registrationStatus === 'approved'; + if (filter === 'pending') return a.registrationStatus === 'pending'; + if (filter === 'cancelled') return a.registrationStatus === 'rejected'; + return true; + }) + .map((attendee) => ( + + + + + + + + ))} + +
NameEmailTeamStatusDate
+
+ {(attendee.user?.name +
+

+{(attendee.user?.name || attendee.user?.email) || ''} +

+

{attendee.user?.email || ''}

+
+
+
{attendee.user?.email || ''}{attendee.team?.name || 'Individual'} + + {attendee.registrationStatus} + + + {new Date(attendee.registeredAt).toLocaleDateString()} +
+
+ )} +
+
+
+
+ ); +} diff --git a/sites/mainweb/app/(portal)/admin/hackathons/[id]/attendees/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/[id]/attendees/page.tsx index 6a672802..3e77568c 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/[id]/attendees/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/[id]/attendees/page.tsx @@ -4,7 +4,6 @@ import React from 'react'; import { useSession } from 'next-auth/react'; import { trpc } from '@/lib/trpc'; import { useParams, useRouter } from 'next/navigation'; -import Background from '@/components/portal/Background'; import { LiquidGlass } from '@/components/portal/LiquidGlass'; import { LoadingScreen } from '@/components/portal/LoadingScreen'; import Link from 'next/link'; @@ -81,7 +80,6 @@ export default function AdminAttendeeViewer() { return (
-
diff --git a/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx index 6de9b201..8950212a 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx @@ -5,7 +5,6 @@ import { trpc } from '@/lib/trpc'; import { useRouter, useParams } from 'next/navigation'; import { useState } from 'react'; import Link from 'next/link'; -import Background from '@/components/portal/Background'; import { LoadingScreen } from '@/components/portal/LoadingScreen'; import { ScannerTab } from '@/components/admin/hackathons/ScannerTab'; import { AttendeesTab } from '@/components/admin/hackathons/AttendeesTab'; @@ -40,7 +39,6 @@ export default function AdminHackathonDashboard() { return (
- {/* HEADER */}
diff --git a/sites/mainweb/app/(portal)/admin/hackathons/error.tsx b/sites/mainweb/app/(portal)/admin/hackathons/error.tsx index 3235da80..5a31843a 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/error.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/error.tsx @@ -2,7 +2,6 @@ import { useEffect } from 'react'; import Link from 'next/link'; -import Background from '@/components/portal/Background'; import { LiquidGlass } from '@/components/portal/LiquidGlass'; export default function AdminError({ @@ -18,7 +17,6 @@ export default function AdminError({ return (
-
diff --git a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx index 871b648d..46294f84 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx @@ -31,22 +31,22 @@ export default function AdminHackathonsPage() { return ( -
-
-
-

- Hackathon Manager -

-

- Manage your hackathon events -

-
- +
+
+

+ Hackathon Manager +

+

+ Manage your hackathons, participants, and event check-in locations. +

+
+
+
{showCreate && ( @@ -70,17 +70,15 @@ export default function AdminHackathonsPage() { /> )} -
-
-

Hackathons

-

{hackathons?.length || 0} total

+
+
+
+

Hackathons

+

{hackathons?.length || 0} total

+
- {isLoading ? ( -
-

Loading...

-
- ) : !hackathons || hackathons.length === 0 ? ( + {!hackathons || hackathons.length === 0 ? (
diff --git a/sites/mainweb/app/(portal)/admin/page.tsx b/sites/mainweb/app/(portal)/admin/page.tsx index c442d24a..2203b877 100644 --- a/sites/mainweb/app/(portal)/admin/page.tsx +++ b/sites/mainweb/app/(portal)/admin/page.tsx @@ -133,18 +133,25 @@ export default function AdminPage() { /> )} -
- +
+
+

+ Check-in Events +

+

+ Manage your event check-in locations, QR codes, and attendance tracking. +

+
{/* View Controls */}
-
+
+ + {/* Search bar */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full h-9 pl-10 pr-4 bg-white/5 border border-white/10 rounded-xl text-sm text-white placeholder:text-gray-500 focus:outline-none focus:border-[#00A8A8]/50 focus:ring-2 focus:ring-[#00A8A8]/20" + autoFocus={showSearch} + onBlur={() => setTimeout(() => setShowSearch(false), 200)} + /> +
+
+ + {/* Search toggle */} + + + {/* Notifications */} + + + {/* Theme toggle */} + + + {/* Logo */} + +
+ GL +
+ + GreenLight + + +
+
+ ); +} diff --git a/sites/mainweb/components/portal/AdminLayout.tsx b/sites/mainweb/components/portal/AdminLayout.tsx index d53ebab6..51e2eff0 100644 --- a/sites/mainweb/components/portal/AdminLayout.tsx +++ b/sites/mainweb/components/portal/AdminLayout.tsx @@ -1,9 +1,9 @@ 'use client'; import { useSession } from 'next-auth/react'; +import AdminSidebar from './AdminSidebar'; +import AdminHeader from './AdminHeader'; import { useEffect, useState } from 'react'; -import Background from './Background'; -import { Spinner } from './Spinner'; export default function AdminLayout({ children }: { children: React.ReactNode }) { const { data: session, status } = useSession(); @@ -21,18 +21,19 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) return (
- +
); } return ( -
- -
+
+ + +
{children}
); -} \ No newline at end of file +} diff --git a/sites/mainweb/components/portal/AdminSidebar.tsx b/sites/mainweb/components/portal/AdminSidebar.tsx new file mode 100644 index 00000000..949417b0 --- /dev/null +++ b/sites/mainweb/components/portal/AdminSidebar.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useSession, signOut } from 'next-auth/react'; +import { LayoutDashboard, Code, ClipboardList, Users, FileText, BarChart3, Settings, LogOut, Menu } from 'lucide-react'; + +const routes = [ + { name: 'Events', href: '/admin', icon: LayoutDashboard }, + { name: 'Hackathons', href: '/admin/hackathons', icon: Code }, + { name: 'Judging', href: '/admin/judging', icon: ClipboardList }, + { name: 'Attendees', href: '/admin/attendees', icon: Users }, + { name: 'Projects', href: '/admin/projects', icon: FileText }, + { name: 'Analytics', href: '/admin/analytics', icon: BarChart3 }, + { name: 'Settings', href: '/admin/settings', icon: Settings }, +]; + +export default function AdminSidebar() { + const [isOpen, setIsOpen] = useState(true); + const pathname = usePathname(); + const { data: session } = useSession(); + + return ( +
+ {/* Header */} +
+ {isOpen && ( +
+
+ +
+ + GreenLight + +
+ )} + {!isOpen && ( +
+ +
+ )} + + {/* Toggle button */} + +
+ + {/* Navigation */} + + + {/* User section */} +
+
+ {session?.user?.name + {isOpen && ( +
+

{session?.user?.name || 'Admin User'}

+

{session?.user?.email || 'Admin'}

+
+ )} + {!isOpen && ( + User + )} +
+ {isOpen && ( + + )} +
+
+ ); +} diff --git a/sites/mainweb/components/portal/Background.tsx b/sites/mainweb/components/portal/Background.tsx deleted file mode 100644 index 95ec237e..00000000 --- a/sites/mainweb/components/portal/Background.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -const Background = ({ className = "" }: { className?: string }) => { - return ( -
- {/* Primary Glow */} -
- - {/* Secondary Orbital Glows */} -
-
- - {/* Technical Grid Overlay */} -
-
- ); -}; - -export default Background; diff --git a/sites/mainweb/components/portal/Spinner.tsx b/sites/mainweb/components/portal/Spinner.tsx deleted file mode 100644 index 8906eaa9..00000000 --- a/sites/mainweb/components/portal/Spinner.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export function Spinner({ className }: { className?: string }) { - return ( -
- - - - -
- ); -}