feat(client): profile improvements
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -3,7 +3,7 @@ import pluginQuery from '@tanstack/eslint-plugin-query';
|
||||
|
||||
export default antfu({
|
||||
gitignore: true,
|
||||
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*'],
|
||||
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*', 'src/client/**/*'],
|
||||
react: true,
|
||||
stylistic: {
|
||||
semi: true,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
@@ -11,6 +12,10 @@
|
||||
"gen": "openapi-ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.1.0",
|
||||
"@dicebear/collection": "^9.3.1",
|
||||
"@dicebear/core": "^9.3.1",
|
||||
"@dicebear/identicon": "^9.3.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@@ -25,6 +30,7 @@
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toggle": "^1.1.10",
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
@@ -98,6 +104,5 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "eslint --fix"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
|
||||
}
|
||||
}
|
||||
|
||||
458
client/cms/pnpm-lock.yaml
generated
458
client/cms/pnpm-lock.yaml
generated
@@ -8,6 +8,18 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@base-ui/react':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@dicebear/collection':
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/core':
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1
|
||||
'@dicebear/identicon':
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -50,6 +62,9 @@ importers:
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-switch':
|
||||
specifier: ^1.2.6
|
||||
version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -406,12 +421,229 @@ packages:
|
||||
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui/react@1.1.0':
|
||||
resolution: {integrity: sha512-ikcJRNj1mOiF2HZ5jQHrXoVoHcNHdBU5ejJljcBl+VTLoYXR6FidjTN86GjO6hyshi6TZFuNvv0dEOgaOFv6Lw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@base-ui/utils@0.2.4':
|
||||
resolution: {integrity: sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@clack/core@0.5.0':
|
||||
resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
|
||||
|
||||
'@clack/prompts@0.11.0':
|
||||
resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==}
|
||||
|
||||
'@dicebear/adventurer-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-MKrzLkAGx0cdBVD+XJu6ERhdJjWsjoFS+0nF9MZT17h/m/Q12FSoj+ACoKTEXBS/LBQfQqjA9HstBlSxMzmBdw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/adventurer@9.3.1':
|
||||
resolution: {integrity: sha512-MBCA8QtRC4mWbYncFDxI67LxxXMccsORqJS8osD4F/MgOPMJsdoN9QrRfsY/MjO+4NbTSxsVzOhn2nf1WzoLbA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/avataaars-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-d9enbUJcRfMui0ZESJ9ofJXKJPdqrzKgqefT9fcC8EfOvP0WqVtsUzcPj9l6FYhG1fMDdTsx+A8e//1lCynbQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/avataaars@9.3.1':
|
||||
resolution: {integrity: sha512-gQwtaTfPVwNAvVktdTjyhGnQtt5ifeE/6XyMX/fUJTTo/uI2NLy4LedzjsibA/DW8xi+TbgUyXlyTaJs0H6MGA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-ears-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-Fvw/GoT+3q77zwUbHOujGujQ4oVgtoOXE7ByfxcPeVcaUUTRARpWXlNwUBg0zt+o/Dfv875awpt3sIgKuecGsw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-ears@9.3.1':
|
||||
resolution: {integrity: sha512-JN2ZlrSvpKJNyRAFzyeg+Y5wBG0EZQc8Ds5bZIHkf2/uaLUQIeDT1At2Sr7hSJDKSYZ8z83H6ckbzpDl5b9MzQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/big-smile@9.3.1':
|
||||
resolution: {integrity: sha512-c5USb4n3Zw32WIJUZqc2+mCe3vbN6XJtZjKtFbisFujMAX6I3avRf6S1JBbm5oT86ynGH6P1/EZ2K7WkThfEBg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/bottts-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-Ea3dZ7/absDmedpFIZp+yoeS6Dq0sZ8W87xw39SS45Mr1s3i4lVd/0XWc9U5QBl0XrP4CQKB7b2QpcSY4tIYtg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/bottts@9.3.1':
|
||||
resolution: {integrity: sha512-qIPokserYLIwpScbsvFADwspBfa1Mg8JFEtYcXYcbPLnNek8bZiAhpQSc1bHSqHjm10bFEjvTr0opSNr72CBzw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/collection@9.3.1':
|
||||
resolution: {integrity: sha512-3hDYu9K4quu9jiXQTno2e0AyBzmrqm1PE6Mw7u2gYOZZ5GsSqrDdNHQODShyzqDF1LuyypZY4XN4YjFJ6fWqig==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/core@9.3.1':
|
||||
resolution: {integrity: sha512-N6Gl9z3SxYp1OVtOzQegtURFqr0D62l3QcXgvshDAVXDNjkziZ5gWj//JxYJRWldNZfVp9/pm97V3ExKI5AXPg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@dicebear/croodles-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-NEOV/j+pqxhFmxSC4EFjPgjbTsnOXkX3WgLLVz0PZBpVpS2kPOwBQXZT2fUGZIq7zHucWWatnMaKBvd2LYmFhA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/croodles@9.3.1':
|
||||
resolution: {integrity: sha512-p40OXll38AYpWh7vOVuapv6ClQuzaMh77e++QJjJGNr6n1OE6YmmQbp6XzE7iELzz2yGoCPIm/FjI+zcH0aAwA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/dylan@9.3.1':
|
||||
resolution: {integrity: sha512-CWP8S9heivya/KSSF72IJ6QKE5bUsoxKSlnLD21uO+NAm5Mzkw00PM0cgA4RvnPNf0Et7HmoJXrrvOBavWE65A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/fun-emoji@9.3.1':
|
||||
resolution: {integrity: sha512-oSJPxHvAnORxa3FJYwYGQcUuP5LIFRKzMJJ9RP4D5GTYmpjsdG0K895eo4vKkXrY/BVNVROBlfK0KcTX1xOU8g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/glass@9.3.1':
|
||||
resolution: {integrity: sha512-yNxDgIE9A+/n5VgOMH8P0qb1EGsMhMSSrl0s6ZnTBpHLGwRv1iGXezJaZrkx/ZSPsp8KlOOfTodeqi2vkVdFHg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/icons@9.3.1':
|
||||
resolution: {integrity: sha512-p8BrJ/6C2smLKU8vFFu+B54zD/GFbdjumVubzcURjvbOh1YOWU2CD4TruSBZ4d2zbAwgJIAjE+5oANB0a1gfdg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/identicon@9.3.1':
|
||||
resolution: {integrity: sha512-P3TmN7pRqlS8S9/1E+lGEMrBbQvjjXGNgXnw+Okviq+41172iLVg6Wv0nbNsOyF9QjRTjrJMq4VT3XgOuU4JAg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/initials@9.3.1':
|
||||
resolution: {integrity: sha512-1O61oYxKVeeGL6QcNCcxH7zsqbp37NmHbR/Y5CVqr6AVv0bBswvCzVzUv/Zmmsp70DoYQB+lbX+oNIdcqUWAaw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/lorelei-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-GP2EX3w8Di4b3XN0uM7lARbg1iZ9r0zaZHlUbCE2CzFy3xxrKSrRDYf2BvVt7x76doijXR5SLm4DMbEA9ARJWA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/lorelei@9.3.1':
|
||||
resolution: {integrity: sha512-4btARyv+ITuL3GWKA68/h6hAPL52lN1034JHx+dJCjy7zXrsXvFKkQj62LbCkQKHQOihTkAW1dfccVQ7mlGn4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/micah@9.3.1':
|
||||
resolution: {integrity: sha512-LAPY6Zlw/nh0Xts4aIY5d0hlaJEfSah+M5GoBRzKFKlieYdee7hvE8gsCE+OZ4pZUc98Dh7h0XXqVt/ojYW3jw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/miniavs@9.3.1':
|
||||
resolution: {integrity: sha512-LRLKxDAIk8fW/88YB0vbYiJ850FaO2EcdznOfyW0izDp0ghTGZXsO5B5RUiLTunH8ZCnDdA+DtaugaFTdvOx/Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/notionists-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-Z9dikJjibAc94EtFnHQb1+ADMISLedgLls5+ARiKwKjPcYZcuRm4U8kR9tMLmqggro10uJlT7YrLSCC/5abUXA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/notionists@9.3.1':
|
||||
resolution: {integrity: sha512-xSukD2J+iKaKq/kEOZ6svwon9sQYRpgIeNC7Gfskb7uyC+iUHQmCy6hSxLFGIOFVqEbw6Ow8uNpn9NqaFpQA4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/open-peeps@9.3.1':
|
||||
resolution: {integrity: sha512-g4A3XcLrKPy44ajlhWfmGXYUDzXfogzB/H7Z46k+mxvrhVSF0jsmReYjX80jqHNeEZ9ikIpR5g4Hbo6vmOmjGQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/personas@9.3.1':
|
||||
resolution: {integrity: sha512-2xqaiY0/uHKFNhC+ZEBINJZM9/fC8gUMFCqP4N6QuXkFbqNZn4RjgbTITkGtRE5Z4m2q9hEfPey4Dc9jep5lzA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/pixel-art-neutral@9.3.1':
|
||||
resolution: {integrity: sha512-N3mcC4CFTAMk3TqRvZVsZAGY2NONnQwoGpP+MD4E2GF+kVWoQYpvzOybVgFoOz2G0Oe4HAwSO5Qt7KTbAiD7KA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/pixel-art@9.3.1':
|
||||
resolution: {integrity: sha512-yUufylvVqkb9wpG/sYRzNTeSk1YbzVgSq/ZSMyxy1kx/R4BhOkiZBSs6Ra3VjeKWVNDBzUWERaVdylLbFvAQaw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/rings@9.3.1':
|
||||
resolution: {integrity: sha512-1bQTKJbVzpBPbhSyHS5bzlRjYIRQKO1hR0JGmC/ZWFiE9+ySk/NNwkNghvcDxvDUaz02NLSJXlSp2T8nrsdNHg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/shapes@9.3.1':
|
||||
resolution: {integrity: sha512-xzw/BWSQCznRDFBp8DKQtg1Jxawq+R3upOM2pURwbCPC+9bi8f8CAz1SExA3tlAbbrVx0HQdRKIYS3GW6/GBBA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/thumbs@9.3.1':
|
||||
resolution: {integrity: sha512-HS14oyT9HXLT8OPqEz8n0Bdob3oRWoNZ5PSZrxT4nyYXxh0rDSxCCOFwPKanXznk1qCAngtAvuzzID3vo7UG3A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dicebear/toon-head@9.3.1':
|
||||
resolution: {integrity: sha512-9a9ydhbrVG57NuscH92yzIMQ0yxEPgJtzOMG1QR6jWctgbeEuzQvJDPvJQTxtfFjx71VlQNsSL40/5rnMtCaTw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
'@dicebear/core': ^9.0.0
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
@@ -1105,6 +1337,19 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-switch@1.2.6':
|
||||
resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13':
|
||||
resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
|
||||
peerDependencies:
|
||||
@@ -3793,6 +4038,9 @@ packages:
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
reserved-identifiers@1.2.0:
|
||||
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3960,6 +4208,9 @@ packages:
|
||||
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
tabbable@6.4.0:
|
||||
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
|
||||
|
||||
tagged-tag@1.0.0:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -4470,6 +4721,31 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@base-ui/react@1.1.0(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@base-ui/utils': 0.2.4(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
reselect: 5.1.1
|
||||
tabbable: 6.4.0
|
||||
use-sync-external-store: 1.6.0(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
|
||||
'@base-ui/utils@0.2.4(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
reselect: 5.1.1
|
||||
use-sync-external-store: 1.6.0(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
|
||||
'@clack/core@0.5.0':
|
||||
dependencies:
|
||||
picocolors: 1.1.1
|
||||
@@ -4481,6 +4757,169 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
sisteransi: 1.0.5
|
||||
|
||||
'@dicebear/adventurer-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/adventurer@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/avataaars-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/avataaars@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/big-ears-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/big-ears@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/big-smile@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/bottts-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/bottts@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/collection@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/adventurer': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/adventurer-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/avataaars': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/avataaars-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/big-ears': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/big-ears-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/big-smile': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/bottts': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/bottts-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/core': 9.3.1
|
||||
'@dicebear/croodles': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/croodles-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/dylan': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/fun-emoji': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/glass': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/icons': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/identicon': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/initials': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/lorelei': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/lorelei-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/micah': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/miniavs': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/notionists': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/notionists-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/open-peeps': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/personas': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/pixel-art': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/pixel-art-neutral': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/rings': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/shapes': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/thumbs': 9.3.1(@dicebear/core@9.3.1)
|
||||
'@dicebear/toon-head': 9.3.1(@dicebear/core@9.3.1)
|
||||
|
||||
'@dicebear/core@9.3.1':
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@dicebear/croodles-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/croodles@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/dylan@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/fun-emoji@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/glass@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/icons@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/identicon@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/initials@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/lorelei-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/lorelei@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/micah@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/miniavs@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/notionists-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/notionists@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/open-peeps@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/personas@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/pixel-art-neutral@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/pixel-art@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/rings@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/shapes@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/thumbs@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dicebear/toon-head@9.3.1(@dicebear/core@9.3.1)':
|
||||
dependencies:
|
||||
'@dicebear/core': 9.3.1
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
@@ -5166,6 +5605,21 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
|
||||
'@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
@@ -8263,6 +8717,8 @@ snapshots:
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
reselect@5.1.1: {}
|
||||
|
||||
reserved-identifiers@1.2.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
@@ -8429,6 +8885,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.2.9
|
||||
|
||||
tabbable@6.4.0: {}
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tailwind-merge@3.4.0: {}
|
||||
|
||||
@@ -38,20 +38,20 @@ function QrSection({ eventId, enabled }: { eventId: string; enabled: boolean })
|
||||
const data = { data: { checkin_code: `dummy${eventId}${enabled}` } };
|
||||
return data
|
||||
? (
|
||||
<>
|
||||
<div className="border-2 px-4 py-8 border-muted rounded-xl flex flex-col items-center justify-center p-4">
|
||||
<QRCode data={data.data.checkin_code} className="size-60" />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<div className="flex flex-1 items-center ml-2 font-mono text-3xl tracking-widest text-primary/80 justify-center">
|
||||
{data.data.checkin_code}
|
||||
<>
|
||||
<div className="border-2 px-4 py-8 border-muted rounded-xl flex flex-col items-center justify-center p-4">
|
||||
<QRCode data={data.data.checkin_code} className="size-60" />
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</>
|
||||
)
|
||||
<DialogFooter>
|
||||
<div className="flex flex-1 items-center ml-2 font-mono text-3xl tracking-widest text-primary/80 justify-center">
|
||||
{data.data.checkin_code}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<QrSectionSkeleton />
|
||||
);
|
||||
<QrSectionSkeleton />
|
||||
);
|
||||
}
|
||||
|
||||
function QrSectionSkeleton() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import z from 'zod';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -21,12 +22,14 @@ import {
|
||||
} from '@/components/ui/input';
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||
import { Switch } from '../ui/switch';
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(5),
|
||||
nickname: z.string().min(1),
|
||||
subtitle: z.string().min(1),
|
||||
avatar: z.url().min(1),
|
||||
nickname: z.string(),
|
||||
subtitle: z.string(),
|
||||
avatar: z.url().or(z.literal('')),
|
||||
allow_public: z.boolean(),
|
||||
});
|
||||
export function EditProfileDialog() {
|
||||
const { data } = useUserInfo();
|
||||
@@ -39,6 +42,7 @@ export function EditProfileDialog() {
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
subtitle: user.subtitle,
|
||||
allow_public: user.allow_public,
|
||||
},
|
||||
validators: {
|
||||
onBlur: formSchema,
|
||||
@@ -57,8 +61,16 @@ export function EditProfileDialog() {
|
||||
},
|
||||
});
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
form.reset();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="w-full" size="lg">编辑个人资料</Button>
|
||||
</DialogTrigger>
|
||||
@@ -67,7 +79,7 @@ export function EditProfileDialog() {
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
form.handleSubmit().then(() => setOpen(false));
|
||||
}}
|
||||
className="grid gap-4"
|
||||
>
|
||||
@@ -138,13 +150,24 @@ export function EditProfileDialog() {
|
||||
</Field>
|
||||
)}
|
||||
</form.Field>
|
||||
<form.Field name="allow_public">
|
||||
{field => (
|
||||
<Field orientation="horizontal" className="my-2">
|
||||
<FieldLabel htmlFor="allow_public">公开个人资料</FieldLabel>
|
||||
<Switch id="allow_public" onCheckedChange={e => field.handleChange(e)} defaultChecked={user.allow_public} />
|
||||
</Field>
|
||||
)}
|
||||
</form.Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">取消</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button type="submit">保存</Button>
|
||||
</DialogClose>
|
||||
<form.Subscribe
|
||||
selector={state => [state.canSubmit]}
|
||||
children={([canSubmit]) => (
|
||||
<Button type="submit" disabled={!canSubmit}>保存</Button>
|
||||
)}
|
||||
/>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { identicon } from '@dicebear/collection';
|
||||
import { createAvatar } from '@dicebear/core';
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { Mail, Pencil } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Markdown from 'react-markdown';
|
||||
import { toast } from 'sonner';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Avatar, AvatarImage } from '@/components/ui/avatar';
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||
import { base64ToUtf8, utf8ToBase64 } from '@/lib/utils';
|
||||
@@ -18,6 +20,14 @@ export function MainProfile() {
|
||||
const [enableBioEdit, setEnableBioEdit] = useState(false);
|
||||
const { mutateAsync } = useUpdateUser();
|
||||
|
||||
const IdentIcon = useMemo(() => {
|
||||
const avatar = createAvatar(identicon, {
|
||||
size: 128,
|
||||
seed: user.user_id,
|
||||
}).toDataUri();
|
||||
return <img src={avatar} alt="Avatar" />;
|
||||
}, [user.user_id]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center w-full lg:w-auto h-full lg:h-auto lg:flex-row lg:gap-8">
|
||||
<div className="flex w-full flex-row mt-2 lg:mt-0 lg:flex-col lg:w-max">
|
||||
@@ -25,8 +35,7 @@ export function MainProfile() {
|
||||
<div className="flex flex-col w-full gap-3">
|
||||
<div className="flex flex-row gap-3 w-full lg:flex-col">
|
||||
<Avatar className="size-16 rounded-full border-2 border-muted lg:size-64">
|
||||
<AvatarImage src={user.avatar} alt={user.nickname} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
{user.avatar ? <AvatarImage src={user.avatar} alt={user.nickname} /> : IdentIcon}
|
||||
</Avatar>
|
||||
<div className="flex flex-1 flex-col justify-center lg:mt-3">
|
||||
<span className="font-semibold text-2xl" aria-hidden="true">{user.nickname}</span>
|
||||
@@ -45,12 +54,12 @@ export function MainProfile() {
|
||||
{/* Bio */}
|
||||
{enableBioEdit
|
||||
? (
|
||||
<MDEditor
|
||||
value={bio}
|
||||
onChange={setBio}
|
||||
height="100%"
|
||||
/>
|
||||
)
|
||||
<MDEditor
|
||||
value={bio}
|
||||
onChange={setBio}
|
||||
height="100%"
|
||||
/>
|
||||
)
|
||||
: <div className="p-6 prose dark:prose-invert"><Markdown>{bio}</Markdown></div>}
|
||||
<Button
|
||||
className="absolute bottom-4 right-4"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { identicon } from '@dicebear/collection';
|
||||
|
||||
import { createAvatar } from '@dicebear/core';
|
||||
import {
|
||||
IconDotsVertical,
|
||||
IconLogout,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from '@/components/ui/avatar';
|
||||
import {
|
||||
@@ -33,6 +35,14 @@ function NavUser_() {
|
||||
const user = data.data!;
|
||||
const { logout } = useLogout();
|
||||
|
||||
const IdentIcon = useMemo(() => {
|
||||
const avatar = createAvatar(identicon, {
|
||||
size: 128,
|
||||
seed: user.user_id,
|
||||
}).toDataUri();
|
||||
return <img src={avatar} alt="Avatar" />;
|
||||
}, [user.user_id]);
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
@@ -43,8 +53,7 @@ function NavUser_() {
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.nickname} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
{user.avatar ? <AvatarImage src={user.avatar} alt={user.nickname} /> : IdentIcon}
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.nickname}</span>
|
||||
@@ -64,8 +73,7 @@ function NavUser_() {
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.nickname} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
{user.avatar ? <AvatarImage src={user.avatar} alt={user.nickname} /> : IdentIcon}
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.nickname}</span>
|
||||
|
||||
@@ -36,7 +36,6 @@ export function ThemeProvider({
|
||||
root.classList.add(theme);
|
||||
}, [theme]);
|
||||
|
||||
// eslint-disable-next-line react/no-unstable-context-value
|
||||
const value = {
|
||||
theme,
|
||||
setTheme: (theme: Theme) => {
|
||||
|
||||
310
client/cms/src/components/ui/combobox.tsx
Normal file
310
client/cms/src/components/ui/combobox.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
||||
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/components/ui/input-group"
|
||||
|
||||
const Combobox = ComboboxPrimitive.Root
|
||||
|
||||
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
|
||||
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
|
||||
}
|
||||
|
||||
function ComboboxTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComboboxPrimitive.Trigger.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Trigger
|
||||
data-slot="combobox-trigger"
|
||||
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon
|
||||
data-slot="combobox-trigger-icon"
|
||||
className="text-muted-foreground pointer-events-none size-4"
|
||||
/>
|
||||
</ComboboxPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Clear
|
||||
data-slot="combobox-clear"
|
||||
render={<InputGroupButton variant="ghost" size="icon-xs" />}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
>
|
||||
<XIcon className="pointer-events-none" />
|
||||
</ComboboxPrimitive.Clear>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxInput({
|
||||
className,
|
||||
children,
|
||||
disabled = false,
|
||||
showTrigger = true,
|
||||
showClear = false,
|
||||
...props
|
||||
}: ComboboxPrimitive.Input.Props & {
|
||||
showTrigger?: boolean
|
||||
showClear?: boolean
|
||||
}) {
|
||||
return (
|
||||
<InputGroup className={cn("w-auto", className)}>
|
||||
<ComboboxPrimitive.Input
|
||||
render={<InputGroupInput disabled={disabled} />}
|
||||
{...props}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
{showTrigger && (
|
||||
<InputGroupButton
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
asChild
|
||||
data-slot="input-group-button"
|
||||
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
||||
disabled={disabled}
|
||||
>
|
||||
<ComboboxTrigger />
|
||||
</InputGroupButton>
|
||||
)}
|
||||
{showClear && <ComboboxClear disabled={disabled} />}
|
||||
</InputGroupAddon>
|
||||
{children}
|
||||
</InputGroup>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxContent({
|
||||
className,
|
||||
side = "bottom",
|
||||
sideOffset = 6,
|
||||
align = "start",
|
||||
alignOffset = 0,
|
||||
anchor,
|
||||
...props
|
||||
}: ComboboxPrimitive.Popup.Props &
|
||||
Pick<
|
||||
ComboboxPrimitive.Positioner.Props,
|
||||
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
||||
>) {
|
||||
return (
|
||||
<ComboboxPrimitive.Portal>
|
||||
<ComboboxPrimitive.Positioner
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
anchor={anchor}
|
||||
className="isolate z-50"
|
||||
>
|
||||
<ComboboxPrimitive.Popup
|
||||
data-slot="combobox-content"
|
||||
data-chips={!!anchor}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 group/combobox-content relative max-h-96 w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-md shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ComboboxPrimitive.Positioner>
|
||||
</ComboboxPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.List
|
||||
data-slot="combobox-list"
|
||||
className={cn(
|
||||
"max-h-[min(calc(--spacing(96)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComboboxPrimitive.Item.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Item
|
||||
data-slot="combobox-item"
|
||||
className={cn(
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ComboboxPrimitive.ItemIndicator
|
||||
data-slot="combobox-item-indicator"
|
||||
render={
|
||||
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
||||
}
|
||||
>
|
||||
<CheckIcon className="pointer-events-none size-4 pointer-coarse:size-5" />
|
||||
</ComboboxPrimitive.ItemIndicator>
|
||||
</ComboboxPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Group
|
||||
data-slot="combobox-group"
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxLabel({
|
||||
className,
|
||||
...props
|
||||
}: ComboboxPrimitive.GroupLabel.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.GroupLabel
|
||||
data-slot="combobox-label"
|
||||
className={cn(
|
||||
"text-muted-foreground px-2 py-1.5 text-xs pointer-coarse:px-3 pointer-coarse:py-2 pointer-coarse:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Empty
|
||||
data-slot="combobox-empty"
|
||||
className={cn(
|
||||
"text-muted-foreground hidden w-full justify-center py-2 text-center text-sm group-data-empty/combobox-content:flex",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxSeparator({
|
||||
className,
|
||||
...props
|
||||
}: ComboboxPrimitive.Separator.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Separator
|
||||
data-slot="combobox-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxChips({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
|
||||
ComboboxPrimitive.Chips.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Chips
|
||||
data-slot="combobox-chips"
|
||||
className={cn(
|
||||
"dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px] has-aria-invalid:ring-[3px] has-data-[slot=combobox-chip]:px-1.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxChip({
|
||||
className,
|
||||
children,
|
||||
showRemove = true,
|
||||
...props
|
||||
}: ComboboxPrimitive.Chip.Props & {
|
||||
showRemove?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ComboboxPrimitive.Chip
|
||||
data-slot="combobox-chip"
|
||||
className={cn(
|
||||
"bg-muted text-foreground flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm px-1.5 text-xs font-medium whitespace-nowrap has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showRemove && (
|
||||
<ComboboxPrimitive.ChipRemove
|
||||
render={<Button variant="ghost" size="icon-xs" />}
|
||||
className="-ml-1 opacity-50 hover:opacity-100"
|
||||
data-slot="combobox-chip-remove"
|
||||
>
|
||||
<XIcon className="pointer-events-none" />
|
||||
</ComboboxPrimitive.ChipRemove>
|
||||
)}
|
||||
</ComboboxPrimitive.Chip>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxChipsInput({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ComboboxPrimitive.Input.Props) {
|
||||
return (
|
||||
<ComboboxPrimitive.Input
|
||||
data-slot="combobox-chip-input"
|
||||
className={cn("min-w-16 flex-1 outline-none", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function useComboboxAnchor() {
|
||||
return React.useRef<HTMLDivElement | null>(null)
|
||||
}
|
||||
|
||||
export {
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxContent,
|
||||
ComboboxList,
|
||||
ComboboxItem,
|
||||
ComboboxGroup,
|
||||
ComboboxLabel,
|
||||
ComboboxCollection,
|
||||
ComboboxEmpty,
|
||||
ComboboxSeparator,
|
||||
ComboboxChips,
|
||||
ComboboxChip,
|
||||
ComboboxChipsInput,
|
||||
ComboboxTrigger,
|
||||
ComboboxValue,
|
||||
useComboboxAnchor,
|
||||
}
|
||||
168
client/cms/src/components/ui/input-group.tsx
Normal file
168
client/cms/src/components/ui/input-group.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
className={cn(
|
||||
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"h-9 min-w-0 has-[>textarea]:h-auto",
|
||||
|
||||
// Variants based on alignment.
|
||||
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
||||
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
||||
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
||||
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
||||
|
||||
// Focus state.
|
||||
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
|
||||
|
||||
// Error state.
|
||||
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
||||
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const inputGroupAddonVariants = cva(
|
||||
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
align: {
|
||||
"inline-start":
|
||||
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
||||
"inline-end":
|
||||
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
||||
"block-start":
|
||||
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
||||
"block-end":
|
||||
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
align: "inline-start",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function InputGroupAddon({
|
||||
className,
|
||||
align = "inline-start",
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="input-group-addon"
|
||||
data-align={align}
|
||||
className={cn(inputGroupAddonVariants({ align }), className)}
|
||||
onClick={(e) => {
|
||||
if ((e.target as HTMLElement).closest("button")) {
|
||||
return
|
||||
}
|
||||
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const inputGroupButtonVariants = cva(
|
||||
"text-sm shadow-none flex gap-2 items-center",
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
||||
"icon-xs":
|
||||
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "xs",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function InputGroupButton({
|
||||
className,
|
||||
type = "button",
|
||||
variant = "ghost",
|
||||
size = "xs",
|
||||
...props
|
||||
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
||||
VariantProps<typeof inputGroupButtonVariants>) {
|
||||
return (
|
||||
<Button
|
||||
type={type}
|
||||
data-size={size}
|
||||
variant={variant}
|
||||
className={cn(inputGroupButtonVariants({ size }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function InputGroupInput({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<Input
|
||||
data-slot="input-group-control"
|
||||
className={cn(
|
||||
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function InputGroupTextarea({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
className={cn(
|
||||
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupText,
|
||||
InputGroupInput,
|
||||
InputGroupTextarea,
|
||||
}
|
||||
33
client/cms/src/components/ui/switch.tsx
Normal file
33
client/cms/src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
size = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Switch }
|
||||
18
client/cms/src/components/ui/textarea.tsx
Normal file
18
client/cms/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Textarea }
|
||||
@@ -1,6 +1,6 @@
|
||||
import { postAuthExchangeMutation } from "@/client/@tanstack/react-query.gen";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { postAuthExchangeMutation } from '@/client/@tanstack/react-query.gen';
|
||||
|
||||
export function useExchangeToken() {
|
||||
return useMutation({
|
||||
@@ -10,7 +10,7 @@ export function useExchangeToken() {
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast("An error occurred while exchanging the token. Please login manually.");
|
||||
}
|
||||
})
|
||||
toast('An error occurred while exchanging the token. Please login manually.');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { postAuthMagicMutation } from '@/client/@tanstack/react-query.gen';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { postAuthMagicMutation } from '@/client/@tanstack/react-query.gen';
|
||||
|
||||
export function useGetMagicLink() {
|
||||
return useMutation({
|
||||
...postAuthMagicMutation()
|
||||
})
|
||||
...postAuthMagicMutation(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen';
|
||||
|
||||
export function useUpdateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -8,5 +8,5 @@ export function useUpdateUser() {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey() });
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getUserInfoOptions } from '@/client/@tanstack/react-query.gen';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { getUserInfoOptions } from '@/client/@tanstack/react-query.gen';
|
||||
|
||||
export function useUserInfo() {
|
||||
return useSuspenseQuery({
|
||||
...getUserInfoOptions(),
|
||||
staleTime: 10 * 60 * 1000
|
||||
staleTime: 10 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { isEmpty, isNil } from 'lodash-es';
|
||||
import { client } from '@/client/client.gen';
|
||||
import { router } from './router';
|
||||
import {
|
||||
getToken,
|
||||
getRefreshToken,
|
||||
setToken,
|
||||
setRefreshToken,
|
||||
clearTokens,
|
||||
doRefreshToken
|
||||
} from "./token";
|
||||
import { router } from "./router";
|
||||
import { isEmpty,
|
||||
isNil } from "lodash-es";
|
||||
import { client } from "@/client/client.gen";
|
||||
doRefreshToken,
|
||||
getRefreshToken,
|
||||
getToken,
|
||||
setRefreshToken,
|
||||
setToken,
|
||||
} from './token';
|
||||
|
||||
export function configInternalApiClient() {
|
||||
client.setConfig({
|
||||
@@ -50,7 +49,8 @@ export function configInternalApiClient() {
|
||||
signal: request.signal,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
catch (e) {
|
||||
clearTokens();
|
||||
await router.navigate({ to: '/authorize' });
|
||||
return response;
|
||||
@@ -59,5 +59,4 @@ export function configInternalApiClient() {
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { postAuthRefresh, type ServiceAuthTokenResponse } from '@/client';
|
||||
import type { ServiceAuthTokenResponse } from '@/client';
|
||||
import { postAuthRefresh } from '@/client';
|
||||
|
||||
export function setToken(token: string) {
|
||||
localStorage.setItem('token', token);
|
||||
@@ -32,8 +33,8 @@ export function clearTokens() {
|
||||
export async function doRefreshToken(): Promise<ServiceAuthTokenResponse | undefined> {
|
||||
const { data } = await postAuthRefresh({
|
||||
body: {
|
||||
refresh_token: getRefreshToken()!
|
||||
}
|
||||
refresh_token: getRefreshToken()!,
|
||||
},
|
||||
});
|
||||
return data?.data;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { zodValidator } from '@tanstack/zod-adapter';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useEffect } from 'react';
|
||||
import z from 'zod';
|
||||
import { LoginForm } from '@/components/login-form';
|
||||
import { useExchangeToken } from '@/hooks/data/useExchangeToken';
|
||||
import { generateOAuthState } from '@/lib/random';
|
||||
import { getToken } from '@/lib/token';
|
||||
import { useExchangeToken } from '@/hooks/data/useExchangeToken';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_URL;
|
||||
|
||||
@@ -39,7 +38,7 @@ function RouteComponent() {
|
||||
client_id: oauthParams.client_id,
|
||||
redirect_uri: oauthParams.redirect_uri,
|
||||
state: oauthParams.state,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [token, mutation.isIdle]);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import {
|
||||
useEffect,
|
||||
useState } from 'react';
|
||||
useState,
|
||||
} from 'react';
|
||||
import z from 'zod';
|
||||
import { postAuthTokenMutation } from '@/client/@tanstack/react-query.gen';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { setRefreshToken, setToken } from '@/lib/token';
|
||||
|
||||
const tokenCodeSchema = z.object({
|
||||
@@ -24,20 +25,20 @@ function RouteComponent() {
|
||||
const mutation = useMutation({
|
||||
...postAuthTokenMutation(),
|
||||
onSuccess: (data) => {
|
||||
setToken(data.data?.access_token!)
|
||||
setRefreshToken(data.data?.refresh_token!)
|
||||
setToken(data.data?.access_token!);
|
||||
setRefreshToken(data.data?.refresh_token!);
|
||||
void navigate({ to: '/' });
|
||||
},
|
||||
onError: () => {
|
||||
setStatus('Error getting token');
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (mutation.isIdle) {
|
||||
mutation.mutate({ body: { code } })
|
||||
mutation.mutate({ body: { code } });
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
return <div>{status}</div>;
|
||||
}
|
||||
|
||||
6
client/cms/src/vite.env.d.ts
vendored
6
client/cms/src/vite.env.d.ts
vendored
@@ -1,11 +1,11 @@
|
||||
interface ViteTypeOptions {
|
||||
strictImportMetaEnv: unknown
|
||||
strictImportMetaEnv: unknown;
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_BASE_URL: string
|
||||
readonly VITE_APP_BASE_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user