From aae4ec86891279732a5e682fc55ca902dd7d1314 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Wed, 4 Feb 2026 02:57:28 +0100 Subject: [PATCH 01/11] checkpoint: header, passkey, assoc members, rewrites --- .gitignore | 2 + package.json | 5 +- pnpm-lock.yaml | 278 ++++++++++++++++-- src/app/dashboard/(active)/account/page.tsx | 36 ++- .../(active)/account/passkey-button.tsx | 35 +++ src/app/dashboard/(active)/assoc/page.tsx | 27 ++ src/app/layout.tsx | 16 +- src/app/login/login-form.tsx | 171 ++++++----- src/app/login/page.tsx | 4 +- src/app/onboarding/link/telegram.tsx | 25 +- src/app/page.tsx | 3 +- src/assets/logo.png | Bin 0 -> 4113 bytes src/assets/logo.svg | 9 + src/components/create-assoc-member.tsx | 130 ++++++++ src/components/header-login-button.tsx | 44 +++ src/components/header.tsx | 43 ++- src/components/logo.tsx | 6 + src/components/sidebar/admin-sidebar.tsx | 6 + src/components/theme-button.tsx | 4 +- src/components/ui/button.tsx | 4 +- src/index.css | 2 +- src/lib/auth.ts | 3 +- 22 files changed, 707 insertions(+), 146 deletions(-) create mode 100644 src/app/dashboard/(active)/account/passkey-button.tsx create mode 100644 src/app/dashboard/(active)/assoc/page.tsx create mode 100644 src/assets/logo.png create mode 100644 src/assets/logo.svg create mode 100644 src/components/create-assoc-member.tsx create mode 100644 src/components/header-login-button.tsx create mode 100644 src/components/logo.tsx diff --git a/.gitignore b/.gitignore index 30cce58..b2de018 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ yarn-error.log* # idea files .idea + +certificates \ No newline at end of file diff --git a/package.json b/package.json index b9da9b0..1bca3c7 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@better-auth/passkey": "^1.4.17", "@hookform/resolvers": "^3.9.1", - "@polinetwork/backend": "^0.14.0", + "@polinetwork/backend": "file:../backend/package/dist/", "@radix-ui/react-alert-dialog": "^1.1.3", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", @@ -34,8 +35,8 @@ "@trpc/next": "11.5.1", "@trpc/react-query": "11.5.1", "@trpc/tanstack-react-query": "11.5.1", - "better-auth": "^1.4.15", "babel-plugin-react-compiler": "1.0.0", + "better-auth": "^1.4.17", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afcba2c..2900802 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,15 @@ importers: .: dependencies: + '@better-auth/passkey': + specifier: ^1.4.17 + version: 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.1.8(zod@4.3.5))(nanostores@1.1.0) '@hookform/resolvers': specifier: ^3.9.1 version: 3.10.0(react-hook-form@7.55.0(react@18.3.1)) '@polinetwork/backend': - specifier: ^0.14.0 - version: 0.14.0 + specifier: file:../backend/package/dist/ + version: dist@file:../backend/package/dist '@radix-ui/react-alert-dialog': specifier: ^1.1.3 version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -75,8 +78,8 @@ importers: specifier: 1.0.0 version: 1.0.0 better-auth: - specifier: ^1.4.15 - version: 1.4.15(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.4.17 + version: 1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -181,8 +184,8 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@better-auth/core@1.4.15': - resolution: {integrity: sha512-uAvq8YA7SaS7v+TrvH/Kwt7LAJihzUqB3FX8VweDsqu3gn5t51M+Bve+V1vVWR9qBAtC6cN68V6b+scxZxDY4A==} + '@better-auth/core@1.4.17': + resolution: {integrity: sha512-WSaEQDdUO6B1CzAmissN6j0lx9fM9lcslEYzlApB5UzFaBeAOHNUONTdglSyUs6/idiZBoRvt0t/qMXCgIU8ug==} peerDependencies: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -191,10 +194,20 @@ packages: kysely: ^0.28.5 nanostores: ^1.0.1 - '@better-auth/telemetry@1.4.15': - resolution: {integrity: sha512-7NW/2PS4RN85rv+ozpAezP/kSLPZeWkxqcA6RA/CFXqWp2YR2e5q5E6Hym1qBgVBkoAQa3lWFdX3b+jEs+vvrQ==} + '@better-auth/passkey@1.4.17': + resolution: {integrity: sha512-SmS7a3UYi9A0muo4fWx1wEn59vtjoOLBFD5PnRo330BcXro6mHWxG/IXtnOnyeQKNGnEp4X7/QXcRrW/nDH0Iw==} peerDependencies: - '@better-auth/core': 1.4.15 + '@better-auth/core': 1.4.17 + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + better-auth: 1.4.17 + better-call: 1.1.8 + nanostores: ^1.0.1 + + '@better-auth/telemetry@1.4.17': + resolution: {integrity: sha512-R1BC4e/bNjQbXu7lG6ubpgmsPj7IMqky5DvMlzAtnAJWJhh99pMh/n6w5gOHa0cqDZgEAuj75IPTxv+q3YiInA==} + peerDependencies: + '@better-auth/core': 1.4.17 '@better-auth/utils@0.3.0': resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} @@ -273,6 +286,9 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -415,6 +431,9 @@ packages: cpu: [x64] os: [win32] + '@levischuck/tiny-cbor@0.2.11': + resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@next/env@15.5.9': resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} @@ -474,9 +493,42 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} - '@polinetwork/backend@0.14.0': - resolution: {integrity: sha512-VSkSLetxprjPu38WFK580uQ3VBtflM4Srz+D32ONtL0u9SUjixSq5lNwPXcQmGqiByrV3RIA3Z2WBJY2A9Lk4w==} - engines: {node: '>=24.8.0', pnpm: '>=10.17.1'} + '@peculiar/asn1-android@2.6.0': + resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==} + + '@peculiar/asn1-cms@2.6.0': + resolution: {integrity: sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==} + + '@peculiar/asn1-csr@2.6.0': + resolution: {integrity: sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==} + + '@peculiar/asn1-ecc@2.6.0': + resolution: {integrity: sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==} + + '@peculiar/asn1-pfx@2.6.0': + resolution: {integrity: sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==} + + '@peculiar/asn1-pkcs8@2.6.0': + resolution: {integrity: sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==} + + '@peculiar/asn1-pkcs9@2.6.0': + resolution: {integrity: sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==} + + '@peculiar/asn1-rsa@2.6.0': + resolution: {integrity: sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==} + + '@peculiar/asn1-schema@2.6.0': + resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==} + + '@peculiar/asn1-x509-attr@2.6.0': + resolution: {integrity: sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==} + + '@peculiar/asn1-x509@2.6.0': + resolution: {integrity: sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==} + + '@peculiar/x509@1.14.3': + resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} + engines: {node: '>=20.0.0'} '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} @@ -1017,6 +1069,13 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@simplewebauthn/browser@13.2.2': + resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==} + + '@simplewebauthn/server@13.2.2': + resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==} + engines: {node: '>=20.0.0'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1233,11 +1292,15 @@ packages: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} + asn1js@3.0.7: + resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} + engines: {node: '>=12.0.0'} + babel-plugin-react-compiler@1.0.0: resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} - better-auth@1.4.15: - resolution: {integrity: sha512-XZr4GnFPbjvf8wip8AAjTrpGNn3Sba600zT+DgsR3NNCMWCt9aD8+nuRah6BHwHWnVP1nfnby07tPmti72SRBw==} + better-auth@1.4.17: + resolution: {integrity: sha512-VmHGQyKsEahkEs37qguROKg/6ypYpNF13D7v/lkbO7w7Aivz0Bv2h+VyUkH4NzrGY0QBKXi1577mGhDCVwp0ew==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1346,6 +1409,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dist@file:../backend/package/dist: + resolution: {directory: ../backend/package/dist, type: directory} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -1520,6 +1586,13 @@ packages: peerDependencies: react: '>=16.0.0' + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.5: + resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + engines: {node: '>=16.0.0'} + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -1565,6 +1638,9 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} @@ -1579,8 +1655,8 @@ packages: server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} @@ -1634,9 +1710,16 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + typescript@5.7.3: resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} @@ -1681,7 +1764,7 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': + '@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -1692,9 +1775,21 @@ snapshots: nanostores: 1.1.0 zod: 4.3.5 - '@better-auth/telemetry@1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))': + '@better-auth/passkey@1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.1.8(zod@4.3.5))(nanostores@1.1.0)': + dependencies: + '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@simplewebauthn/browser': 13.2.2 + '@simplewebauthn/server': 13.2.2 + better-auth: 1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + better-call: 1.1.8(zod@4.3.5) + nanostores: 1.1.0 + zod: 4.3.5 + + '@better-auth/telemetry@1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))': dependencies: - '@better-auth/core': 1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -1759,6 +1854,8 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@hexagon/base64@1.1.28': {} + '@hookform/resolvers@3.10.0(react-hook-form@7.55.0(react@18.3.1))': dependencies: react-hook-form: 7.55.0(react@18.3.1) @@ -1860,6 +1957,8 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@levischuck/tiny-cbor@0.2.11': {} + '@next/env@15.5.9': {} '@next/swc-darwin-arm64@15.5.7': @@ -1890,7 +1989,101 @@ snapshots: '@noble/hashes@2.0.1': {} - '@polinetwork/backend@0.14.0': {} + '@peculiar/asn1-android@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-cms@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509-attr': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.6.0': + dependencies: + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-pkcs8': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.6.0': + dependencies: + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-pfx': 2.6.0 + '@peculiar/asn1-pkcs8': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509-attr': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.6.0': + dependencies: + asn1js: 3.0.7 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + asn1js: 3.0.7 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.6.0': + dependencies: + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.7 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/x509@1.14.3': + dependencies: + '@peculiar/asn1-cms': 2.6.0 + '@peculiar/asn1-csr': 2.6.0 + '@peculiar/asn1-ecc': 2.6.0 + '@peculiar/asn1-pkcs9': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 '@radix-ui/number@1.1.0': {} @@ -2410,6 +2603,19 @@ snapshots: '@radix-ui/rect@1.1.0': {} + '@simplewebauthn/browser@13.2.2': {} + + '@simplewebauthn/server@13.2.2': + dependencies: + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.11 + '@peculiar/asn1-android': 2.6.0 + '@peculiar/asn1-ecc': 2.6.0 + '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.0 + '@peculiar/x509': 1.14.3 + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.15': @@ -2569,14 +2775,20 @@ snapshots: dependencies: tslib: 2.8.1 + asn1js@3.0.7: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.5 + tslib: 2.8.1 + babel-plugin-react-compiler@1.0.0: dependencies: '@babel/types': 7.28.2 - better-auth@1.4.15(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@better-auth/core': 1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) + '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 @@ -2597,7 +2809,7 @@ snapshots: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 rou3: 0.7.12 - set-cookie-parser: 2.7.1 + set-cookie-parser: 2.7.2 optionalDependencies: zod: 4.3.5 @@ -2638,6 +2850,8 @@ snapshots: detect-node-es@1.1.0: {} + dist@file:../backend/package/dist: {} + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -2776,6 +2990,12 @@ snapshots: clsx: 2.1.1 react: 18.3.1 + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.5: {} + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -2817,6 +3037,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + reflect-metadata@0.2.2: {} + rou3@0.7.12: {} scheduler@0.23.2: @@ -2828,7 +3050,7 @@ snapshots: server-only@0.0.1: {} - set-cookie-parser@2.7.1: {} + set-cookie-parser@2.7.2: {} sharp@0.34.5: dependencies: @@ -2895,8 +3117,14 @@ snapshots: tapable@2.2.1: {} + tslib@1.14.1: {} + tslib@2.8.1: {} + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + typescript@5.7.3: {} undici-types@6.19.8: {} diff --git a/src/app/dashboard/(active)/account/page.tsx b/src/app/dashboard/(active)/account/page.tsx index 7ee6670..6152ce7 100644 --- a/src/app/dashboard/(active)/account/page.tsx +++ b/src/app/dashboard/(active)/account/page.tsx @@ -1,21 +1,32 @@ -import { CircleAlert, UserIcon } from "lucide-react" +import { Calendar, CircleAlert, KeyIcon, UserIcon } from "lucide-react" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { getInitials } from "@/lib/utils" import { getServerSession } from "@/server/auth" import { SetName } from "./set-name" import { Telegram } from "./telegram" +import { NewPasskeyButton } from "./passkey-button" +import { auth } from "@/lib/auth" +import { headers } from "next/headers" +import { Button } from "@/components/ui/button" export default async function Account() { const { data: session } = await getServerSession() if (!session) return + const { data: passkeys, error } = await auth.passkey.listUserPasskeys({ + fetchOptions: { + headers: await headers(), + }, + }) + + console.log(passkeys, error) + const { user } = session return (

Account

- -
+
{user.image && } @@ -40,6 +51,25 @@ export default async function Account() {
+
+

Passkeys

+ {passkeys?.map((p) => ( +
+
+ +
+
+

{p.name}

+

+ + Created on {p.createdAt.toLocaleDateString()} +

+
+ +
+ ))} + +
) } diff --git a/src/app/dashboard/(active)/account/passkey-button.tsx b/src/app/dashboard/(active)/account/passkey-button.tsx new file mode 100644 index 0000000..b4d96a4 --- /dev/null +++ b/src/app/dashboard/(active)/account/passkey-button.tsx @@ -0,0 +1,35 @@ +"use client" + +import { toast } from "sonner" +import { Button } from "@/components/ui/button" +import { auth } from "@/lib/auth" +import { useRouter } from "next/navigation" +import { useState } from "react" + +export function NewPasskeyButton() { + const router = useRouter() + const [isLoading, setIsLoading] = useState(false) + + return ( + + ) +} diff --git a/src/app/dashboard/(active)/assoc/page.tsx b/src/app/dashboard/(active)/assoc/page.tsx new file mode 100644 index 0000000..754191b --- /dev/null +++ b/src/app/dashboard/(active)/assoc/page.tsx @@ -0,0 +1,27 @@ +import { CreateAssocUser } from "@/components/create-assoc-member" +import { getQueryClient, trpc } from "@/lib/trpc/server" + +export default async function AssocIndex() { + const qc = getQueryClient() + const members = await qc.fetchQuery(trpc.azure.members.getAll.queryOptions()) + if (members.error) return
Error: {members.error}
+ + return ( +
+

{members.members.length} soci registrati

+ + {members.members + // biome-ignore lint/style/noNonNullAssertion: got it + .sort((a, b) => parseInt(a.employeeId!, 10) - parseInt(b.employeeId!, 10)) + // .sort((a, b) => a.displayName!.localeCompare(b.displayName!)) + .map((m) => ( +
+ {m.displayName} + {m.employeeId ?? "N/A"} + {m.mail} + {m.assignedLicensesIds.join(", ")} +
+ ))} +
+ ) +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 985c474..48bc746 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,4 @@ -import { GeistSans } from "geist/font/sans" +import { Poppins } from "next/font/google" import type { Metadata } from "next" import "@/index.css" import { HEADER_HEIGHT, Header } from "@/components/header" @@ -18,9 +18,11 @@ export const metadata: Metadata = { icons: [{ rel: "icon", url: "/favicon.ico" }], } +const poppins = Poppins({ weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], subsets: ["latin"] }) + export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( - + -
+
{children}
diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx index aae38a4..532d395 100644 --- a/src/app/login/login-form.tsx +++ b/src/app/login/login-form.tsx @@ -1,11 +1,10 @@ "use client" -import { Loader2 } from "lucide-react" +import { ArrowRight, KeyRound, Loader2 } from "lucide-react" import { useRouter } from "next/navigation" -import { useState } from "react" +import { useEffect, useState } from "react" import { toast } from "sonner" import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp" import { Label } from "@/components/ui/label" @@ -14,16 +13,36 @@ import { auth } from "@/lib/auth" export default function LoginForm() { const [email, setEmail] = useState("") const [sent, setSent] = useState(false) + const router = useRouter() + + // useEffect(() => { + // if (!PublicKeyCredential.isConditionalMediationAvailable || + // !PublicKeyCredential.isConditionalMediationAvailable()) { + // return; + // } + // + // void auth.signIn.passkey({ autoFill: true }).then(({ data, error }) => { + // if (error) { + // if ("code" in error && error.code === "AUTH_CANCELLED") return + // console.error("ERROR PASSKEY LOGIN", error) + // toast.error("Error passkey") + // return + // } + // + // console.log({ data }) + // toast.success("Logged in with passkey!") + // router.refresh() + // }) + // }, []) return ( -
- {!sent ? ( - setEmail(v)} onSend={() => setSent(true)} /> - ) : ( - - )} +
+ setEmail(v)} onSend={() => setSent(true)} /> + {sent && }
) + // Or continue with + // } function EmailCard({ @@ -36,8 +55,19 @@ function EmailCard({ onSend: () => void }) { const [loading, setLoading] = useState(false) + const router = useRouter() async function sendOtp() { + const { data: passkeyData, error: passkeyError } = await auth.signIn.passkey({ + autoFill: true, + }) + + if (passkeyData && !passkeyError) { + toast.success("Logged in with passkey!") + router.push("/dashboard") + return + } + const { data, error } = await auth.emailOtp.sendVerificationOtp({ type: "sign-in", email, @@ -57,45 +87,48 @@ function EmailCard({ return } + if (error?.code === "INVALID_EMAIL") { + toast.error("Invalid email") + return + } + toast.error("There was an unexpected error") console.error({ error }) } return ( - - - Sign In - - Enter your email below to login to your account - - - -
{ - e.preventDefault() - await sendOtp() - }} - > -
- +
+ { + e.preventDefault() + setLoading(true) + await sendOtp() + setLoading(false) + }} + > +
+ +
{ onChange(e.target.value) }} value={email} + autoComplete="email webauthn" /> +
- - - - +
+ +
) } @@ -130,47 +163,39 @@ function OTPCard({ email }: { email: string }) { } } return ( - - - Sign In - Enter the OTP we sent to your email - - -
{ - e.preventDefault() - await verifyOtp() - }} + { + e.preventDefault() + await verifyOtp() + }} + > +
+ + setOtp(v)} + autoComplete="off" + data-1p-ignore + data-lpignore="true" + data-protonpass-ignore="true" + type="text" > -
- - setOtp(v)} - autoComplete="off" - data-1p-ignore - data-lpignore="true" - data-protonpass-ignore="true" - type="text" - > - - - - - - - - - -
- - - - + + + + + + + + +
+
+ + ) } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index e701bc7..b23c53a 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -7,7 +7,9 @@ export default async function Page() { if (session) return redirect("/dashboard") return ( -
+
+

Login

+

Enter your email below to recieve an OTP to login or use a registered passkey.

) diff --git a/src/app/onboarding/link/telegram.tsx b/src/app/onboarding/link/telegram.tsx index 2d97f5a..ba5c468 100644 --- a/src/app/onboarding/link/telegram.tsx +++ b/src/app/onboarding/link/telegram.tsx @@ -11,6 +11,7 @@ import { Progress } from "@/components/ui/progress" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { useLocalStorage } from "@/hooks/use-local-storage" import { auth, useSession } from "@/lib/auth" +import { toast } from "sonner" type SavedLink = { username: string @@ -120,11 +121,25 @@ export function TelegramLink({ botUsername }: { botUsername: string }) { return savedLink && timeLeft ? (
-

- {savedLink.code.split("").map((c, i) => ( - {c} - ))} -

+ setExpired(true)} />
diff --git a/src/app/page.tsx b/src/app/page.tsx index eda2643..ca01951 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,8 +9,7 @@ export default async function IndexPage() { if (session.data?.user) redirect("/dashboard") return ( -
- +
diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d064c305184bef8ca6188d4add71a9bd760ce0ef GIT binary patch literal 4113 zcmV+s5bp1ZP)euK{$nHiW- zA(2H;=b{Ca5ZGPT6^-tg7~0VR2#B zvFyVqb91&ii+3HPxz46_RQv~r*tDq>my(|Ths^5zA7NmpWI{QiiJD2zOpq zFzuO66qMW|S)BC5szsN+du>INBA$)}^F;w#DBN#p#<#_YqLxl2EAlh3$iRS3Cz1N>uyesRF# z~39<|-rgq!vZ?-L5^xJ@wX zbz(v{gUm<>FDeObDImt(_4dNWPYnv7c$HG;?Sg-Fz)hBCLELT+YOd77&Uh~PmPVX} z|BL4g))0%Q5M1Kj=aSJ-;kfNQHk_f08TY=rm*+_g<2^>MT-25V{NJ-ZbHQ?2T>Lht z9LLUFyD30;k}V>+j)|k ze18(nn|)7D44%GC<4d#Ve}i$!5vaV<0AGf}DcawF4f}p%+M!#!Dt#urR7231W!#9l zFAVIiS4crD_~Q{6&9_>HWpt1)w@zTIi3XUR9@LiEZk8lt-o!T{vH2 zgDE-?8+Kg8GY=)8v9UwCqYXbgt$8btFlR#lD5FYD_*wp(6;S8SedfPNFd-|y3LZ*{ zZ6(>@r~r(A^;<1)o1bqzP>g-KS5dreD$Z|t7{dtqsxQ@QSkLiNTTp|7&mX}ZPk#+h zkP%}BMqqz#1(7`oCSs`jDGmnrvihU|dqe}Rn^)v@+4L7@|DI)M!%@4iDNw_QMzba0p@tlx7H7s!_btbQ;`B0}PVaGETW^u%^p@7auJCd9)+ z!m|(cXZj%t8kO!DX+b`nN^{6iuE9rNE zXqQ)1OGS>E3mon5cEe_K!RGMFnK&qAvUo>w(nx-jcc~5rOUJM#l30zD%)~b63=8cc zO9%St5k7G%tdl;)kChJ1bP*(=lLFS`xjFl?RnHXJ5Jy#-&>&+#Oc3fxNm*_?OG^~Y ztT2xuLz6~|3B6)_@SCg)RnYl$tbmT(!O^H_Fxq<5kgzX4l88@s6+mHQkh|%cPAaii zC@xT$sW(Zt%&V`mW7?gusQNk$2Ud+ke^cXuZ9N7ickXoHG!}5dUdSKSw zaW_6AxxZd|Cp_fVtp^+MA|V_Pl08@q^fPGryoT7JFVx0jkYJ}+Y583~?)!&DVK1e8 z9a-4ZDhqmug{G1Md$Nf>7;g9;`@6H&MM2;5CIo7B&tXF&Z$T!F(ljwyi&-*>jQ^Tu zg5Xnu%;?hKypeo({ZLY>oCL9{Xw$VCX~RC0M}8oP){%5A^cfU!(ny>O<324=spzk))dJ0*kZ*Qyi`riBE zQB+~a_QNHRD3*-Gr5bCTSWAIhQRl>ci~ko>N5^90u8YVm;M)6EPNon7_U;y-y}n#l zSu33@EGnee5y`zm;3gO|8Kv7O0D?#fPFe&q@unvSVO3@>tX(bIMtmYJ%GM5c8G^%< zgiwtFbCRngm)W`dB0?##S;%$R3mOP6m(RNc(^6v*eCNjq?Hz)}87H-zVQGU<@{lQ` z59t}Kz0NNzDU?oR1V9URYw3A9B0nZTaHU{4i%3kBZOS1;xx0Cw7Z)66-iAQk(qc`Yq zyTN^=-qGF@IQ~Pa#kOawCFbtm^EXHqF9DjyQ{$5y4CnybDPc^sI z=bAe*+@jGJ9@!^zzL084P93YTc@XBW!z1_fz(2`9TO$ekehL*ZpAd;+H{!Y=|FTU> zC`Zp-!x0kB-y&)Af?FBEO|CPTj(k0x7IU9Qa)DnDkHX)HhKs{Vcyb$Gh370j{egH* zhRfBId#J-rs+8H+Z98#|#a!~(0NBcIEi&Ach-OloqMuwmGQteDWFWcxODKiBeY+KXGw}ZZTKkluDbL zh|HA|g-#-*+x3ULVHvvtfiZ!Q72k#Dd8-eXz801$HAN3HTxv^&7t_#0BO~1i8;n>x(z&GtMJ_`RW3e{UaL{6re_bBc|LH ztJMgBRN+1S-$(J`%fEv4zE2=(!Q8o4jhzr{FSQ&;kSm`qmNBTiL6f~%0b$hi_ck2d zJbxO!)`bz16gaW9vJii{kzi8G6-}9&typ)t9&u!yl`kY?2F1rELa9}spTnQ$CSP|e zHr42tb|bHe$8=`s2+0D7^Cv^G_fQ?>NY(p_Mz@o#v{UvUv#7_APRtn_KEEi1xaB{K2u17o~+>}>bVvCQIJX8YeEY#ffqsEe=bWRIBDPB(5_YS(S5Xe?pC(98Q#mJ-h zG@nt&O|8e;Ju?~Il>95II54gUS)+ux^mRJi7K7H&t;`6=#ip=nd|Pt}9kkb>eW7CWq0k|3%%QHZKy|Fzux}Q+ z681Jae2L^>sQG6U-GXcRC1Aj)Be6rLp z`N>rF2HeJZJGvQH=;#g)jMJN!tBwW}ynL2rTy4Mrb<(a4I`^6AkRctkBg*?KwSep38f?cBs962+H}e7ak?27&^;~~b0+sjI zwBT|BwQ1^6Wn;usVWo1jCsl2IsVf~xl@`ZP(?y5&Q-jl`F5$TaYd0R+GJkfH&?tvOGD8fG&!HpZ2U-ZWw?agOPnYc+jcwo{LZuj-+ z727>3(i%iQZKAv-G&tPOvhwQMvjs(k$Fs5zpU*z=B^Od$i?mv&)8qdDSN|DNKErLE P00000NkvXXu0mjf?8@U# literal 0 HcmV?d00001 diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..1b0d8fc --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/create-assoc-member.tsx b/src/components/create-assoc-member.tsx new file mode 100644 index 0000000..856482a --- /dev/null +++ b/src/components/create-assoc-member.tsx @@ -0,0 +1,130 @@ +"use client" +import { useMutation } from "@tanstack/react-query" +import { useState } from "react" +import { toast } from "sonner" +import { + Sheet, + SheetClose, + SheetContent, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" +import { useTRPC } from "@/lib/trpc/client" +import { Button } from "./ui/button" +import { Input } from "./ui/input" +import { Label } from "./ui/label" + +type Props = { + trigger?: React.ReactNode +} + +export function CreateAssocUser({ trigger }: Props) { + const [open, setOpen] = useState(false) + const [firstName, setFirstName] = useState("") + const [lastName, setLastName] = useState("") + const [assocNumber, setAssocNumber] = useState("") + const [sendTo, setSendTo] = useState("") + const trpc = useTRPC() + + const { mutateAsync, isPending } = useMutation(trpc.azure.members.create.mutationOptions()) + + function handleOpenChange(v: boolean): void { + setOpen(v) + setFirstName("") + setLastName("") + setAssocNumber("") + setSendTo("") + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!assocNumber || Number.isNaN(parseInt(assocNumber, 10))) return + + const res = await mutateAsync({ assocNumber: parseInt(assocNumber, 10), firstName, lastName, sendEmailTo: sendTo }) + if (res.error !== null) { + toast.error(res.error) + return + } + + console.log("Created user", res) + toast.success(`User created, email: ${res.email}`, { duration: 10_000 }) + } + + return ( + + {trigger ?? } + + + New Assoc Member + +
+
+
+ + setFirstName(e.target.value)} + /> +
+
+ + setLastName(e.target.value)} + /> +
+ +
+ + setAssocNumber(e.target.value)} + /> +
+ +
+ + setSendTo(e.target.value)} + /> +
+
+ + + + + + +
+
+
+ ) +} diff --git a/src/components/header-login-button.tsx b/src/components/header-login-button.tsx new file mode 100644 index 0000000..03c8259 --- /dev/null +++ b/src/components/header-login-button.tsx @@ -0,0 +1,44 @@ +"use client" +import { LogIn, LogOut } from "lucide-react" +import Link from "next/link" +import { usePathname, useRouter } from "next/navigation" +import { auth, useSession } from "@/lib/auth" +import { Button } from "./ui/button" +import { Skeleton } from "./ui/skeleton" +import { useIsMobile } from "@/hooks/use-mobile" + +export function HeaderLoginButton() { + const { data, isPending } = useSession() + const pathname = usePathname() + const isMobile = useIsMobile() + const router = useRouter() + + if (isPending) return + if (data) + return ( + + ) + + return ( + + + + ) +} diff --git a/src/components/header.tsx b/src/components/header.tsx index 47c5bb0..c35022e 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,36 +1,31 @@ import { GlobeIcon } from "lucide-react" -import Image from "next/image" import Link from "next/link" -import logo from "@/assets/svg/logo.svg" import { ThemeButton } from "@/components/theme-button" import { Button } from "./ui/button" +import { Logo } from "./logo" +import { HeaderLoginButton } from "./header-login-button" export const HEADER_HEIGHT = "3.3rem" export async function Header() { return ( -
-
- -
- PoliNetwork Logo -

PoliNetwork Admin

-
- - - -
+
+ +
+ +

+ PoliNetwork Admin +

+
+ + +
) } diff --git a/src/components/logo.tsx b/src/components/logo.tsx new file mode 100644 index 0000000..f51fb95 --- /dev/null +++ b/src/components/logo.tsx @@ -0,0 +1,6 @@ +import Image, { type ImageProps } from "next/image" +import logo from "@/assets/logo.svg" + +export function Logo({ size, ...props }: { size?: number } & Omit) { + return PoliNetwork Logo +} diff --git a/src/components/sidebar/admin-sidebar.tsx b/src/components/sidebar/admin-sidebar.tsx index ef5fb96..12bbaab 100644 --- a/src/components/sidebar/admin-sidebar.tsx +++ b/src/components/sidebar/admin-sidebar.tsx @@ -28,6 +28,12 @@ const data = { }, ], }, + { + title: "Assoc", + url: "/dashboard/assoc", + icon: Users, + // items: [], + }, { title: "Management", //url: "/dashboard/management", diff --git a/src/components/theme-button.tsx b/src/components/theme-button.tsx index fbebe64..cbaa0ad 100644 --- a/src/components/theme-button.tsx +++ b/src/components/theme-button.tsx @@ -5,8 +5,10 @@ import { Button } from "./ui/button" export function ThemeButton() { const { resolvedTheme, setTheme } = useTheme() + + // TODO: remove disabled when light theme is ready return ( - diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index abfc91b..a841b07 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-30 [&_svg]:pointer-events-none [&_svg]:shrink-0", + "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-default disabled:opacity-30 [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { @@ -36,7 +36,7 @@ const buttonVariants = cva( export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps { + VariantProps { asChild?: boolean; } diff --git a/src/index.css b/src/index.css index 8784f7b..dc1d8d4 100644 --- a/src/index.css +++ b/src/index.css @@ -65,7 +65,7 @@ } .dark { - --background: oklch(0.22 0.03 249.42); + --background: oklch(0.19 0.03 256.45); --foreground: oklch(0.88 0.03 277.2); --card: oklch(0.24 0.03 259.05); --card-foreground: oklch(0.9 0.03 255.59); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index bac714a..50723e2 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,7 @@ import { AUTH_PATH, type TelegramPlugin } from "@polinetwork/backend" import type { BetterAuthClientPlugin } from "better-auth" import { emailOTPClient } from "better-auth/client/plugins" +import { passkeyClient } from "@better-auth/passkey/client" import { nextCookies } from "better-auth/next-js" import { createAuthClient } from "better-auth/react" import { getBaseUrl } from "./utils" @@ -15,7 +16,7 @@ const telegramPlugin = () => { export const auth = createAuthClient({ baseURL: getBaseUrl(), basePath: AUTH_PATH, - plugins: [telegramPlugin(), emailOTPClient(), nextCookies()], + plugins: [telegramPlugin(), emailOTPClient(), nextCookies(), passkeyClient()], }) export const { signIn, signOut, getSession, useSession } = auth From d931be5429334e969fa654fdad37b26db3830890 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Wed, 4 Feb 2026 02:59:17 +0100 Subject: [PATCH 02/11] style: assoc member wrong cols --- src/app/dashboard/(active)/assoc/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/dashboard/(active)/assoc/page.tsx b/src/app/dashboard/(active)/assoc/page.tsx index 754191b..62ea733 100644 --- a/src/app/dashboard/(active)/assoc/page.tsx +++ b/src/app/dashboard/(active)/assoc/page.tsx @@ -15,7 +15,7 @@ export default async function AssocIndex() { .sort((a, b) => parseInt(a.employeeId!, 10) - parseInt(b.employeeId!, 10)) // .sort((a, b) => a.displayName!.localeCompare(b.displayName!)) .map((m) => ( -
+
{m.displayName} {m.employeeId ?? "N/A"} {m.mail} From 45d73d0c211df386c958a9fb2b30d2b7d6c0a7a8 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Thu, 5 Feb 2026 20:34:03 +0100 Subject: [PATCH 03/11] fix: sidebar state persistance, tRPC RQ hydration --- src/app/dashboard/(active)/account/page.tsx | 8 ++++---- .../(active)/account/passkey-button.tsx | 4 ++-- src/app/dashboard/layout.tsx | 6 +++++- src/app/layout.tsx | 13 ++++++++----- src/app/login/login-form.tsx | 6 +++--- src/app/login/page.tsx | 4 +++- src/app/onboarding/link/telegram.tsx | 2 +- src/app/page.tsx | 1 - src/components/data-table.tsx | 13 ++++++++++--- src/components/header-login-button.tsx | 2 +- src/components/header.tsx | 4 ++-- src/components/ui/sidebar.tsx | 6 +++--- src/lib/auth.ts | 2 +- src/lib/trpc/server.tsx | 17 ++++++++++++++++- src/lib/trpc/types.tsx | 6 ++++++ 15 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 src/lib/trpc/types.tsx diff --git a/src/app/dashboard/(active)/account/page.tsx b/src/app/dashboard/(active)/account/page.tsx index 6152ce7..4f71631 100644 --- a/src/app/dashboard/(active)/account/page.tsx +++ b/src/app/dashboard/(active)/account/page.tsx @@ -1,13 +1,13 @@ import { Calendar, CircleAlert, KeyIcon, UserIcon } from "lucide-react" +import { headers } from "next/headers" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { auth } from "@/lib/auth" import { getInitials } from "@/lib/utils" import { getServerSession } from "@/server/auth" +import { NewPasskeyButton } from "./passkey-button" import { SetName } from "./set-name" import { Telegram } from "./telegram" -import { NewPasskeyButton } from "./passkey-button" -import { auth } from "@/lib/auth" -import { headers } from "next/headers" -import { Button } from "@/components/ui/button" export default async function Account() { const { data: session } = await getServerSession() diff --git a/src/app/dashboard/(active)/account/passkey-button.tsx b/src/app/dashboard/(active)/account/passkey-button.tsx index b4d96a4..0cd4836 100644 --- a/src/app/dashboard/(active)/account/passkey-button.tsx +++ b/src/app/dashboard/(active)/account/passkey-button.tsx @@ -1,10 +1,10 @@ "use client" +import { useRouter } from "next/navigation" +import { useState } from "react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { auth } from "@/lib/auth" -import { useRouter } from "next/navigation" -import { useState } from "react" export function NewPasskeyButton() { const router = useRouter() diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index f5e9888..c0316b3 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,3 +1,4 @@ +import { cookies } from "next/headers" import { redirect } from "next/navigation" import { SidebarProvider } from "@/components/ui/sidebar" import { getQueryClient, trpc } from "@/lib/trpc/server" @@ -18,5 +19,8 @@ export default async function AdminLayout({ children }: { children: React.ReactN if (!roles || roles.length === 0) redirect("/onboarding/no-role") if (roles.includes("creator")) redirect("/onboarding/unauthorized") - return {children} + const cookieStore = await cookies() + const defaultOpen = cookieStore.get("sidebar:state")?.value === "true" + + return {children} } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 48bc746..ef158bb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,11 +1,12 @@ -import { Poppins } from "next/font/google" import type { Metadata } from "next" +import { Poppins } from "next/font/google" import "@/index.css" import { HEADER_HEIGHT, Header } from "@/components/header" import { ThemeProvider } from "@/components/theme-provider" import { Toaster } from "@/components/ui/sonner" import { TooltipProvider } from "@/components/ui/tooltip" import { TRPCReactProvider } from "@/lib/trpc/client" +import { HydrateClient } from "@/lib/trpc/server" const desc = "PoliNetwork Admin Dashboard" @@ -42,10 +43,12 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac > -
-
- {children} -
+ +
+
+ {children} +
+
diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx index 532d395..feae630 100644 --- a/src/app/login/login-form.tsx +++ b/src/app/login/login-form.tsx @@ -1,8 +1,8 @@ "use client" -import { ArrowRight, KeyRound, Loader2 } from "lucide-react" +import { ArrowRight, Loader2 } from "lucide-react" import { useRouter } from "next/navigation" -import { useEffect, useState } from "react" +import { useState } from "react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -13,7 +13,7 @@ import { auth } from "@/lib/auth" export default function LoginForm() { const [email, setEmail] = useState("") const [sent, setSent] = useState(false) - const router = useRouter() + const _router = useRouter() // useEffect(() => { // if (!PublicKeyCredential.isConditionalMediationAvailable || diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index b23c53a..c4c10db 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -9,7 +9,9 @@ export default async function Page() { return (

Login

-

Enter your email below to recieve an OTP to login or use a registered passkey.

+

+ Enter your email below to recieve an OTP to login or use a registered passkey. +

) diff --git a/src/app/onboarding/link/telegram.tsx b/src/app/onboarding/link/telegram.tsx index ba5c468..7f33a13 100644 --- a/src/app/onboarding/link/telegram.tsx +++ b/src/app/onboarding/link/telegram.tsx @@ -3,6 +3,7 @@ import { APIError } from "better-auth/api" import { CircleCheckBig, ClockAlertIcon } from "lucide-react" import { useRouter } from "next/navigation" import { useCallback, useEffect, useState } from "react" +import { toast } from "sonner" import { Code } from "@/components/code" import { InputWithPrefix } from "@/components/input-prefix" import { Button } from "@/components/ui/button" @@ -11,7 +12,6 @@ import { Progress } from "@/components/ui/progress" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { useLocalStorage } from "@/hooks/use-local-storage" import { auth, useSession } from "@/lib/auth" -import { toast } from "sonner" type SavedLink = { username: string diff --git a/src/app/page.tsx b/src/app/page.tsx index ca01951..a2711ff 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,6 @@ import { redirect } from "next/navigation" import { getServerSession } from "@/server/auth" import { CanIAccess } from "./login/can-i-access" -import { LoginButton } from "./login/login-button" import { WhatIs } from "./login/what-is" export default async function IndexPage() { diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx index bdd6798..eba2000 100644 --- a/src/components/data-table.tsx +++ b/src/components/data-table.tsx @@ -1,15 +1,22 @@ "use client" -import { type ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table" +import { + type ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + type RowData, + useReactTable, +} from "@tanstack/react-table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -interface DataTableProps { +interface DataTableProps { columns: ColumnDef[] data: TData[] } -export function DataTable({ columns, data }: DataTableProps) { +export function DataTable({ columns, data }: DataTableProps) { const table = useReactTable({ data, columns, diff --git a/src/components/header-login-button.tsx b/src/components/header-login-button.tsx index 03c8259..61b9d90 100644 --- a/src/components/header-login-button.tsx +++ b/src/components/header-login-button.tsx @@ -2,10 +2,10 @@ import { LogIn, LogOut } from "lucide-react" import Link from "next/link" import { usePathname, useRouter } from "next/navigation" +import { useIsMobile } from "@/hooks/use-mobile" import { auth, useSession } from "@/lib/auth" import { Button } from "./ui/button" import { Skeleton } from "./ui/skeleton" -import { useIsMobile } from "@/hooks/use-mobile" export function HeaderLoginButton() { const { data, isPending } = useSession() diff --git a/src/components/header.tsx b/src/components/header.tsx index c35022e..55c341e 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,9 +1,9 @@ import { GlobeIcon } from "lucide-react" import Link from "next/link" import { ThemeButton } from "@/components/theme-button" -import { Button } from "./ui/button" -import { Logo } from "./logo" import { HeaderLoginButton } from "./header-login-button" +import { Logo } from "./logo" +import { Button } from "./ui/button" export const HEADER_HEIGHT = "3.3rem" diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 050e3d5..7032ed5 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -84,7 +84,7 @@ const SidebarProvider = React.forwardRef< } // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE} SameSite=Lax`; }, [setOpenProp, open], ); @@ -623,7 +623,7 @@ const SidebarMenuAction = React.forwardRef< "peer-data-[size=lg]/menu-button:top-2.5", "group-data-[collapsible=icon]:hidden", showOnHover && - "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", + "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", className, )} {...props} @@ -661,7 +661,7 @@ const SidebarMenuSkeleton = React.forwardRef< >(({ className, showIcon = false, ...props }, ref) => { // Random width between 50 to 90%. const width = React.useMemo(() => { - return `${Math.floor(Math.random() * 40) + 50}%`; + return `${Math.floor(Math.random() * 40) + 50}% `; }, []); return ( diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 50723e2..f00cd8e 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,7 +1,7 @@ +import { passkeyClient } from "@better-auth/passkey/client" import { AUTH_PATH, type TelegramPlugin } from "@polinetwork/backend" import type { BetterAuthClientPlugin } from "better-auth" import { emailOTPClient } from "better-auth/client/plugins" -import { passkeyClient } from "@better-auth/passkey/client" import { nextCookies } from "better-auth/next-js" import { createAuthClient } from "better-auth/react" import { getBaseUrl } from "./utils" diff --git a/src/lib/trpc/server.tsx b/src/lib/trpc/server.tsx index 05b288b..93d09af 100644 --- a/src/lib/trpc/server.tsx +++ b/src/lib/trpc/server.tsx @@ -1,8 +1,9 @@ import "server-only" // <-- ensure this file cannot be imported from the client import { type AppRouter, TRPC_PATH } from "@polinetwork/backend" +import { dehydrate, HydrationBoundary } from "@tanstack/react-query" import { createTRPCClient, httpLink } from "@trpc/client" -import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query" +import { createTRPCOptionsProxy, type TRPCQueryOptions } from "@trpc/tanstack-react-query" import { cache } from "react" import SuperJSON from "superjson" import { getBaseUrl } from "../utils" @@ -16,3 +17,17 @@ export const trpc = createTRPCOptionsProxy({ }), queryClient: getQueryClient, }) +export function HydrateClient(props: { children: React.ReactNode }) { + const queryClient = getQueryClient() + return {props.children} +} +// biome-ignore lint/suspicious/noExplicitAny: tRPC docs +export function prefetch>>(queryOptions: T) { + const queryClient = getQueryClient() + if (queryOptions.queryKey[1]?.type === "infinite") { + // biome-ignore lint/suspicious/noExplicitAny: tRPC docs + void queryClient.prefetchInfiniteQuery(queryOptions as any) + } else { + void queryClient.prefetchQuery(queryOptions) + } +} diff --git a/src/lib/trpc/types.tsx b/src/lib/trpc/types.tsx new file mode 100644 index 0000000..ca397f6 --- /dev/null +++ b/src/lib/trpc/types.tsx @@ -0,0 +1,6 @@ +import type { AppRouter } from "@polinetwork/backend" +import type { inferRouterError, inferRouterInputs, inferRouterOutputs } from "@trpc/server" + +export type ApiOutput = inferRouterOutputs +export type ApiInput = inferRouterInputs +export type ApiError = inferRouterError From 3353f0e7b0fb92598637d98d27ccebcf9e211d86 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Thu, 5 Feb 2026 20:34:32 +0100 Subject: [PATCH 04/11] feat: assoc users table, dialog to set assoc number --- package.json | 3 +- pnpm-lock.yaml | 14 ++- .../_components/set-assoc-number-dialog.tsx | 90 +++++++++++++++++++ src/app/dashboard/(active)/assoc/columns.tsx | 60 +++++++++++++ src/app/dashboard/(active)/assoc/page.tsx | 30 +++---- src/app/dashboard/(active)/assoc/table.tsx | 54 +++++++++++ src/components/ui/badge.tsx | 36 ++++++++ 7 files changed, 266 insertions(+), 21 deletions(-) create mode 100644 src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx create mode 100644 src/app/dashboard/(active)/assoc/columns.tsx create mode 100644 src/app/dashboard/(active)/assoc/table.tsx create mode 100644 src/components/ui/badge.tsx diff --git a/package.json b/package.json index 1bca3c7..31eac10 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@radix-ui/react-progress": "^1.1.3", "@radix-ui/react-select": "^2.1.4", "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@t3-oss/env-nextjs": "^0.13.10", "@tanstack/react-query": "^5.90.19", @@ -48,6 +48,7 @@ "postgres": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^6.1.0", "react-hook-form": "^7.55.0", "server-only": "^0.0.1", "sonner": "^2.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2900802..7f7f662 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,7 +48,7 @@ importers: specifier: ^1.1.0 version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': - specifier: ^1.1.0 + specifier: ^1.1.2 version: 1.1.2(@types/react@18.3.18)(react@18.3.1) '@radix-ui/react-tooltip': specifier: ^1.1.4 @@ -113,6 +113,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-error-boundary: + specifier: ^6.1.0 + version: 6.1.0(react@18.3.1) react-hook-form: specifier: ^7.55.0 version: 7.55.0(react@18.3.1) @@ -1598,6 +1601,11 @@ packages: peerDependencies: react: ^18.3.1 + react-error-boundary@6.1.0: + resolution: {integrity: sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-hook-form@7.55.0: resolution: {integrity: sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==} engines: {node: '>=18.0.0'} @@ -3002,6 +3010,10 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-error-boundary@6.1.0(react@18.3.1): + dependencies: + react: 18.3.1 + react-hook-form@7.55.0(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx b/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx new file mode 100644 index 0000000..843d688 --- /dev/null +++ b/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx @@ -0,0 +1,90 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useRouter } from "next/navigation" +import { useState } from "react" +import { toast } from "sonner" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { useTRPC } from "@/lib/trpc/client" + +export function SetAssocNumberDialog({ userId, children }: { userId: string; children?: React.ReactNode }) { + const [value, setValue] = useState("") + const [open, setOpen] = useState(false) + const trpc = useTRPC() + const _router = useRouter() + + const qc = useQueryClient() + const { mutateAsync, isPending } = useMutation(trpc.azure.members.setAssocNumber.mutationOptions()) + + function handleOpenChange(v: boolean): void { + setOpen(v) + setValue("") + } + + async function handleSubmit(e: React.FormEvent) { + console.log("submit") + e.preventDefault() + if (isPending || !value || Number.isNaN(parseInt(value, 10))) return + + const res = await mutateAsync({ userId, assocNumber: parseInt(value, 10) }) + handleOpenChange(false) + if (res.error !== null) { + toast.error("There was an error") + console.error(res.error) + return + } + + console.log("Updated user assocNumber", userId, value) + await qc.invalidateQueries({ queryKey: trpc.azure.members.getAll.queryKey() }) + toast.success(`Updated successfully!`) + } + + return ( + + {children ?? } + + + Set Assoc Number + This changes the `employeeId` field in the Azure User properties. + +
+
+ + setValue(e.target.value)} + /> +
+ + + + + + +
+
+
+ ) +} diff --git a/src/app/dashboard/(active)/assoc/columns.tsx b/src/app/dashboard/(active)/assoc/columns.tsx new file mode 100644 index 0000000..9de908f --- /dev/null +++ b/src/app/dashboard/(active)/assoc/columns.tsx @@ -0,0 +1,60 @@ +"use client" +import { createColumnHelper, type Row } from "@tanstack/react-table" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import type { ApiOutput } from "@/lib/trpc/types" +import { SetAssocNumberDialog } from "./_components/set-assoc-number-dialog" + +type ParsedUser = ApiOutput["azure"]["members"]["getAll"][0] +const ch = createColumnHelper() + +export const columns = [ + ch.accessor("employeeId", { + id: "number", + header: "#", + footer: (props) => props.column.id, + cell: ({ getValue, row }) => { + const value = getValue() + return value ? ( + {value} + ) : ( + + + + ) + }, + }), + ch.accessor("displayName", { + id: "displayName", + header: "Full Name", + cell: ({ getValue, row }) => ( + <> + {row.original.isMember && Socio} + {getValue()} + + ), + }), + ch.accessor("mail", { id: "mail", header: "Email" }), + ch.accessor("assignedLicensesIds", { + id: "licenses", + header: "Licenses", + cell: ({ getValue }) => { + const licenses = getValue().sort((a, b) => a.localeCompare(b)) + return licenses.map((l) => ( + + {l} + + )) + }, + }), + ch.group({ + id: "actions", + cell: (props) => , + }), +] + +function RowActions({ row }: { row: Row }) { + return
+} diff --git a/src/app/dashboard/(active)/assoc/page.tsx b/src/app/dashboard/(active)/assoc/page.tsx index 62ea733..477c03b 100644 --- a/src/app/dashboard/(active)/assoc/page.tsx +++ b/src/app/dashboard/(active)/assoc/page.tsx @@ -1,27 +1,19 @@ -import { CreateAssocUser } from "@/components/create-assoc-member" +import { Suspense } from "react" +import { ErrorBoundary } from "react-error-boundary" +import { Spinner } from "@/components/spinner" import { getQueryClient, trpc } from "@/lib/trpc/server" +import { AssocTable } from "./table" export default async function AssocIndex() { const qc = getQueryClient() - const members = await qc.fetchQuery(trpc.azure.members.getAll.queryOptions()) - if (members.error) return
Error: {members.error}
- + void qc.prefetchQuery(trpc.azure.members.getAll.queryOptions()) return ( -
-

{members.members.length} soci registrati

- - {members.members - // biome-ignore lint/style/noNonNullAssertion: got it - .sort((a, b) => parseInt(a.employeeId!, 10) - parseInt(b.employeeId!, 10)) - // .sort((a, b) => a.displayName!.localeCompare(b.displayName!)) - .map((m) => ( -
- {m.displayName} - {m.employeeId ?? "N/A"} - {m.mail} - {m.assignedLicensesIds.join(", ")} -
- ))} +
+ Something went wrong
}> + }> + + +
) } diff --git a/src/app/dashboard/(active)/assoc/table.tsx b/src/app/dashboard/(active)/assoc/table.tsx new file mode 100644 index 0000000..765da54 --- /dev/null +++ b/src/app/dashboard/(active)/assoc/table.tsx @@ -0,0 +1,54 @@ +"use client" + +import { useSuspenseQuery } from "@tanstack/react-query" +import { CreateAssocUser } from "@/components/create-assoc-member" +import { DataTable } from "@/components/data-table" +import { Badge } from "@/components/ui/badge" +import { useTRPC } from "@/lib/trpc/client" +import type { ApiOutput } from "@/lib/trpc/types" +import { columns } from "./columns" + +type ParsedUser = ApiOutput["azure"]["members"]["getAll"][0] +function sortByAssocNumber(users: ParsedUser[]) { + if (!users || users.length === 0) return users + if (users.every((u) => !u.employeeId)) + return users.sort((a, b) => (a.displayName ?? "").localeCompare(b.displayName ?? "")) + + return users.sort((a, b) => { + if (a.employeeId && b.employeeId) { + const aInt = parseInt(a.employeeId, 10) + const bInt = parseInt(b.employeeId, 10) + if (Number.isNaN(aInt) && Number.isNaN(bInt)) return 0 + if (Number.isNaN(aInt)) return 1 + if (Number.isNaN(bInt)) return -1 + return aInt - bInt + } + if (a.employeeId) return -1 + if (b.employeeId) return 1 + return 0 + }) +} + +export function AssocTable() { + const trpc = useTRPC() + const { data: users } = useSuspenseQuery(trpc.azure.members.getAll.queryOptions()) + + return ( +
+
+

Utenti MS @polinetwork.org

+ {users.length} Soci + {users.length} fuori + {users.filter((u) => u.assignedLicensesIds.includes("OFFICE_365")).length} licenze Office +
+ +
+ + +
+ ) +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..b47f184 --- /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-full 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 hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground 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 } From fde92117224d27a9d4801b440ce70e3fb95e329f Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Mon, 16 Mar 2026 12:16:56 +0100 Subject: [PATCH 05/11] fix: wait 2s before updating --- .../assoc/_components/set-assoc-number-dialog.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx b/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx index 843d688..3fb2a41 100644 --- a/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx +++ b/src/app/dashboard/(active)/assoc/_components/set-assoc-number-dialog.tsx @@ -1,5 +1,4 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useRouter } from "next/navigation" import { useState } from "react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -16,12 +15,12 @@ import { import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { useTRPC } from "@/lib/trpc/client" +import { wait } from "@/lib/utils" export function SetAssocNumberDialog({ userId, children }: { userId: string; children?: React.ReactNode }) { const [value, setValue] = useState("") const [open, setOpen] = useState(false) const trpc = useTRPC() - const _router = useRouter() const qc = useQueryClient() const { mutateAsync, isPending } = useMutation(trpc.azure.members.setAssocNumber.mutationOptions()) @@ -36,6 +35,7 @@ export function SetAssocNumberDialog({ userId, children }: { userId: string; chi e.preventDefault() if (isPending || !value || Number.isNaN(parseInt(value, 10))) return + const loading = toast.loading(`Updating...`) const res = await mutateAsync({ userId, assocNumber: parseInt(value, 10) }) handleOpenChange(false) if (res.error !== null) { @@ -44,9 +44,10 @@ export function SetAssocNumberDialog({ userId, children }: { userId: string; chi return } + await wait(2000) + await qc.refetchQueries({ queryKey: trpc.azure.members.getAll.queryKey() }) + toast.success(`Updated successfully!`, { id: loading }) console.log("Updated user assocNumber", userId, value) - await qc.invalidateQueries({ queryKey: trpc.azure.members.getAll.queryKey() }) - toast.success(`Updated successfully!`) } return ( From 890541163a5496586a9cbd3380a94347853ab5e1 Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Wed, 8 Apr 2026 18:16:31 +0200 Subject: [PATCH 06/11] snapshot --- package.json | 10 +- pnpm-lock.yaml | 387 +++++++++++++----- src/app/(auth)/auth-card.tsx | 115 ++++++ src/app/(auth)/layout.tsx | 15 + src/app/{ => (auth)}/login/can-i-access.tsx | 0 src/app/{ => (auth)}/login/login-button.tsx | 0 src/app/{ => (auth)}/login/login-form.tsx | 114 ++++-- src/app/(auth)/login/page.tsx | 19 + src/app/{ => (auth)}/login/what-is.tsx | 0 .../onboarding/_layout.tsx} | 0 .../{ => (auth)}/onboarding/link/logout.tsx | 2 +- src/app/{ => (auth)}/onboarding/link/page.tsx | 0 .../{ => (auth)}/onboarding/link/telegram.tsx | 0 .../{ => (auth)}/onboarding/no-role/page.tsx | 0 src/app/(auth)/signup/page.tsx | 53 +++ src/app/layout.tsx | 5 +- src/app/login/page.tsx | 18 - src/app/page.tsx | 16 +- src/assets/svg/shapes/big-blue.svg | 1 + src/assets/svg/shapes/big-teal.svg | 1 + src/assets/svg/shapes/looper.svg | 1 + src/assets/svg/shapes/small-blue.svg | 1 + src/components/create-tg-user.tsx | 130 ------ src/components/select-users.tsx | 98 ----- src/components/shapes.tsx | 32 ++ src/components/ui/field.tsx | 244 +++++++++++ src/server/actions/auth.ts | 28 ++ 27 files changed, 903 insertions(+), 387 deletions(-) create mode 100644 src/app/(auth)/auth-card.tsx create mode 100644 src/app/(auth)/layout.tsx rename src/app/{ => (auth)}/login/can-i-access.tsx (100%) rename src/app/{ => (auth)}/login/login-button.tsx (100%) rename src/app/{ => (auth)}/login/login-form.tsx (59%) create mode 100644 src/app/(auth)/login/page.tsx rename src/app/{ => (auth)}/login/what-is.tsx (100%) rename src/app/{onboarding/layout.tsx => (auth)/onboarding/_layout.tsx} (100%) rename src/app/{ => (auth)}/onboarding/link/logout.tsx (94%) rename src/app/{ => (auth)}/onboarding/link/page.tsx (100%) rename src/app/{ => (auth)}/onboarding/link/telegram.tsx (100%) rename src/app/{ => (auth)}/onboarding/no-role/page.tsx (100%) create mode 100644 src/app/(auth)/signup/page.tsx delete mode 100644 src/app/login/page.tsx create mode 100644 src/assets/svg/shapes/big-blue.svg create mode 100644 src/assets/svg/shapes/big-teal.svg create mode 100644 src/assets/svg/shapes/looper.svg create mode 100644 src/assets/svg/shapes/small-blue.svg delete mode 100644 src/components/create-tg-user.tsx delete mode 100644 src/components/select-users.tsx create mode 100644 src/components/shapes.tsx create mode 100644 src/components/ui/field.tsx create mode 100644 src/server/actions/auth.ts diff --git a/package.json b/package.json index 31eac10..ea99c37 100644 --- a/package.json +++ b/package.json @@ -13,19 +13,19 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@better-auth/passkey": "^1.4.17", + "@better-auth/passkey": "^1.5.5", "@hookform/resolvers": "^3.9.1", - "@polinetwork/backend": "file:../backend/package/dist/", + "@polinetwork/backend": "^0.15.5", "@radix-ui/react-alert-dialog": "^1.1.3", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "1.1.4", "@radix-ui/react-progress": "^1.1.3", "@radix-ui/react-select": "^2.1.4", - "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@t3-oss/env-nextjs": "^0.13.10", @@ -36,7 +36,7 @@ "@trpc/react-query": "11.5.1", "@trpc/tanstack-react-query": "11.5.1", "babel-plugin-react-compiler": "1.0.0", - "better-auth": "^1.4.17", + "better-auth": "^1.5.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f7f662..6090cf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@better-auth/passkey': - specifier: ^1.4.17 - version: 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.1.8(zod@4.3.5))(nanostores@1.1.0) + specifier: ^1.5.5 + version: 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(mongodb@7.1.0)(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0) '@hookform/resolvers': specifier: ^3.9.1 version: 3.10.0(react-hook-form@7.55.0(react@18.3.1)) '@polinetwork/backend': - specifier: file:../backend/package/dist/ - version: dist@file:../backend/package/dist + specifier: ^0.15.5 + version: 0.15.5 '@radix-ui/react-alert-dialog': specifier: ^1.1.3 version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -33,7 +33,7 @@ importers: specifier: ^2.1.2 version: 2.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-label': - specifier: ^2.1.1 + specifier: ^2.1.2 version: 2.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-popover': specifier: 1.1.4 @@ -45,7 +45,7 @@ importers: specifier: ^2.1.4 version: 2.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-separator': - specifier: ^1.1.0 + specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.2 @@ -78,8 +78,8 @@ importers: specifier: 1.0.0 version: 1.0.0 better-auth: - specifier: ^1.4.17 - version: 1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.5.5 + version: 1.5.5(mongodb@7.1.0)(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -187,33 +187,80 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@better-auth/core@1.4.17': - resolution: {integrity: sha512-WSaEQDdUO6B1CzAmissN6j0lx9fM9lcslEYzlApB5UzFaBeAOHNUONTdglSyUs6/idiZBoRvt0t/qMXCgIU8ug==} + '@better-auth/core@1.5.5': + resolution: {integrity: sha512-1oR/2jAp821Dcf67kQYHUoyNcdc1TcShfw4QMK0YTVntuRES5mUOyvEJql5T6eIuLfaqaN4LOF78l0FtF66HXA==} peerDependencies: - '@better-auth/utils': 0.3.0 + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-call: 1.1.8 + '@cloudflare/workers-types': '>=4' + better-call: 1.3.2 jose: ^6.1.0 kysely: ^0.28.5 nanostores: ^1.0.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + '@better-auth/drizzle-adapter@1.5.5': + resolution: {integrity: sha512-HAi9xAP40oDt48QZeYBFTcmg3vt1Jik90GwoRIfangd7VGbxesIIDBJSnvwMbZ52GBIc6+V4FRw9lasNiNrPfw==} + peerDependencies: + '@better-auth/core': 1.5.5 + '@better-auth/utils': ^0.3.0 + drizzle-orm: '>=0.41.0' + peerDependenciesMeta: + drizzle-orm: + optional: true - '@better-auth/passkey@1.4.17': - resolution: {integrity: sha512-SmS7a3UYi9A0muo4fWx1wEn59vtjoOLBFD5PnRo330BcXro6mHWxG/IXtnOnyeQKNGnEp4X7/QXcRrW/nDH0Iw==} + '@better-auth/kysely-adapter@1.5.5': + resolution: {integrity: sha512-LmHffIVnqbfsxcxckMOoE8MwibWrbVFch+kwPKJ5OFDFv6lin75ufN7ZZ7twH0IMPLT/FcgzaRjP8jRrXRef9g==} peerDependencies: - '@better-auth/core': 1.4.17 - '@better-auth/utils': 0.3.0 + '@better-auth/core': 1.5.5 + '@better-auth/utils': ^0.3.0 + kysely: ^0.27.0 || ^0.28.0 + + '@better-auth/memory-adapter@1.5.5': + resolution: {integrity: sha512-4X0j1/2L+nsgmObjmy9xEGUFWUv38Qjthp558fwS3DAp6ueWWyCaxaD6VJZ7m5qPNMrsBStO5WGP8CmJTEWm7g==} + peerDependencies: + '@better-auth/core': 1.5.5 + '@better-auth/utils': ^0.3.0 + + '@better-auth/mongo-adapter@1.5.5': + resolution: {integrity: sha512-P1J9ljL5X5k740I8Rx1esPWNgWYPdJR5hf2CY7BwDSrQFPUHuzeCg0YhtEEP55niNateTXhBqGAcy0fVOeamZg==} + peerDependencies: + '@better-auth/core': 1.5.5 + '@better-auth/utils': ^0.3.0 + mongodb: ^6.0.0 || ^7.0.0 + + '@better-auth/passkey@1.5.5': + resolution: {integrity: sha512-waGngvVophgoi/yqyU8fPZS04ZRMfjPBlxRlbV49nOgqFXcA9+914cGUedmaePXlTH6q6Z9K3dXUlg8H4g5tTQ==} + peerDependencies: + '@better-auth/core': 1.5.5 + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-auth: 1.4.17 - better-call: 1.1.8 + better-auth: 1.5.5 + better-call: 1.3.2 nanostores: ^1.0.1 - '@better-auth/telemetry@1.4.17': - resolution: {integrity: sha512-R1BC4e/bNjQbXu7lG6ubpgmsPj7IMqky5DvMlzAtnAJWJhh99pMh/n6w5gOHa0cqDZgEAuj75IPTxv+q3YiInA==} + '@better-auth/prisma-adapter@1.5.5': + resolution: {integrity: sha512-CliDd78CXHzzwQIXhCdwGr5Ml53i6JdCHWV7PYwTIJz9EAm6qb2RVBdpP3nqEfNjINGM22A6gfleCgCdZkTIZg==} + peerDependencies: + '@better-auth/core': 1.5.5 + '@better-auth/utils': ^0.3.0 + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@prisma/client': + optional: true + prisma: + optional: true + + '@better-auth/telemetry@1.5.5': + resolution: {integrity: sha512-1+lklxArn4IMHuU503RcPdXrSG2tlXt4jnGG3omolmspQ7tktg/Y9XO/yAkYDurtvMn1xJ8X1Ov01Ji/r5s9BQ==} peerDependencies: - '@better-auth/core': 1.4.17 + '@better-auth/core': 1.5.5 - '@better-auth/utils@0.3.0': - resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + '@better-auth/utils@0.3.1': + resolution: {integrity: sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==} '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} @@ -437,6 +484,9 @@ packages: '@levischuck/tiny-cbor@0.2.11': resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@mongodb-js/saslprep@1.4.6': + resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==} + '@next/env@15.5.9': resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} @@ -505,8 +555,8 @@ packages: '@peculiar/asn1-csr@2.6.0': resolution: {integrity: sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==} - '@peculiar/asn1-ecc@2.6.0': - resolution: {integrity: sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==} + '@peculiar/asn1-ecc@2.6.1': + resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==} '@peculiar/asn1-pfx@2.6.0': resolution: {integrity: sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==} @@ -517,8 +567,8 @@ packages: '@peculiar/asn1-pkcs9@2.6.0': resolution: {integrity: sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==} - '@peculiar/asn1-rsa@2.6.0': - resolution: {integrity: sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==} + '@peculiar/asn1-rsa@2.6.1': + resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==} '@peculiar/asn1-schema@2.6.0': resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==} @@ -526,13 +576,16 @@ packages: '@peculiar/asn1-x509-attr@2.6.0': resolution: {integrity: sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==} - '@peculiar/asn1-x509@2.6.0': - resolution: {integrity: sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==} + '@peculiar/asn1-x509@2.6.1': + resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==} '@peculiar/x509@1.14.3': resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} engines: {node: '>=20.0.0'} + '@polinetwork/backend@0.15.5': + resolution: {integrity: sha512-+60RfYsbPUnqpjbCeAMmhjdnBmKuSBQXEGF+YLQOAt7oRWBeW5woN8fA5dj+Ec1fEQjXOxlvDY6dOPiwyzSGMA==} + '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} @@ -1075,8 +1128,8 @@ packages: '@simplewebauthn/browser@13.2.2': resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==} - '@simplewebauthn/server@13.2.2': - resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==} + '@simplewebauthn/server@13.3.0': + resolution: {integrity: sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ==} engines: {node: '>=20.0.0'} '@standard-schema/spec@1.1.0': @@ -1291,6 +1344,12 @@ packages: '@types/react@18.3.18': resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@13.0.0': + resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==} + aria-hidden@1.2.4: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} @@ -1302,8 +1361,8 @@ packages: babel-plugin-react-compiler@1.0.0: resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} - better-auth@1.4.17: - resolution: {integrity: sha512-VmHGQyKsEahkEs37qguROKg/6ypYpNF13D7v/lkbO7w7Aivz0Bv2h+VyUkH4NzrGY0QBKXi1577mGhDCVwp0ew==} + better-auth@1.5.5: + resolution: {integrity: sha512-GpVPaV1eqr3mOovKfghJXXk6QvlcVeFbS3z+n+FPDid5rK/2PchnDtiaVCzWyXA9jH2KkirOfl+JhAUvnja0Eg==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1364,14 +1423,18 @@ packages: vue: optional: true - better-call@1.1.8: - resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} + better-call@1.3.2: + resolution: {integrity: sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw==} peerDependencies: zod: ^4.0.0 peerDependenciesMeta: zod: optional: true + bson@7.2.0: + resolution: {integrity: sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==} + engines: {node: '>=20.19.0'} + caniuse-lite@1.0.30001706: resolution: {integrity: sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==} @@ -1412,9 +1475,6 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dist@file:../backend/package/dist: - resolution: {directory: ../backend/package/dist, type: directory} - enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -1451,8 +1511,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - kysely@0.28.10: - resolution: {integrity: sha512-ksNxfzIW77OcZ+QWSAPC7yDqUSaIVwkTWnTPNiIy//vifNbwsSgQ57OkkncHxxpcBHM3LRfLAZVEh7kjq5twVA==} + kysely@0.28.12: + resolution: {integrity: sha512-kWiueDWXhbCchgiotwXkwdxZE/6h56IHAeFWg4euUfW0YsmO9sxbAxzx1KLLv2lox15EfuuxHQvgJ1qIfZuHGw==} engines: {node: '>=20.0.0'} lightningcss-darwin-arm64@1.29.2: @@ -1528,6 +1588,40 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + + mongodb-connection-string-url@7.0.1: + resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==} + engines: {node: '>=20.19.0'} + + mongodb@7.1.0: + resolution: {integrity: sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.806.0 + '@mongodb-js/zstd': ^7.0.0 + gcp-metadata: ^7.0.1 + kerberos: ^7.0.0 + mongodb-client-encryption: '>=7.0.0 <7.1.0' + snappy: ^7.3.2 + socks: ^2.8.6 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1538,8 +1632,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanostores@1.1.0: - resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} + nanostores@1.2.0: + resolution: {integrity: sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg==} engines: {node: ^20.0.0 || >=22.0.0} next-themes@0.4.4: @@ -1589,6 +1683,10 @@ packages: peerDependencies: react: '>=16.0.0' + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} @@ -1663,8 +1761,8 @@ packages: server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} @@ -1680,6 +1778,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -1718,6 +1819,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -1756,9 +1861,20 @@ packages: '@types/react': optional: true + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + zod@4.3.5: resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -1772,36 +1888,63 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': + '@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0)': dependencies: - '@better-auth/utils': 0.3.0 + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@standard-schema/spec': 1.1.0 - better-call: 1.1.8(zod@4.3.5) + better-call: 1.3.2(zod@4.3.5) jose: 6.1.3 - kysely: 0.28.10 - nanostores: 1.1.0 - zod: 4.3.5 + kysely: 0.28.12 + nanostores: 1.2.0 + zod: 4.3.6 + + '@better-auth/drizzle-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)': + dependencies: + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 - '@better-auth/passkey@1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.1.8(zod@4.3.5))(nanostores@1.1.0)': + '@better-auth/kysely-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(kysely@0.28.12)': dependencies: - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/utils': 0.3.0 + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 + kysely: 0.28.12 + + '@better-auth/memory-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)': + dependencies: + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 + + '@better-auth/mongo-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(mongodb@7.1.0)': + dependencies: + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 + mongodb: 7.1.0 + + '@better-auth/passkey@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.5.5(mongodb@7.1.0)(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(better-call@1.3.2(zod@4.3.5))(nanostores@1.2.0)': + dependencies: + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@simplewebauthn/browser': 13.2.2 - '@simplewebauthn/server': 13.2.2 - better-auth: 1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - better-call: 1.1.8(zod@4.3.5) - nanostores: 1.1.0 - zod: 4.3.5 + '@simplewebauthn/server': 13.3.0 + better-auth: 1.5.5(mongodb@7.1.0)(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + better-call: 1.3.2(zod@4.3.5) + nanostores: 1.2.0 + zod: 4.3.6 + + '@better-auth/prisma-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)': + dependencies: + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 - '@better-auth/telemetry@1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))': + '@better-auth/telemetry@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))': dependencies: - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/utils': 0.3.0 + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - '@better-auth/utils@0.3.0': {} + '@better-auth/utils@0.3.1': {} '@better-fetch/fetch@1.1.21': {} @@ -1967,6 +2110,10 @@ snapshots: '@levischuck/tiny-cbor@0.2.11': {} + '@mongodb-js/saslprep@1.4.6': + dependencies: + sparse-bitfield: 3.0.3 + '@next/env@15.5.9': {} '@next/swc-darwin-arm64@15.5.7': @@ -2006,7 +2153,7 @@ snapshots: '@peculiar/asn1-cms@2.6.0': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 '@peculiar/asn1-x509-attr': 2.6.0 asn1js: 3.0.7 tslib: 2.8.1 @@ -2014,14 +2161,14 @@ snapshots: '@peculiar/asn1-csr@2.6.0': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.6.0': + '@peculiar/asn1-ecc@2.6.1': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 asn1js: 3.0.7 tslib: 2.8.1 @@ -2029,7 +2176,7 @@ snapshots: dependencies: '@peculiar/asn1-cms': 2.6.0 '@peculiar/asn1-pkcs8': 2.6.0 - '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-rsa': 2.6.1 '@peculiar/asn1-schema': 2.6.0 asn1js: 3.0.7 tslib: 2.8.1 @@ -2037,7 +2184,7 @@ snapshots: '@peculiar/asn1-pkcs8@2.6.0': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 asn1js: 3.0.7 tslib: 2.8.1 @@ -2047,15 +2194,15 @@ snapshots: '@peculiar/asn1-pfx': 2.6.0 '@peculiar/asn1-pkcs8': 2.6.0 '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 '@peculiar/asn1-x509-attr': 2.6.0 asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.6.0': + '@peculiar/asn1-rsa@2.6.1': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 asn1js: 3.0.7 tslib: 2.8.1 @@ -2068,11 +2215,11 @@ snapshots: '@peculiar/asn1-x509-attr@2.6.0': dependencies: '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-x509@2.6.0': + '@peculiar/asn1-x509@2.6.1': dependencies: '@peculiar/asn1-schema': 2.6.0 asn1js: 3.0.7 @@ -2083,16 +2230,18 @@ snapshots: dependencies: '@peculiar/asn1-cms': 2.6.0 '@peculiar/asn1-csr': 2.6.0 - '@peculiar/asn1-ecc': 2.6.0 + '@peculiar/asn1-ecc': 2.6.1 '@peculiar/asn1-pkcs9': 2.6.0 - '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-rsa': 2.6.1 '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 pvtsutils: 1.3.6 reflect-metadata: 0.2.2 tslib: 2.8.1 tsyringe: 4.10.0 + '@polinetwork/backend@0.15.5': {} + '@radix-ui/number@1.1.0': {} '@radix-ui/primitive@1.1.1': {} @@ -2613,15 +2762,15 @@ snapshots: '@simplewebauthn/browser@13.2.2': {} - '@simplewebauthn/server@13.2.2': + '@simplewebauthn/server@13.3.0': dependencies: '@hexagon/base64': 1.1.28 '@levischuck/tiny-cbor': 0.2.11 '@peculiar/asn1-android': 2.6.0 - '@peculiar/asn1-ecc': 2.6.0 - '@peculiar/asn1-rsa': 2.6.0 + '@peculiar/asn1-ecc': 2.6.1 + '@peculiar/asn1-rsa': 2.6.1 '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 '@peculiar/x509': 1.14.3 '@standard-schema/spec@1.1.0': {} @@ -2779,6 +2928,12 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@13.0.0': + dependencies: + '@types/webidl-conversions': 7.0.3 + aria-hidden@1.2.4: dependencies: tslib: 2.8.1 @@ -2793,34 +2948,44 @@ snapshots: dependencies: '@babel/types': 7.28.2 - better-auth@1.4.17(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + better-auth@1.5.5(mongodb@7.1.0)(next@15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) - '@better-auth/utils': 0.3.0 + '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0) + '@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1) + '@better-auth/kysely-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(kysely@0.28.12) + '@better-auth/memory-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1) + '@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(mongodb@7.1.0) + '@better-auth/prisma-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0))(@better-auth/utils@0.3.1) + '@better-auth/telemetry': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.12)(nanostores@1.2.0)) + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 '@noble/hashes': 2.0.1 - better-call: 1.1.8(zod@4.3.5) + better-call: 1.3.2(zod@4.3.5) defu: 6.1.4 jose: 6.1.3 - kysely: 0.28.10 - nanostores: 1.1.0 - zod: 4.3.5 + kysely: 0.28.12 + nanostores: 1.2.0 + zod: 4.3.6 optionalDependencies: + mongodb: 7.1.0 next: 15.5.9(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@cloudflare/workers-types' - better-call@1.1.8(zod@4.3.5): + better-call@1.3.2(zod@4.3.5): dependencies: - '@better-auth/utils': 0.3.0 + '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 rou3: 0.7.12 - set-cookie-parser: 2.7.2 + set-cookie-parser: 3.0.1 optionalDependencies: zod: 4.3.5 + bson@7.2.0: {} + caniuse-lite@1.0.30001706: {} class-variance-authority@0.7.1: @@ -2858,8 +3023,6 @@ snapshots: detect-node-es@1.1.0: {} - dist@file:../backend/package/dist: {} - enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -2886,7 +3049,7 @@ snapshots: js-tokens@4.0.0: {} - kysely@0.28.10: {} + kysely@0.28.12: {} lightningcss-darwin-arm64@1.29.2: optional: true @@ -2941,11 +3104,24 @@ snapshots: dependencies: react: 18.3.1 + memory-pager@1.5.0: {} + + mongodb-connection-string-url@7.0.1: + dependencies: + '@types/whatwg-url': 13.0.0 + whatwg-url: 14.2.0 + + mongodb@7.1.0: + dependencies: + '@mongodb-js/saslprep': 1.4.6 + bson: 7.2.0 + mongodb-connection-string-url: 7.0.1 + nanoid@3.3.11: {} nanoid@3.3.8: {} - nanostores@1.1.0: {} + nanostores@1.2.0: {} next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -2998,6 +3174,8 @@ snapshots: clsx: 2.1.1 react: 18.3.1 + punycode@2.3.1: {} + pvtsutils@1.3.6: dependencies: tslib: 2.8.1 @@ -3062,7 +3240,7 @@ snapshots: server-only@0.0.1: {} - set-cookie-parser@2.7.2: {} + set-cookie-parser@3.0.1: {} sharp@0.34.5: dependencies: @@ -3103,6 +3281,10 @@ snapshots: source-map-js@1.2.1: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + styled-jsx@5.1.6(react@18.3.1): dependencies: client-only: 0.0.1 @@ -3129,6 +3311,10 @@ snapshots: tapable@2.2.1: {} + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tslib@1.14.1: {} tslib@2.8.1: {} @@ -3156,4 +3342,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + zod@4.3.5: {} + + zod@4.3.6: {} diff --git a/src/app/(auth)/auth-card.tsx b/src/app/(auth)/auth-card.tsx new file mode 100644 index 0000000..2756093 --- /dev/null +++ b/src/app/(auth)/auth-card.tsx @@ -0,0 +1,115 @@ +import { Logo } from "@/components/logo" +import { Card } from "@/components/ui/card" +import { cn } from "@/lib/utils" +import { Check, CheckCircle2, CircleArrowRight, CircleDashed, Clock } from "lucide-react" +import { PropsWithChildren } from "react" + +type Type = "signup" | "login" +type Props = PropsWithChildren<{ + type: Type + stage: number +}> + +export const MaxStage: Record = { + login: 2, + signup: 4, +} + +export function AuthCard(props: Props) { + return ( +
+
+ +

+ PoliNetwork Auth +

+
+ + +
+ ) +} + +function DesktopCard(props: Props) { + return ( + +
+ {props.type === "login" && } + {props.type === "signup" && } +
+
{props.children}
+
+ ) +} + +function MobileCard(props: Props) { + return ( +
+ + +

Polinetwork Auth

+
+ {props.children} +
+ ) +} + +function Stage({ + children, + currentStage, + activeStage, +}: PropsWithChildren<{ currentStage: number; activeStage: number }>) { + return ( +
svg]:size-8" : "", + currentStage > activeStage ? "text-green-600" : "" + )} + > + {currentStage < activeStage && } + {currentStage === activeStage && } + {currentStage > activeStage && } +

{children}

+
+ ) +} + +function LoginStages({ stage }: { stage: number }) { + return ( +
+

Login

+
+ + Insert Email + + + Authenticate + +
+
+ ) +} + +function SignupStages({ stage }: { stage: number }) { + return ( +
+

Signup

+
+ + Insert Email + + + Profile Setup + + + Review + + + Verification + +
+
+ ) +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..f7109d7 --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,15 @@ +import { Shape } from "@/components/shapes" + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ + + + +
+
+ ) +} diff --git a/src/app/login/can-i-access.tsx b/src/app/(auth)/login/can-i-access.tsx similarity index 100% rename from src/app/login/can-i-access.tsx rename to src/app/(auth)/login/can-i-access.tsx diff --git a/src/app/login/login-button.tsx b/src/app/(auth)/login/login-button.tsx similarity index 100% rename from src/app/login/login-button.tsx rename to src/app/(auth)/login/login-button.tsx diff --git a/src/app/login/login-form.tsx b/src/app/(auth)/login/login-form.tsx similarity index 59% rename from src/app/login/login-form.tsx rename to src/app/(auth)/login/login-form.tsx index feae630..77ac78d 100644 --- a/src/app/login/login-form.tsx +++ b/src/app/(auth)/login/login-form.tsx @@ -1,7 +1,7 @@ "use client" -import { ArrowRight, Loader2 } from "lucide-react" -import { useRouter } from "next/navigation" +import { ArrowRight, Key, Loader2, Mail } from "lucide-react" +import { redirect, useRouter } from "next/navigation" import { useState } from "react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -9,6 +9,10 @@ import { Input } from "@/components/ui/input" import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp" import { Label } from "@/components/ui/label" import { auth } from "@/lib/auth" +import { checkEmail } from "@/server/actions/auth" +import { Separator } from "@/components/ui/separator" +import Link from "next/link" +import { FieldSeparator } from "@/components/ui/field" export default function LoginForm() { const [email, setEmail] = useState("") @@ -36,9 +40,15 @@ export default function LoginForm() { // }, []) return ( -
+
setEmail(v)} onSend={() => setSent(true)} /> {sent && } +

+ Don't have an account? + + Sign up + +

) // Or continue with @@ -55,27 +65,55 @@ function EmailCard({ onSend: () => void }) { const [loading, setLoading] = useState(false) - const router = useRouter() async function sendOtp() { - const { data: passkeyData, error: passkeyError } = await auth.signIn.passkey({ - autoFill: true, - }) + console.log("Called sendOtp") + const checkEmailRes = await checkEmail(email) + + if (checkEmailRes.error) { + toast.error(checkEmailRes.error) + return + } - if (passkeyData && !passkeyError) { - toast.success("Logged in with passkey!") - router.push("/dashboard") + if (!checkEmailRes.exists) { + toast.warning("No user with this email found, redirecting to signup.") + // redirect("/signup") return } + // const { data: passkeyData, error: passkeyError } = await auth.signIn.passkey({ + // autoFill: true, + // fetchOptions: { + // onRequest: () => { + // setLoading(true) + // console.log("call to signIn.passkey REQUEST") + // }, + // onResponse: () => { + // console.log("call to signIn.passkey RESPONSE") + // setLoading(false) + // }, + // }, + // }) + // + // console.log("signIn.passkey RESULT", { passkeyData, passkeyError }) + // + // if (passkeyData && !passkeyError) { + // toast.success("Logged in with passkey!") + // router.push("/dashboard") + // return + // } + + console.log("Call to emailOtp.sendVerificationOtp") const { data, error } = await auth.emailOtp.sendVerificationOtp({ type: "sign-in", email, fetchOptions: { onRequest: () => { setLoading(true) + console.log("call to emailTop.sendVerificationOtp REQUEST") }, onResponse: () => { + console.log("call to emailTop.sendVerificationOtp RESPONSE") setLoading(false) }, }, @@ -83,10 +121,13 @@ function EmailCard({ if (data?.success) { onSend() + console.log("call to emailTop.sendVerificationOtp SUCCESS", { data, error }) toast.success("OTP sent via email!") return } + console.log("call to emailTop.sendVerificationOtp ERROR", { data, error }) + if (error?.code === "INVALID_EMAIL") { toast.error("Invalid email") return @@ -99,7 +140,7 @@ function EmailCard({ return (
{ e.preventDefault() setLoading(true) @@ -107,27 +148,42 @@ function EmailCard({ setLoading(false) }} > -
+
-
- { - onChange(e.target.value) - }} - value={email} - autoComplete="email webauthn" - /> - -
+ { + onChange(e.target.value) + }} + value={email} + autoComplete="email webauthn" + />
+ + Or continue with +
) } diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..12d4e21 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,19 @@ +import { redirect } from "next/navigation" +import { getServerSession } from "@/server/auth" +import LoginForm from "./login-form" +import { Card } from "@/components/ui/card" + +export default async function Page() { + const { data: session } = await getServerSession() + if (session) return redirect("/dashboard") + + return ( +
+ +

Login

+

Login with Email OTP or Passkey

+ +
+
+ ) +} diff --git a/src/app/login/what-is.tsx b/src/app/(auth)/login/what-is.tsx similarity index 100% rename from src/app/login/what-is.tsx rename to src/app/(auth)/login/what-is.tsx diff --git a/src/app/onboarding/layout.tsx b/src/app/(auth)/onboarding/_layout.tsx similarity index 100% rename from src/app/onboarding/layout.tsx rename to src/app/(auth)/onboarding/_layout.tsx diff --git a/src/app/onboarding/link/logout.tsx b/src/app/(auth)/onboarding/link/logout.tsx similarity index 94% rename from src/app/onboarding/link/logout.tsx rename to src/app/(auth)/onboarding/link/logout.tsx index 8cca84f..0b743f8 100644 --- a/src/app/onboarding/link/logout.tsx +++ b/src/app/(auth)/onboarding/link/logout.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation" import { toast } from "sonner" -import { auth } from "../../../lib/auth" +import { auth } from "../../../../lib/auth" export function Logout({ email }: { email: string }) { const router = useRouter() diff --git a/src/app/onboarding/link/page.tsx b/src/app/(auth)/onboarding/link/page.tsx similarity index 100% rename from src/app/onboarding/link/page.tsx rename to src/app/(auth)/onboarding/link/page.tsx diff --git a/src/app/onboarding/link/telegram.tsx b/src/app/(auth)/onboarding/link/telegram.tsx similarity index 100% rename from src/app/onboarding/link/telegram.tsx rename to src/app/(auth)/onboarding/link/telegram.tsx diff --git a/src/app/onboarding/no-role/page.tsx b/src/app/(auth)/onboarding/no-role/page.tsx similarity index 100% rename from src/app/onboarding/no-role/page.tsx rename to src/app/(auth)/onboarding/no-role/page.tsx diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..c8a388f --- /dev/null +++ b/src/app/(auth)/signup/page.tsx @@ -0,0 +1,53 @@ +"use client" +import { ArrowLeft, ArrowRight } from "lucide-react" +import Link from "next/link" +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { AuthCard, MaxStage } from "../auth-card" + +export default function Signup() { + const [stage, setStage] = useState(2) + return ( + <> + + Back to home + + +
+ {stage === 1 && ( +
+

Profile Setup

+

Fake inputs for demo purposes.

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ )} +
+ + +
+
+
+ + ) +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ef158bb..cf4f00a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -44,10 +44,7 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac -
-
- {children} -
+
{children}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx deleted file mode 100644 index c4c10db..0000000 --- a/src/app/login/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { redirect } from "next/navigation" -import { getServerSession } from "@/server/auth" -import LoginForm from "./login-form" - -export default async function Page() { - const { data: session } = await getServerSession() - if (session) return redirect("/dashboard") - - return ( -
-

Login

-

- Enter your email below to recieve an OTP to login or use a registered passkey. -

- -
- ) -} diff --git a/src/app/page.tsx b/src/app/page.tsx index a2711ff..593df2c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,16 +1,20 @@ import { redirect } from "next/navigation" import { getServerSession } from "@/server/auth" -import { CanIAccess } from "./login/can-i-access" -import { WhatIs } from "./login/what-is" +import { CanIAccess } from "./(auth)/login/can-i-access" +import { WhatIs } from "./(auth)/login/what-is" +import { Header } from "@/components/header" export default async function IndexPage() { const session = await getServerSession() if (session.data?.user) redirect("/dashboard") return ( -
- - -
+ <> +
+
+ + +
+ ) } diff --git a/src/assets/svg/shapes/big-blue.svg b/src/assets/svg/shapes/big-blue.svg new file mode 100644 index 0000000..354ec86 --- /dev/null +++ b/src/assets/svg/shapes/big-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/shapes/big-teal.svg b/src/assets/svg/shapes/big-teal.svg new file mode 100644 index 0000000..ffce061 --- /dev/null +++ b/src/assets/svg/shapes/big-teal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/shapes/looper.svg b/src/assets/svg/shapes/looper.svg new file mode 100644 index 0000000..9b118d2 --- /dev/null +++ b/src/assets/svg/shapes/looper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/shapes/small-blue.svg b/src/assets/svg/shapes/small-blue.svg new file mode 100644 index 0000000..feedfff --- /dev/null +++ b/src/assets/svg/shapes/small-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/create-tg-user.tsx b/src/components/create-tg-user.tsx deleted file mode 100644 index 6a82611..0000000 --- a/src/components/create-tg-user.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client" -import type { User } from "better-auth" -import { CircleCheck, CircleX } from "lucide-react" -import { useEffect, useState, useTransition } from "react" -import { - Sheet, - SheetClose, - SheetContent, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet" -import { cn } from "@/lib/utils" -import { Code } from "./code" -import { Button } from "./ui/button" -import { Input } from "./ui/input" -import { Label } from "./ui/label" - -type Props = { - trigger?: React.ReactNode - users: User[] -} - -export function CreateTgUser({ trigger, users }: Props) { - const [open, setOpen] = useState(false) - const [isPending] = useTransition() - const [name, setName] = useState("") - const [username, setUsername] = useState("") - const [validUsername, setValidUsername] = useState() - - function handleOpenChange(v: boolean): void { - setOpen(v) - setName("") - setUsername("") - setValidUsername(undefined) - } - - function handleConfirm(): void { - console.log(name, username) - // TODO - } - - function handleUsernameChange(s: string): void { - const localUsername = s.startsWith("@") ? s.slice(1) : s - setUsername(localUsername.trim()) - setValidUsername(undefined) - } - - useEffect(() => { - const handler = setTimeout(() => { - if (username !== "") { - // const existing = users.find(u => u.tgUsername.toLowerCase() === username.toLowerCase()) ? true : false; - console.error("TODO", users) - const existing = true // TODO - setValidUsername(existing) - } - }, 600) - - // Cleanup function to clear the timeout if the component unmounts - // or if inputValue changes before the delay is over - return () => { - clearTimeout(handler) - } - }, [username, users]) - - return ( - - {trigger ?? } - - - New Telegram User - -
-
- - setName(e.target.value)} - /> -
- -
- -
- handleUsernameChange(e.target.value)} - /> - {validUsername !== undefined && - (validUsername ? : )} -
-

You can insert the username (with or without @).

-
-

- An user with username @{username} has already been registered. -

-
- - - - - - -
-
- ) -} diff --git a/src/components/select-users.tsx b/src/components/select-users.tsx deleted file mode 100644 index d2025b6..0000000 --- a/src/components/select-users.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client" -import { useEffect, useState, useTransition } from "react" -import { - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet" -import { cn } from "@/lib/utils" -import type { UserWithAccountProviders } from "@/server/actions/users" -import { Button } from "./ui/button" - -type Props = { - users: UserWithAccountProviders[] - allowMultipleSelection?: boolean - initialSelectedIds?: string[] - selectAction?: (userIds: string[]) => Promise // must end with "Action" - max?: number -} - -export function SelectUsers({ users, allowMultipleSelection, selectAction, initialSelectedIds = [], max }: Props) { - const [selectedIds, setSelectedIds] = useState(initialSelectedIds) - const [isPending, startTransition] = useTransition() - const [open, setOpen] = useState(false) - - max = allowMultipleSelection ? max : 1 - - useEffect(() => { - setSelectedIds(initialSelectedIds) - }, [initialSelectedIds]) - - async function handleConfirm() { - startTransition(async () => { - await selectAction?.(selectedIds) - handleOpenChange(false) - }) - } - - function handleSelect(id: string) { - const found = selectedIds.find((s) => s === id) - if (found) setSelectedIds((a) => a.filter((s) => s !== id)) - else { - setSelectedIds((a) => (allowMultipleSelection ? [...a, id] : [id])) - } - } - - function handleOpenChange(open: boolean) { - setOpen(open) - if (open === false) setSelectedIds(initialSelectedIds) - } - - return ( - - - - - - - Select {allowMultipleSelection ? "User(s)" : "User"} - {max && Max {max} selection(s)} - -
- {users.map((u) => ( - - ))} -
- - - - - - -
-
- ) -} diff --git a/src/components/shapes.tsx b/src/components/shapes.tsx new file mode 100644 index 0000000..1e1b1f2 --- /dev/null +++ b/src/components/shapes.tsx @@ -0,0 +1,32 @@ +import Image from "next/image" +import bigBlueSvg from "@/assets/svg/shapes/big-blue.svg" +import bigTealSvg from "@/assets/svg/shapes/big-teal.svg" +import looperSvg from "@/assets/svg/shapes/looper.svg" +import smallBlueSvg from "@/assets/svg/shapes/small-blue.svg" +import { cn } from "@/lib/utils" + +export type ShapeVariant = "big-blue" | "big-teal" | "small-blue" | "looper" + +export type ShapeProps = { + variant: ShapeVariant + className?: string +} + +export const Shape: React.FC = ({ variant, className }) => { + const getShapeSrc = () => { + switch (variant) { + case "big-blue": + return bigBlueSvg + case "big-teal": + return bigTealSvg + case "small-blue": + return smallBlueSvg + case "looper": + return looperSvg + default: + return "" + } + } + + return +} diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 0000000..0a276fc --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,244 @@ +"use client" + +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4", + className + )} + {...props} + /> + ) +} + +const fieldVariants = cva( + "group/field data-[invalid=true]:text-destructive flex w-full gap-3", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start", + ], + responsive: [ + "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +