wip: dashboard
This commit is contained in:
parent
66796503a5
commit
b6c91e8bd0
|
@ -14,6 +14,7 @@
|
|||
"@prisma/migrate": "^5.5.2",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
|
@ -33,11 +34,13 @@
|
|||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
"recharts": "^2.9.2",
|
||||
"sonner": "^1.2.0",
|
||||
"superjson": "^2.2.0",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-permissions": "^1.0.0",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -48,6 +51,7 @@
|
|||
"@types/node": "^20.8.10",
|
||||
"@types/react": "^18.2.35",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/ua-parser-js": "^0.7.38",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||
"@typescript-eslint/parser": "^6.9.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
|
|
278
pnpm-lock.yaml
278
pnpm-lock.yaml
|
@ -17,6 +17,9 @@ dependencies:
|
|||
'@radix-ui/react-label':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(@types/react-dom@18.2.14)(@types/react@18.2.35)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-separator':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.35)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@types/react@18.2.35)(react@18.2.0)
|
||||
|
@ -74,6 +77,9 @@ dependencies:
|
|||
react-icons:
|
||||
specifier: ^4.11.0
|
||||
version: 4.11.0(react@18.2.0)
|
||||
recharts:
|
||||
specifier: ^2.9.2
|
||||
version: 2.9.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
sonner:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -89,6 +95,9 @@ dependencies:
|
|||
ts-permissions:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
ua-parser-js:
|
||||
specifier: ^1.0.37
|
||||
version: 1.0.37
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
|
@ -115,6 +124,9 @@ devDependencies:
|
|||
'@types/react-dom':
|
||||
specifier: ^18.2.14
|
||||
version: 18.2.14
|
||||
'@types/ua-parser-js':
|
||||
specifier: ^0.7.38
|
||||
version: 0.7.38
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.53.0)(typescript@5.2.2)
|
||||
|
@ -1042,6 +1054,27 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.35)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.35)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/react': 18.2.35
|
||||
'@types/react-dom': 18.2.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.0.2(@types/react@18.2.35)(react@18.2.0):
|
||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||
peerDependencies:
|
||||
|
@ -1214,6 +1247,48 @@ packages:
|
|||
'@types/node': 20.8.10
|
||||
dev: false
|
||||
|
||||
/@types/d3-array@3.2.0:
|
||||
resolution: {integrity: sha512-tjU8juPSfhMnu6mJZPOCVVGba4rZoE0tjHDPb81PYwA8CzbaFscGjgkUM7juUJu6iWA1cCVWNEVwxZ5HN9Jj8Q==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-color@3.1.2:
|
||||
resolution: {integrity: sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-ease@3.0.1:
|
||||
resolution: {integrity: sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-interpolate@3.0.3:
|
||||
resolution: {integrity: sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==}
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.2
|
||||
dev: false
|
||||
|
||||
/@types/d3-path@3.0.1:
|
||||
resolution: {integrity: sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-scale@4.0.6:
|
||||
resolution: {integrity: sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==}
|
||||
dependencies:
|
||||
'@types/d3-time': 3.0.2
|
||||
dev: false
|
||||
|
||||
/@types/d3-shape@3.1.4:
|
||||
resolution: {integrity: sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==}
|
||||
dependencies:
|
||||
'@types/d3-path': 3.0.1
|
||||
dev: false
|
||||
|
||||
/@types/d3-time@3.0.2:
|
||||
resolution: {integrity: sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-timer@3.0.1:
|
||||
resolution: {integrity: sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg==}
|
||||
dev: false
|
||||
|
||||
/@types/debug@4.1.9:
|
||||
resolution: {integrity: sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==}
|
||||
dependencies:
|
||||
|
@ -1286,6 +1361,10 @@ packages:
|
|||
resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==}
|
||||
dev: true
|
||||
|
||||
/@types/ua-parser-js@0.7.38:
|
||||
resolution: {integrity: sha512-59CA5oavBEWSNLtS/BChj9xntiWMsIf9IytjxmBo9OuZEYuRzRf3K1ARzFPlXTOz5Zm2wXI38AP9RlLqDYMToQ==}
|
||||
dev: true
|
||||
|
||||
/@types/webidl-conversions@7.0.2:
|
||||
resolution: {integrity: sha512-uNv6b/uGRLlCVmelat2rA8bcVd3k/42mV2EmjhPh6JLkd35T5bgwR/t6xy7a9MWhd9sixIeBUzhBenvk3NO+DQ==}
|
||||
dev: false
|
||||
|
@ -1952,6 +2031,10 @@ packages:
|
|||
clsx: 2.0.0
|
||||
dev: false
|
||||
|
||||
/classnames@2.3.2:
|
||||
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||
dev: false
|
||||
|
||||
/clean-stack@2.2.0:
|
||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -2114,6 +2197,77 @@ packages:
|
|||
/csstype@3.1.2:
|
||||
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
|
||||
|
||||
/d3-array@3.2.4:
|
||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
internmap: 2.0.3
|
||||
dev: false
|
||||
|
||||
/d3-color@3.1.0:
|
||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-ease@3.0.1:
|
||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-format@3.1.0:
|
||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-interpolate@3.0.1:
|
||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-color: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-path@3.1.0:
|
||||
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-scale@4.0.2:
|
||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
d3-format: 3.1.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-time: 3.1.0
|
||||
d3-time-format: 4.1.0
|
||||
dev: false
|
||||
|
||||
/d3-shape@3.2.0:
|
||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-path: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-time-format@4.1.0:
|
||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-time: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-time@3.1.0:
|
||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
dev: false
|
||||
|
||||
/d3-timer@3.0.1:
|
||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d@1.0.1:
|
||||
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
|
||||
dependencies:
|
||||
|
@ -2147,6 +2301,10 @@ packages:
|
|||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
dev: false
|
||||
|
||||
/decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -2260,6 +2418,12 @@ packages:
|
|||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dom-helpers@3.4.0:
|
||||
resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.2
|
||||
dev: false
|
||||
|
||||
/dotenv@16.0.3:
|
||||
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -2891,6 +3055,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
dev: false
|
||||
|
||||
/events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
|
@ -2925,6 +3093,11 @@ packages:
|
|||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
/fast-equals@5.0.1:
|
||||
resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: false
|
||||
|
||||
/fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
dev: false
|
||||
|
@ -3429,6 +3602,11 @@ packages:
|
|||
hasown: 2.0.0
|
||||
side-channel: 1.0.4
|
||||
|
||||
/internmap@2.0.3:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/ip@2.0.0:
|
||||
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
||||
dev: false
|
||||
|
@ -4920,7 +5098,6 @@ packages:
|
|||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
dev: true
|
||||
|
||||
/pump@3.0.0:
|
||||
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||
|
@ -4970,7 +5147,35 @@ packages:
|
|||
|
||||
/react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
dev: true
|
||||
|
||||
/react-lifecycles-compat@3.0.4:
|
||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||
dev: false
|
||||
|
||||
/react-resize-detector@8.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==}
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-smooth@2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==}
|
||||
peerDependencies:
|
||||
prop-types: ^15.6.0
|
||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
fast-equals: 5.0.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-ssr-prepass@1.5.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-yFNHrlVEReVYKsLI5lF05tZoHveA5pGzjFbFJY/3pOqqjGOmMmqx83N4hIjN2n6E1AOa+eQEUxs3CgRnPmT0RQ==}
|
||||
|
@ -4980,6 +5185,20 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-transition-group@2.9.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==}
|
||||
peerDependencies:
|
||||
react: '>=15.0.0'
|
||||
react-dom: '>=15.0.0'
|
||||
dependencies:
|
||||
dom-helpers: 3.4.0
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-lifecycles-compat: 3.0.4
|
||||
dev: false
|
||||
|
||||
/react@18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -5055,6 +5274,34 @@ packages:
|
|||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/recharts-scale@0.4.5:
|
||||
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
|
||||
dependencies:
|
||||
decimal.js-light: 2.5.1
|
||||
dev: false
|
||||
|
||||
/recharts@2.9.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ig0zYgO5nUP/896GW16b9yy2sHIRW1AHB90x48hypFTSjjxQt/J9rPzlLJjgNupzJKEHPCwMi1VnvN/k20K45w==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
prop-types: ^15.6.0
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
classnames: 2.3.2
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.21
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-is: 16.13.1
|
||||
react-resize-detector: 8.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
react-smooth: 2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
recharts-scale: 0.4.5
|
||||
tiny-invariant: 1.3.1
|
||||
victory-vendor: 36.6.12
|
||||
dev: false
|
||||
|
||||
/reflect.getprototypeof@1.0.4:
|
||||
resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -5705,6 +5952,10 @@ packages:
|
|||
next-tick: 1.1.0
|
||||
dev: true
|
||||
|
||||
/tiny-invariant@1.3.1:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
||||
dev: false
|
||||
|
||||
/tmp@0.2.1:
|
||||
resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==}
|
||||
engines: {node: '>=8.17.0'}
|
||||
|
@ -5847,6 +6098,10 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
/ua-parser-js@1.0.37:
|
||||
resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==}
|
||||
dev: false
|
||||
|
||||
/unbox-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||
dependencies:
|
||||
|
@ -5915,6 +6170,25 @@ packages:
|
|||
spdx-expression-parse: 3.0.1
|
||||
dev: false
|
||||
|
||||
/victory-vendor@36.6.12:
|
||||
resolution: {integrity: sha512-pJrTkNHln+D83vDCCSUf0ZfxBvIaVrFHmrBOsnnLAbdqfudRACAj51He2zU94/IWq9464oTADcPVkmWAfNMwgA==}
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.0
|
||||
'@types/d3-ease': 3.0.1
|
||||
'@types/d3-interpolate': 3.0.3
|
||||
'@types/d3-scale': 4.0.6
|
||||
'@types/d3-shape': 3.1.4
|
||||
'@types/d3-time': 3.0.2
|
||||
'@types/d3-timer': 3.0.1
|
||||
d3-array: 3.2.4
|
||||
d3-ease: 3.0.1
|
||||
d3-interpolate: 3.0.1
|
||||
d3-scale: 4.0.2
|
||||
d3-shape: 3.2.0
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
dev: false
|
||||
|
||||
/watchpack@2.4.0:
|
||||
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
|
|
@ -24,9 +24,9 @@ export default function LoginForm() {
|
|||
>();
|
||||
|
||||
const login = api.auth.login.useMutation({
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
toast.success("Successfully logged in!", { id: toastLoading });
|
||||
router.push("/dashboard");
|
||||
router.push("/home");
|
||||
},
|
||||
|
||||
onError: (error) => {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { api } from "~/trpc/server";
|
||||
import Test from "./RSC";
|
||||
|
||||
export default async function DashboardHome() {
|
||||
return (
|
||||
<div>
|
||||
rip
|
||||
<Test />
|
||||
</div>
|
||||
);
|
||||
}
|
0
src/app/home/ChartGraph.tsx
Normal file
0
src/app/home/ChartGraph.tsx
Normal file
9
src/app/home/StatCard.module.css
Normal file
9
src/app/home/StatCard.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* tailwind does not have text-shadow support, so we do it ourselves */
|
||||
.stat-card > p {
|
||||
/* -webkit-text-stroke: 1px theme("colors.card.DEFAULT"); */
|
||||
text-shadow:
|
||||
1px 1px 0 theme("colors.card.DEFAULT"),
|
||||
-1px 1px 0 theme("colors.card.DEFAULT"),
|
||||
1px -1px 0 theme("colors.card.DEFAULT"),
|
||||
-1px -1px 0 theme("colors.card.DEFAULT");
|
||||
}
|
52
src/app/home/StatCard.tsx
Normal file
52
src/app/home/StatCard.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
"use client";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { RiPulseFill } from "react-icons/ri";
|
||||
import { LineChart, Line, ResponsiveContainer } from "recharts";
|
||||
import styles from "./StatCard.module.css";
|
||||
|
||||
const TEST_DATA = [
|
||||
{ cpu: 0.05 },
|
||||
{ cpu: 0.1 },
|
||||
{ cpu: 0.08 },
|
||||
{ cpu: 0.09 },
|
||||
{ cpu: 0.2 },
|
||||
{ cpu: 0.1 },
|
||||
{ cpu: 0.12 },
|
||||
{ cpu: 0.3 },
|
||||
];
|
||||
|
||||
export function StatCard() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle>CPU Usage</CardTitle>
|
||||
<RiPulseFill className="text-2xl text-muted-foreground" />
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="relative">
|
||||
{/* chart background */}
|
||||
<div className="absolute inset-0 z-0 h-full w-full">
|
||||
<ResponsiveContainer width={"100%"} height={"100%"}>
|
||||
<LineChart data={TEST_DATA}>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={"cpu"}
|
||||
stroke="hsl(var(--border))"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className={`relative z-10 ${styles["stat-card"]}`}>
|
||||
<p className="stroke stroke-card text-2xl font-bold">4.5%</p>
|
||||
<p className="stroke stroke-card text-sm text-muted-foreground">
|
||||
of 8 CPUs
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
17
src/app/home/page.tsx
Normal file
17
src/app/home/page.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { api } from "~/trpc/server";
|
||||
import Test from "./RSC";
|
||||
import { StatCard } from "./StatCard";
|
||||
|
||||
export default async function DashboardHome() {
|
||||
return (
|
||||
<div className="mx-auto max-w-[1500px]">
|
||||
<Test />
|
||||
<div className="m-8 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<StatCard />
|
||||
<StatCard />
|
||||
<StatCard />
|
||||
<StatCard />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
21
src/app/settings/SettingsHeader.tsx
Normal file
21
src/app/settings/SettingsHeader.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
type SidebarNavItem = {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export function SettingsHeader(props: { items: SidebarNavItem[] }) {
|
||||
const path = usePathname();
|
||||
const active = props.items.find((item) => item.href === path);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">{active?.title}</h3>
|
||||
<p className="text-muted-foreground">{active?.description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
5
src/app/settings/account/page.tsx
Normal file
5
src/app/settings/account/page.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
"use client";
|
||||
|
||||
export default function AccountSettings() {
|
||||
return <></>;
|
||||
}
|
65
src/app/settings/layout.tsx
Normal file
65
src/app/settings/layout.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import Image from "next/image";
|
||||
|
||||
import { Separator } from "~/components/ui/separator";
|
||||
import { SidebarNav } from "~/components/SidebarNav";
|
||||
import { SettingsHeader } from "./SettingsHeader";
|
||||
|
||||
const sidebarNavItems = [
|
||||
{
|
||||
title: "Account",
|
||||
description: "Manage your account security and settings.",
|
||||
href: "/settings/account",
|
||||
},
|
||||
{
|
||||
title: "Sessions",
|
||||
description: "Manage your active sessions and logout remotely.",
|
||||
href: "/settings/sessions",
|
||||
},
|
||||
];
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SettingsLayout({ children }: SettingsLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
src="/examples/forms-light.png"
|
||||
width={1280}
|
||||
height={791}
|
||||
alt="Forms"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/forms-dark.png"
|
||||
width={1280}
|
||||
height={791}
|
||||
alt="Forms"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div className="space-y-0.5">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your account settings as well as the global instance
|
||||
settings.
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-6" />
|
||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||
<aside className="-mx-4 lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="flex-1 space-y-6 lg:max-w-2xl">
|
||||
<SettingsHeader items={sidebarNavItems} />
|
||||
<Separator />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
5
src/app/settings/page.tsx
Normal file
5
src/app/settings/page.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Settings() {
|
||||
redirect("/settings/account");
|
||||
}
|
23
src/app/settings/sessions/Sessions.tsx
Normal file
23
src/app/settings/sessions/Sessions.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Card } from "~/components/ui/card";
|
||||
import UAParser from "ua-parser-js";
|
||||
|
||||
type SessionData = {
|
||||
lastUA: string | null;
|
||||
lastIP: string | null;
|
||||
lastAccessed: Date | null;
|
||||
createdAt: number;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export default function Session(props: { session: SessionData }) {
|
||||
const ua = new UAParser(props.session.lastUA ?? "");
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<p className="text-lg font-bold">{ua.getBrowser().name}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{ua.getOS().name} {ua.getOS().version}
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
15
src/app/settings/sessions/page.tsx
Normal file
15
src/app/settings/sessions/page.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { api } from "~/trpc/server";
|
||||
import Session from "./Sessions";
|
||||
|
||||
export default async function SessionsPage() {
|
||||
const sessions = await api.auth.sessions.list.query();
|
||||
|
||||
return (
|
||||
<>
|
||||
{sessions.map((session, idx) => (
|
||||
<Session key={idx} session={session} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
43
src/components/SidebarNav.tsx
Normal file
43
src/components/SidebarNav.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { buttonVariants } from "~/components/ui/button";
|
||||
import { cn } from "~/utils/utils";
|
||||
|
||||
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
||||
items: {
|
||||
href: string;
|
||||
title: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={cn(
|
||||
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
pathname === item.href
|
||||
? "bg-muted hover:bg-muted"
|
||||
: "hover:bg-transparent hover:underline",
|
||||
"justify-start",
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
31
src/components/ui/separator.tsx
Normal file
31
src/components/ui/separator.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "~/utils/utils";
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
|
@ -3,14 +3,17 @@ import {
|
|||
authenticatedProcedure,
|
||||
createTRPCRouter,
|
||||
publicProcedure,
|
||||
} from "../trpc";
|
||||
} from "../../trpc";
|
||||
import { users } from "~/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import argon2 from "argon2";
|
||||
import { Session } from "~/server/auth/Session";
|
||||
import { sessionsRouter } from "./sessions";
|
||||
|
||||
export const authRouter = createTRPCRouter({
|
||||
sessions: sessionsRouter,
|
||||
|
||||
me: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
const user = await ctx.session.getUser();
|
||||
|
21
src/server/api/routers/auth/sessions.tsx
Normal file
21
src/server/api/routers/auth/sessions.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { sessions } from "~/server/db/schema";
|
||||
import { authenticatedProcedure, createTRPCRouter } from "../../trpc";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Session } from "~/server/auth/Session";
|
||||
|
||||
export const sessionsRouter = createTRPCRouter({
|
||||
list: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
const activeSessions = ctx.db
|
||||
.select({
|
||||
lastUA: sessions.lastUA,
|
||||
lastIP: sessions.lastIP,
|
||||
lastAccessed: sessions.lastAccessed,
|
||||
createdAt: sessions.createdAt,
|
||||
id: sessions.token,
|
||||
})
|
||||
.from(sessions)
|
||||
.where(eq(sessions.userId, ctx.session.data.userId));
|
||||
|
||||
return activeSessions;
|
||||
}),
|
||||
});
|
|
@ -4,6 +4,7 @@ import { users, sessions } from "../db/schema";
|
|||
import { randomBytes } from "crypto";
|
||||
import assert from "assert";
|
||||
import { NextRequest, userAgent } from "next/server";
|
||||
import { hash } from "argon2";
|
||||
|
||||
export type SessionUpdateData = Partial<{
|
||||
ua: string;
|
||||
|
@ -90,13 +91,13 @@ export class Session {
|
|||
.values({
|
||||
lastUA: parsedContext.ua,
|
||||
lastIP: parsedContext.ip,
|
||||
token,
|
||||
token: await hash(token),
|
||||
userId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
assert(sessionData, "Session should be created");
|
||||
return new Session(sessionData);
|
||||
return new Session(sessionData, token);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,8 +113,12 @@ export class Session {
|
|||
/**
|
||||
* Create a new session instance from a user's session data.
|
||||
* @param sessionData The user's session data.
|
||||
* @param unhashedSessionToken The unhashed session token.
|
||||
*/
|
||||
constructor(public readonly data: typeof sessions.$inferSelect) {}
|
||||
constructor(
|
||||
public readonly data: typeof sessions.$inferSelect,
|
||||
private readonly unhashedSessionToken?: string,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the user associated with this session.
|
||||
|
@ -145,9 +150,11 @@ export class Session {
|
|||
* Returns a cookie string for this session.
|
||||
*/
|
||||
getCookieString() {
|
||||
assert(this.unhashedSessionToken, "Sessions cannot be unhashed");
|
||||
|
||||
const expire = new Date(this.data.createdAt + Session.EXPIRE_TIME);
|
||||
return `sessionToken=${
|
||||
this.data.token
|
||||
this.unhashedSessionToken
|
||||
}; Expires=${expire.toUTCString()}; Path=/; HttpOnly; SameSite=Strict`;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue