Updated reports components.

This commit is contained in:
Mike Cao 2025-03-26 21:54:23 -07:00
parent f5bc3dc6c2
commit 0f6cdf8b80
95 changed files with 580 additions and 698 deletions

View file

@ -78,7 +78,7 @@
"@react-spring/web": "^9.7.5", "@react-spring/web": "^9.7.5",
"@tanstack/react-query": "^5.68.0", "@tanstack/react-query": "^5.68.0",
"@umami/prisma-client": "^0.16.0", "@umami/prisma-client": "^0.16.0",
"@umami/react-zen": "^0.66.0", "@umami/react-zen": "^0.71.0",
"@umami/redis-client": "^0.27.0", "@umami/redis-client": "^0.27.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"chalk": "^4.1.2", "chalk": "^4.1.2",

92
pnpm-lock.yaml generated
View file

@ -45,8 +45,8 @@ importers:
specifier: ^0.16.0 specifier: ^0.16.0
version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))) version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)))
'@umami/react-zen': '@umami/react-zen':
specifier: ^0.66.0 specifier: ^0.71.0
version: 0.66.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0)) version: 0.71.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))
'@umami/redis-client': '@umami/redis-client':
specifier: ^0.27.0 specifier: ^0.27.0
version: 0.27.0 version: 0.27.0
@ -1516,27 +1516,42 @@ packages:
'@formatjs/ecma402-abstract@2.3.3': '@formatjs/ecma402-abstract@2.3.3':
resolution: {integrity: sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg==} resolution: {integrity: sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg==}
'@formatjs/ecma402-abstract@2.3.4':
resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==}
'@formatjs/fast-memoize@2.2.6': '@formatjs/fast-memoize@2.2.6':
resolution: {integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==} resolution: {integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==}
'@formatjs/fast-memoize@2.2.7':
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
'@formatjs/icu-messageformat-parser@2.1.0': '@formatjs/icu-messageformat-parser@2.1.0':
resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==} resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==}
'@formatjs/icu-messageformat-parser@2.11.1': '@formatjs/icu-messageformat-parser@2.11.1':
resolution: {integrity: sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA==} resolution: {integrity: sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA==}
'@formatjs/icu-messageformat-parser@2.11.2':
resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==}
'@formatjs/icu-skeleton-parser@1.3.6': '@formatjs/icu-skeleton-parser@1.3.6':
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==} resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
'@formatjs/icu-skeleton-parser@1.8.13': '@formatjs/icu-skeleton-parser@1.8.13':
resolution: {integrity: sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg==} resolution: {integrity: sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg==}
'@formatjs/icu-skeleton-parser@1.8.14':
resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==}
'@formatjs/intl-localematcher@0.2.25': '@formatjs/intl-localematcher@0.2.25':
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==} resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
'@formatjs/intl-localematcher@0.6.0': '@formatjs/intl-localematcher@0.6.0':
resolution: {integrity: sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA==} resolution: {integrity: sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA==}
'@formatjs/intl-localematcher@0.6.1':
resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==}
'@formatjs/intl-numberformat@5.7.6': '@formatjs/intl-numberformat@5.7.6':
resolution: {integrity: sha512-ZlZfYtvbVHYZY5OG3RXizoCwxKxEKOrzEe2YOw9wbzoxF3PmFn0SAgojCFGLyNXkkR6xVxlylhbuOPf1dkIVNg==} resolution: {integrity: sha512-ZlZfYtvbVHYZY5OG3RXizoCwxKxEKOrzEe2YOw9wbzoxF3PmFn0SAgojCFGLyNXkkR6xVxlylhbuOPf1dkIVNg==}
@ -2949,8 +2964,8 @@ packages:
'@prisma/client': ^4.8.0 '@prisma/client': ^4.8.0
'@prisma/extension-read-replicas': ^0.3.0 '@prisma/extension-read-replicas': ^0.3.0
'@umami/react-zen@0.66.0': '@umami/react-zen@0.71.0':
resolution: {integrity: sha512-RAVrzv+bQQs+UtFpCczJ0ieEb11Q+NLZ0r8GLDTMTXXWlyaLqZxj0GGb2p7BhbsnLh+gQnaiSSeN9Q/ACYA93w==} resolution: {integrity: sha512-SIfbDv/uW7mN5uebDeBeD+MnRiOrjsa2pJmVEdobyiM3ImdshpVVYxyBAJqAbiKbLF4OGZgRVbFsRfUcP/2Y0w==}
'@umami/redis-client@0.27.0': '@umami/redis-client@0.27.0':
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==} resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
@ -4398,6 +4413,10 @@ packages:
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
hasBin: true hasBin: true
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
glob@7.2.3: glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported deprecated: Glob versions prior to v9 are no longer supported
@ -4618,6 +4637,9 @@ packages:
intl-messageformat@10.7.15: intl-messageformat@10.7.15:
resolution: {integrity: sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg==} resolution: {integrity: sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg==}
intl-messageformat@10.7.16:
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
ipaddr.js@2.2.0: ipaddr.js@2.2.0:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@ -4849,6 +4871,9 @@ packages:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jake@10.9.2: jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5592,6 +5617,9 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1: parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -8516,10 +8544,21 @@ snapshots:
decimal.js: 10.5.0 decimal.js: 10.5.0
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/ecma402-abstract@2.3.4':
dependencies:
'@formatjs/fast-memoize': 2.2.7
'@formatjs/intl-localematcher': 0.6.1
decimal.js: 10.5.0
tslib: 2.8.1
'@formatjs/fast-memoize@2.2.6': '@formatjs/fast-memoize@2.2.6':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/fast-memoize@2.2.7':
dependencies:
tslib: 2.8.1
'@formatjs/icu-messageformat-parser@2.1.0': '@formatjs/icu-messageformat-parser@2.1.0':
dependencies: dependencies:
'@formatjs/ecma402-abstract': 1.11.4 '@formatjs/ecma402-abstract': 1.11.4
@ -8532,6 +8571,12 @@ snapshots:
'@formatjs/icu-skeleton-parser': 1.8.13 '@formatjs/icu-skeleton-parser': 1.8.13
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/icu-messageformat-parser@2.11.2':
dependencies:
'@formatjs/ecma402-abstract': 2.3.4
'@formatjs/icu-skeleton-parser': 1.8.14
tslib: 2.8.1
'@formatjs/icu-skeleton-parser@1.3.6': '@formatjs/icu-skeleton-parser@1.3.6':
dependencies: dependencies:
'@formatjs/ecma402-abstract': 1.11.4 '@formatjs/ecma402-abstract': 1.11.4
@ -8542,6 +8587,11 @@ snapshots:
'@formatjs/ecma402-abstract': 2.3.3 '@formatjs/ecma402-abstract': 2.3.3
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/icu-skeleton-parser@1.8.14':
dependencies:
'@formatjs/ecma402-abstract': 2.3.4
tslib: 2.8.1
'@formatjs/intl-localematcher@0.2.25': '@formatjs/intl-localematcher@0.2.25':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@ -8550,6 +8600,10 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@formatjs/intl-localematcher@0.6.1':
dependencies:
tslib: 2.8.1
'@formatjs/intl-numberformat@5.7.6': '@formatjs/intl-numberformat@5.7.6':
dependencies: dependencies:
'@formatjs/ecma402-abstract': 1.4.0 '@formatjs/ecma402-abstract': 1.4.0
@ -8689,7 +8743,7 @@ snapshots:
'@internationalized/message@3.1.6': '@internationalized/message@3.1.6':
dependencies: dependencies:
'@swc/helpers': 0.5.15 '@swc/helpers': 0.5.15
intl-messageformat: 10.7.15 intl-messageformat: 10.7.16
'@internationalized/number@3.6.0': '@internationalized/number@3.6.0':
dependencies: dependencies:
@ -10551,13 +10605,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@umami/react-zen@0.66.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))': '@umami/react-zen@0.71.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))':
dependencies: dependencies:
'@fontsource/jetbrains-mono': 5.2.5 '@fontsource/jetbrains-mono': 5.2.5
'@react-aria/focus': 3.20.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@react-aria/focus': 3.20.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@react-spring/web': 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@react-spring/web': 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
classnames: 2.5.1 classnames: 2.5.1
glob: 10.3.10 glob: 10.4.5
highlight.js: 11.11.1 highlight.js: 11.11.1
lucide-react: 0.479.0(react@19.0.0) lucide-react: 0.479.0(react@19.0.0)
next: 15.2.4(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next: 15.2.4(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -12361,6 +12415,15 @@ snapshots:
minipass: 7.1.2 minipass: 7.1.2
path-scurry: 1.11.1 path-scurry: 1.11.1
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
glob@7.2.3: glob@7.2.3:
dependencies: dependencies:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
@ -12564,6 +12627,13 @@ snapshots:
'@formatjs/icu-messageformat-parser': 2.11.1 '@formatjs/icu-messageformat-parser': 2.11.1
tslib: 2.8.1 tslib: 2.8.1
intl-messageformat@10.7.16:
dependencies:
'@formatjs/ecma402-abstract': 2.3.4
'@formatjs/fast-memoize': 2.2.7
'@formatjs/icu-messageformat-parser': 2.11.2
tslib: 2.8.1
ipaddr.js@2.2.0: {} ipaddr.js@2.2.0: {}
is-array-buffer@3.0.5: is-array-buffer@3.0.5:
@ -12791,6 +12861,12 @@ snapshots:
optionalDependencies: optionalDependencies:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jake@10.9.2: jake@10.9.2:
dependencies: dependencies:
async: 3.2.6 async: 3.2.6
@ -13735,6 +13811,8 @@ snapshots:
p-try@2.2.0: {} p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
parent-module@1.0.1: parent-module@1.0.1:
dependencies: dependencies:
callsites: 3.1.0 callsites: 3.1.0

View file

@ -5,7 +5,7 @@ import { usePathname } from 'next/navigation';
import { UpdateNotice } from './UpdateNotice'; import { UpdateNotice } from './UpdateNotice';
import { Nav } from '@/app/(main)/Nav'; import { Nav } from '@/app/(main)/Nav';
import { MenuBar } from '@/app/(main)/MenuBar'; import { MenuBar } from '@/app/(main)/MenuBar';
import { Page } from '@/components/layout/Page'; import { Page } from '@/components/common/Page';
import { useLoginQuery, useConfig } from '@/components/hooks'; import { useLoginQuery, useConfig } from '@/components/hooks';
export function App({ children }) { export function App({ children }) {
@ -33,7 +33,13 @@ export function App({ children }) {
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" overflow="hidden"> <Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" overflow="hidden">
<Nav gridColumn="1 / 2" gridRow="1 / 3" /> <Nav gridColumn="1 / 2" gridRow="1 / 3" />
<MenuBar gridColumn="2 / 3" gridRow="1 / 2" /> <MenuBar gridColumn="2 / 3" gridRow="1 / 2" />
<Column gridColumn="2 / 3" gridRow="2 / 3" alignItems="center" overflow="auto"> <Column
gridColumn="2 / 3"
gridRow="2 / 3"
alignItems="center"
overflow="auto"
backgroundColor="1"
>
<Page> <Page>
{children} {children}
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && ( {process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (

View file

@ -17,6 +17,8 @@ export function MenuBar(props: RowProps) {
paddingY="3" paddingY="3"
paddingX="3" paddingX="3"
paddingRight="5" paddingRight="5"
backgroundColor="1"
style={{ borderBottom: '1px solid var(--border-color)' }}
> >
<Row> <Row>
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet"> <Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">

View file

@ -2,8 +2,8 @@ import { Button } from '@umami/react-zen';
import Link from 'next/link'; import Link from 'next/link';
import Script from 'next/script'; import Script from 'next/script';
import { WebsiteSelect } from '@/components/input/WebsiteSelect'; import { WebsiteSelect } from '@/components/input/WebsiteSelect';
import { Page } from '@/components/layout/Page'; import { Page } from '@/components/common/Page';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { EventsChart } from '@/components/metrics/EventsChart'; import { EventsChart } from '@/components/metrics/EventsChart';
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart'; import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
import { useApi, useNavigation } from '@/components/hooks'; import { useApi, useNavigation } from '@/components/hooks';

View file

@ -1,6 +1,6 @@
'use client'; 'use client';
import { Icon, Icons, Loading, Text } from '@umami/react-zen'; import { Icon, Icons, Loading, Text } from '@umami/react-zen';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { Pager } from '@/components/common/Pager'; import { Pager } from '@/components/common/Pager';
import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList'; import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList';
import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton'; import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton';

View file

@ -1,4 +1,4 @@
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
export function ProfileHeader() { export function ProfileHeader() {

View file

@ -1,4 +1,4 @@
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { Icon, Icons, Text } from '@umami/react-zen'; import { Icon, Icons, Text } from '@umami/react-zen';
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks'; import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
import { LinkButton } from '@/components/common/LinkButton'; import { LinkButton } from '@/components/common/LinkButton';

View file

@ -4,7 +4,7 @@ import { useMessages, useLoginQuery, useNavigation } from '@/components/hooks';
import { REPORT_TYPES } from '@/lib/constants'; import { REPORT_TYPES } from '@/lib/constants';
import { ReportDeleteButton } from './ReportDeleteButton'; import { ReportDeleteButton } from './ReportDeleteButton';
export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomain?: boolean }) { export function ReportsTable({ data = [] }: { data: any[]; showDomain?: boolean }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery(); const { user } = useLoginQuery();
const { renderTeamUrl } = useNavigation(); const { renderTeamUrl } = useNavigation();
@ -20,11 +20,6 @@ export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomai
); );
}} }}
</DataColumn> </DataColumn>
{showDomain && (
<DataColumn id="domain" label={formatMessage(labels.domain)}>
{(row: any) => row?.website?.domain}
</DataColumn>
)}
<DataColumn id="action" label="" align="end"> <DataColumn id="action" label="" align="end">
{(row: any) => { {(row: any) => {
const { id, name, userId, website } = row; const { id, name, userId, website } = row;

View file

@ -1,3 +0,0 @@
.dropdown div {
max-height: 300px;
}

View file

@ -1,11 +1,9 @@
import { useContext } from 'react';
import { Column, Label } from '@umami/react-zen'; import { Column, Label } from '@umami/react-zen';
import { useReport } from '@/components/hooks';
import { parseDateRange } from '@/lib/date'; import { parseDateRange } from '@/lib/date';
import { DateFilter } from '@/components/input/DateFilter'; import { DateFilter } from '@/components/input/DateFilter';
import { WebsiteSelect } from '@/components/input/WebsiteSelect'; import { WebsiteSelect } from '@/components/input/WebsiteSelect';
import { useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks'; import { useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks';
import { ReportContext } from './Report';
import styles from './BaseParameters.module.css';
export interface BaseParametersProps { export interface BaseParametersProps {
showWebsiteSelect?: boolean; showWebsiteSelect?: boolean;
@ -20,7 +18,7 @@ export function BaseParameters({
showDateSelect = true, showDateSelect = true,
allowDateSelect = true, allowDateSelect = true,
}: BaseParametersProps) { }: BaseParametersProps) {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { teamId } = useNavigation(); const { teamId } = useNavigation();
const { parameters } = report || {}; const { parameters } = report || {};
@ -50,7 +48,7 @@ export function BaseParameters({
</Column> </Column>
)} )}
{showDateSelect && ( {showDateSelect && (
<Column className={styles.dropdown}> <Column>
<Label>{formatMessage(labels.dateRange)}</Label> <Label>{formatMessage(labels.dateRange)}</Label>
{allowDateSelect && ( {allowDateSelect && (
<DateFilter <DateFilter

View file

@ -1,7 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { REPORT_PARAMETERS } from '@/lib/constants'; import { REPORT_PARAMETERS } from '@/lib/constants';
import { PopupForm } from './PopupForm';
import { FieldSelectForm } from './FieldSelectForm'; import { FieldSelectForm } from './FieldSelectForm';
export function FieldAddForm({ export function FieldAddForm({
@ -39,9 +38,7 @@ export function FieldAddForm({
}; };
return createPortal( return createPortal(
<PopupForm> !selected && <FieldSelectForm fields={fields} onSelect={handleSelect} />,
{!selected && <FieldSelectForm fields={fields} onSelect={handleSelect} />}
</PopupForm>,
document.body, document.body,
); );
} }

View file

@ -1,13 +1,11 @@
import { useFields, useMessages } from '@/components/hooks'; import { useFields, useMessages, useReport } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { useContext } from 'react'; import { Button, Row, Label, Icon, Popover, MenuTrigger, Column } from '@umami/react-zen';
import { Button, Row, Label, Icon, Popover, MenuTrigger } from '@umami/react-zen';
import { FieldSelectForm } from '../[reportId]/FieldSelectForm'; import { FieldSelectForm } from '../[reportId]/FieldSelectForm';
import { ParameterList } from '../[reportId]/ParameterList'; import { ParameterList } from '../[reportId]/ParameterList';
import { ReportContext } from './Report';
export function FieldParameters() { export function FieldParameters() {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { parameters } = report || {}; const { parameters } = report || {};
const { fields } = parameters || {}; const { fields } = parameters || {};
@ -26,12 +24,12 @@ export function FieldParameters() {
const AddButton = () => { const AddButton = () => {
return ( return (
<MenuTrigger> <MenuTrigger>
<Button size="sm"> <Button variant="quiet">
<Icon> <Icon size="sm">
<Icons.Plus /> <Icons.Plus />
</Icon> </Icon>
</Button> </Button>
<Popover placement="start"> <Popover placement="right top">
<FieldSelectForm <FieldSelectForm
fields={fieldOptions.filter(({ name }) => !fields.find(f => f.name === name))} fields={fieldOptions.filter(({ name }) => !fields.find(f => f.name === name))}
onSelect={handleAdd} onSelect={handleAdd}
@ -43,8 +41,11 @@ export function FieldParameters() {
}; };
return ( return (
<Row> <Column gap="3">
<Label>{formatMessage(labels.fields)}</Label> <Row justifyContent="space-between">
<Label>{formatMessage(labels.fields)}</Label>
<AddButton />
</Row>
<ParameterList> <ParameterList>
{fields.map(({ name }) => { {fields.map(({ name }) => {
return ( return (
@ -54,7 +55,6 @@ export function FieldParameters() {
); );
})} })}
</ParameterList> </ParameterList>
<AddButton /> </Column>
</Row>
); );
} }

View file

@ -1,6 +1,5 @@
import { List, ListItem, Label, Column } from '@umami/react-zen'; import { Menu, MenuItem, Text, MenuSection } from '@umami/react-zen';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import styles from './FieldSelectForm.module.css';
import { Key } from 'react'; import { Key } from 'react';
export interface FieldSelectFormProps { export interface FieldSelectFormProps {
@ -13,18 +12,17 @@ export function FieldSelectForm({ fields = [], onSelect, showType = true }: Fiel
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<Column> <Menu onSelectionChange={key => onSelect(fields[key as any])}>
<Label>{formatMessage(labels.fields)}</Label> <MenuSection title={formatMessage(labels.fields)}>
<List onSelectionChange={key => onSelect(fields[key as any])}>
{fields.map(({ name, label, type }: any, index: Key) => { {fields.map(({ name, label, type }: any, index: Key) => {
return ( return (
<ListItem key={index} className={styles.item}> <MenuItem key={index}>
<div>{label || name}</div> <Text>{label || name}</Text>
{showType && type && <div className={styles.type}>{type}</div>} {showType && type && <Text color="muted">{type}</Text>}
</ListItem> </MenuItem>
); );
})} })}
</List> </MenuSection>
</Column> </Menu>
); );
} }

View file

@ -1,17 +1,23 @@
import { useContext } from 'react'; import { useMessages, useFormat, useFilters, useFields, useReport } from '@/components/hooks';
import { useMessages, useFormat, useFilters, useFields } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { Button, Row, Label, Icon, Popover, MenuTrigger } from '@umami/react-zen'; import {
Button,
Row,
Column,
Label,
Icon,
Popover,
MenuTrigger,
Focusable,
Text,
} from '@umami/react-zen';
import { FilterSelectForm } from '../[reportId]/FilterSelectForm'; import { FilterSelectForm } from '../[reportId]/FilterSelectForm';
import { ParameterList } from '../[reportId]/ParameterList'; import { ParameterList } from '../[reportId]/ParameterList';
import { PopupForm } from '../[reportId]/PopupForm';
import { ReportContext } from './Report';
import { FieldFilterEditForm } from '../[reportId]/FieldFilterEditForm'; import { FieldFilterEditForm } from '../[reportId]/FieldFilterEditForm';
import { isSearchOperator } from '@/lib/params'; import { isSearchOperator } from '@/lib/params';
import styles from './FilterParameters.module.css';
export function FilterParameters() { export function FilterParameters() {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat(); const { formatValue } = useFormat();
const { parameters } = report || {}; const { parameters } = report || {};
@ -45,28 +51,26 @@ export function FilterParameters() {
const AddButton = () => { const AddButton = () => {
return ( return (
<MenuTrigger> <MenuTrigger>
<Button size="sm"> <Button variant="quiet">
<Icon> <Icon size="sm">
<Icons.Plus /> <Icons.Plus />
</Icon> </Icon>
</Button> </Button>
<Popover placement="bottom start"> <Popover placement="right top">
<PopupForm> <FilterSelectForm
<FilterSelectForm websiteId={websiteId}
websiteId={websiteId} fields={fields.filter(({ name }) => !filters.find(f => f.name === name))}
fields={fields.filter(({ name }) => !filters.find(f => f.name === name))} startDate={dateRange?.startDate}
startDate={dateRange?.startDate} endDate={dateRange?.endDate}
endDate={dateRange?.endDate} onChange={handleAdd}
onChange={handleAdd} />
/>
</PopupForm>
</Popover> </Popover>
</MenuTrigger> </MenuTrigger>
); );
}; };
return ( return (
<> <Column gap="3">
<Row justifyContent="space-between"> <Row justifyContent="space-between">
<Label>{formatMessage(labels.filters)}</Label> <Label>{formatMessage(labels.filters)}</Label>
<AddButton /> <AddButton />
@ -94,7 +98,7 @@ export function FilterParameters() {
}, },
)} )}
</ParameterList> </ParameterList>
</> </Column>
); );
} }
@ -113,12 +117,16 @@ const FilterParameter = ({
return ( return (
<MenuTrigger> <MenuTrigger>
<div className={styles.item}> <Focusable>
<div className={styles.label}>{label}</div> <Row gap="3" alignItems="center">
<div className={styles.op}>{operatorLabels[operator]}</div> <Text>{label}</Text>
<div className={styles.value}>{value}</div> <Text size="2" transform="uppercase">
</div> {operatorLabels[operator]}
<Popover className={styles.edit} placement="right top"> </Text>
<Text weight="bold">{value}</Text>
</Row>
</Focusable>
<Popover placement="right top">
{(close: any) => ( {(close: any) => (
<FieldFilterEditForm <FieldFilterEditForm
websiteId={websiteId} websiteId={websiteId}

View file

@ -1,10 +1,8 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Icon } from '@umami/react-zen'; import { Icon, Row, Text, Button, Column } from '@umami/react-zen';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { Empty } from '@/components/common/Empty'; import { Empty } from '@/components/common/Empty';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import styles from './ParameterList.module.css';
import classNames from 'classnames';
export interface ParameterListProps { export interface ParameterListProps {
children?: ReactNode; children?: ReactNode;
@ -14,34 +12,43 @@ export function ParameterList({ children }: ParameterListProps) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<div className={styles.list}> <Column>
{!children && <Empty message={formatMessage(labels.none)} />} {!children && <Empty message={formatMessage(labels.none)} />}
{children} {children}
</div> </Column>
); );
} }
const Item = ({ const Item = ({
children,
className,
icon, icon,
onClick, onClick,
onRemove, onRemove,
children,
}: { }: {
children?: ReactNode;
className?: string;
icon?: ReactNode; icon?: ReactNode;
onClick?: () => void; onClick?: () => void;
onRemove?: () => void; onRemove?: () => void;
children?: ReactNode;
}) => { }) => {
return ( return (
<div className={classNames(styles.item, className)} onClick={onClick}> <Row
{icon && <Icon className={styles.icon}>{icon}</Icon>} gap="3"
<div className={styles.value}>{children}</div> alignItems="center"
<Icon className={styles.close} onClick={onRemove}> justifyContent="space-between"
<Icons.Close /> onClick={onClick}
</Icon> backgroundColor="2"
</div> border
borderRadius="2"
paddingLeft="3"
>
{icon && <Icon>{icon}</Icon>}
<Text>{children}</Text>
<Button onPress={onRemove} variant="quiet">
<Icon>
<Icons.Close />
</Icon>
</Button>
</Row>
); );
}; };

View file

@ -1,9 +0,0 @@
.form {
background: var(--base50);
min-width: 300px;
padding: 20px;
border: 1px solid var(--base400);
border-radius: var(--border-radius);
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
z-index: 1000;
}

View file

@ -1,23 +0,0 @@
import { CSSProperties, ReactNode } from 'react';
import classNames from 'classnames';
import styles from './PopupForm.module.css';
export function PopupForm({
className,
style,
children,
}: {
className?: string;
style?: CSSProperties;
children: ReactNode;
}) {
return (
<div
className={classNames(styles.form, className)}
style={style}
onClick={e => e.stopPropagation()}
>
{children}
</div>
);
}

View file

@ -1,8 +1,6 @@
import { createContext, ReactNode } from 'react'; import { createContext, ReactNode } from 'react';
import { Loading } from '@umami/react-zen'; import { Loading, Grid } from '@umami/react-zen';
import classNames from 'classnames';
import { useReportQuery } from '@/components/hooks'; import { useReportQuery } from '@/components/hooks';
import styles from './Report.module.css';
export const ReportContext = createContext(null); export const ReportContext = createContext(null);
@ -10,12 +8,10 @@ export function Report({
reportId, reportId,
defaultParameters, defaultParameters,
children, children,
className,
}: { }: {
reportId: string; reportId: string;
defaultParameters: { type: string; parameters: { [key: string]: any } }; defaultParameters: { type: string; parameters: { [key: string]: any } };
children: ReactNode; children: ReactNode;
className?: string;
}) { }) {
const report = useReportQuery(reportId, defaultParameters); const report = useReportQuery(reportId, defaultParameters);
@ -25,7 +21,9 @@ export function Report({
return ( return (
<ReportContext.Provider value={report}> <ReportContext.Provider value={report}>
<div className={classNames(styles.container, className)}>{children}</div> <Grid rows="auto 1fr" columns="auto 1fr" gap="6">
{children}
</Grid>
</ReportContext.Provider> </ReportContext.Provider>
); );
} }

View file

@ -1,13 +1,12 @@
import { useContext } from 'react'; import { Panel } from '@/components/common/Panel';
import { ReportContext } from './Report'; import { useReport } from '@/components/hooks';
import styles from './ReportBody.module.css';
export function ReportBody({ children }) { export function ReportBody({ children }) {
const { report } = useContext(ReportContext); const { report } = useReport();
if (!report) { if (!report) {
return null; return null;
} }
return <div className={styles.body}>{children}</div>; return <Panel>{children}</Panel>;
} }

View file

@ -1,36 +0,0 @@
.header {
display: grid;
grid-template-columns: 1fr min-content;
align-items: center;
grid-row: 1 / 2;
grid-column: 1 / 3;
margin: 20px 0 40px 0;
}
.title {
display: flex;
flex-direction: row;
align-items: center;
font-size: 24px;
font-weight: 700;
gap: 20px;
height: 60px;
}
.type {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
color: var(--base600);
}
.description {
color: var(--font-color300);
max-width: 500px;
height: 30px;
}
.actions {
display: flex;
align-items: center;
}

View file

@ -1,13 +1,18 @@
import { useContext } from 'react'; import {
import { Icon, LoadingButton, InlineEditField, useToast } from '@umami/react-zen'; Row,
import { useMessages, useApi, useNavigation } from '@/components/hooks'; Column,
import { ReportContext } from './Report'; Text,
import styles from './ReportHeader.module.css'; Heading,
Icon,
LoadingButton,
InlineEditField,
useToast,
} from '@umami/react-zen';
import { useMessages, useApi, useNavigation, useReport } from '@/components/hooks';
import { REPORT_TYPES } from '@/lib/constants'; import { REPORT_TYPES } from '@/lib/constants';
import { Breadcrumb } from '@/components/common/Breadcrumb';
export function ReportHeader({ icon }) { export function ReportHeader({ icon }) {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useReport();
const { formatMessage, labels, messages } = useMessages(); const { formatMessage, labels, messages } = useMessages();
const { toast } = useToast(); const { toast } = useToast();
const { router, renderTeamUrl } = useNavigation(); const { router, renderTeamUrl } = useNavigation();
@ -54,41 +59,33 @@ export function ReportHeader({ icon }) {
} }
return ( return (
<div className={styles.header}> <Column marginY="6" gap="3" gridColumn="1 / 3">
<div> <Row gap="3" alignItems="center">
<div className={styles.type}> <Icon size="sm">{icon}</Icon>
<Breadcrumb <Text transform="uppercase" weight="bold">
data={[ {formatMessage(
{ label: formatMessage(labels.reports), url: renderTeamUrl('/reports') }, labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)],
{ )}
label: formatMessage( </Text>
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)], </Row>
), <Row justifyContent="space-between" alignItems="center">
}, <Row gap="6">
]} <Column gap="3">
/> <Row gap="3" alignItems="center">
</div> <InlineEditField key={name} name="name" value={name} onCommit={handleNameChange}>
<div className={styles.title}> <Heading>{name}</Heading>
<Icon size="lg">{icon}</Icon> </InlineEditField>
<InlineEditField </Row>
key={name} <InlineEditField
name="name" key={description}
value={name} name="description"
placeholder={defaultName} value={description}
onCommit={handleNameChange} onCommit={handleDescriptionChange}
/> >
</div> <Text>{description || `+ ${formatMessage(labels.addDescription)}`}</Text>
<div className={styles.description}> </InlineEditField>
<InlineEditField </Column>
key={description} </Row>
name="description"
value={description}
placeholder={`+ ${formatMessage(labels.addDescription)}`}
onCommit={handleDescriptionChange}
/>
</div>
</div>
<div className={styles.actions}>
<LoadingButton <LoadingButton
variant="primary" variant="primary"
isLoading={isCreating || isUpdating} isLoading={isCreating || isUpdating}
@ -97,7 +94,7 @@ export function ReportHeader({ icon }) {
> >
{formatMessage(labels.save)} {formatMessage(labels.save)}
</LoadingButton> </LoadingButton>
</div> </Row>
</div> </Column>
); );
} }

View file

@ -1,25 +1,26 @@
import { useContext, useState } from 'react'; import { useState } from 'react';
import { Icon, Icons } from '@umami/react-zen'; import { Button, Column, Icon, Row } from '@umami/react-zen';
import classNames from 'classnames'; import { Icons } from '@/components/icons';
import { ReportContext } from './Report'; import { useReport } from '@/components/hooks';
import styles from './ReportMenu.module.css';
export function ReportMenu({ children }) { export function ReportMenu({ children }) {
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const { report } = useContext(ReportContext); const { report } = useReport();
if (!report) { if (!report) {
return null; return null;
} }
return ( return (
<div className={classNames(styles.menu, collapsed && styles.collapsed)}> <Column>
<div className={styles.button} onClick={() => setCollapsed(!collapsed)}> <Row alignItems="center" justifyContent="flex-end">
<Icon rotate={collapsed ? -90 : 90}> <Button variant="quiet" onPress={() => setCollapsed(!collapsed)}>
<Icons.Chevron /> <Icon>
</Icon> <Icons.PanelLeft />
</div> </Icon>
</Button>
</Row>
{!collapsed && children} {!collapsed && children}
</div> </Column>
); );
} }

View file

@ -1,7 +1,7 @@
import { Icon, Text, Row, Column, Grid } from '@umami/react-zen'; import { Icon, Text, Row, Column, Grid } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { LinkButton } from '@/components/common/LinkButton'; import { LinkButton } from '@/components/common/LinkButton';
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) { export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
@ -71,7 +71,7 @@ function ReportItem({ title, description, url, icon }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<Column gap="6" padding="6" borderSize="1" borderRadius="3" justifyContent="space-between"> <Column gap="6" padding="6" border borderRadius="3" justifyContent="space-between">
<Row gap="3" alignItems="center"> <Row gap="3" alignItems="center">
<Icon size="md">{icon}</Icon> <Icon size="md">{icon}</Icon>
<Text size="5" weight="bold"> <Text size="5" weight="bold">

View file

@ -1,4 +1,3 @@
import { useContext } from 'react';
import { import {
Form, Form,
FormField, FormField,
@ -10,9 +9,8 @@ import {
} from '@umami/react-zen'; } from '@umami/react-zen';
import { Empty } from '@/components/common/Empty'; import { Empty } from '@/components/common/Empty';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { useApi, useMessages } from '@/components/hooks'; import { useApi, useMessages, useReport } from '@/components/hooks';
import { DATA_TYPES, REPORT_PARAMETERS } from '@/lib/constants'; import { DATA_TYPES, REPORT_PARAMETERS } from '@/lib/constants';
import { ReportContext } from '../[reportId]/Report';
import { FieldAddForm } from '../[reportId]/FieldAddForm'; import { FieldAddForm } from '../[reportId]/FieldAddForm';
import { ParameterList } from '../[reportId]/ParameterList'; import { ParameterList } from '../[reportId]/ParameterList';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
@ -35,7 +33,7 @@ function useFields(websiteId, startDate, endDate) {
} }
export function EventDataParameters() { export function EventDataParameters() {
const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { report, runReport, updateReport, isRunning } = useReport();
const { formatMessage, labels, messages } = useMessages(); const { formatMessage, labels, messages } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};
const { websiteId, dateRange, fields, filters, groups } = parameters || {}; const { websiteId, dateRange, fields, filters, groups } = parameters || {};

View file

@ -1,10 +1,8 @@
import { useContext } from 'react';
import { DataTable, DataColumn } from '@umami/react-zen'; import { DataTable, DataColumn } from '@umami/react-zen';
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { ReportContext } from '../[reportId]/Report';
export function EventDataTable() { export function EventDataTable() {
const { report } = useContext(ReportContext); const { report } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (

View file

@ -1,7 +1,5 @@
import { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { ReportContext } from '../[reportId]/Report';
import { formatLongNumber } from '@/lib/format'; import { formatLongNumber } from '@/lib/format';
import styles from './FunnelChart.module.css'; import styles from './FunnelChart.module.css';
@ -11,7 +9,7 @@ export interface FunnelChartProps {
} }
export function FunnelChart({ className }: FunnelChartProps) { export function FunnelChart({ className }: FunnelChartProps) {
const { report } = useContext(ReportContext); const { report } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { data } = report || {}; const { data } = report || {};

View file

@ -1,5 +1,4 @@
import { useContext } from 'react'; import { useMessages, useReport } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { import {
Icon, Icon,
Form, Form,
@ -13,14 +12,12 @@ import {
} from '@umami/react-zen'; } from '@umami/react-zen';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { FunnelStepAddForm } from './FunnelStepAddForm'; import { FunnelStepAddForm } from './FunnelStepAddForm';
import { ReportContext } from '../[reportId]/Report';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
import { ParameterList } from '../[reportId]/ParameterList'; import { ParameterList } from '../[reportId]/ParameterList';
import { PopupForm } from '../[reportId]/PopupForm';
import styles from './FunnelParameters.module.css'; import styles from './FunnelParameters.module.css';
export function FunnelParameters() { export function FunnelParameters() {
const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { report, runReport, updateReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};
@ -66,9 +63,7 @@ export function FunnelParameters() {
</Icon> </Icon>
</Button> </Button>
<Popover placement="start"> <Popover placement="start">
<PopupForm> <FunnelStepAddForm onChange={handleAddStep} />
<FunnelStepAddForm onChange={handleAddStep} />
</PopupForm>
</Popover> </Popover>
</DialogTrigger> </DialogTrigger>
); );
@ -90,7 +85,6 @@ export function FunnelParameters() {
return ( return (
<DialogTrigger key={index}> <DialogTrigger key={index}>
<ParameterList.Item <ParameterList.Item
className={styles.item}
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />} icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
onRemove={() => handleRemoveStep(index)} onRemove={() => handleRemoveStep(index)}
> >
@ -100,13 +94,11 @@ export function FunnelParameters() {
</ParameterList.Item> </ParameterList.Item>
<Popover placement="start"> <Popover placement="start">
{({ close }: any) => ( {({ close }: any) => (
<PopupForm> <FunnelStepAddForm
<FunnelStepAddForm type={step.type}
type={step.type} value={step.value}
value={step.value} onChange={handleUpdateStep.bind(null, close, index)}
onChange={handleUpdateStep.bind(null, close, index)} />
/>
</PopupForm>
)} )}
</Popover> </Popover>
</DialogTrigger> </DialogTrigger>

View file

@ -1,12 +1,10 @@
import { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { ReportContext } from '../[reportId]/Report';
import { formatLongNumber } from '@/lib/format'; import { formatLongNumber } from '@/lib/format';
import styles from './GoalsChart.module.css'; import styles from './GoalsChart.module.css';
export function GoalsChart({ className }: { className?: string; isLoading?: boolean }) { export function GoalsChart({ className }: { className?: string; isLoading?: boolean }) {
const { report } = useContext(ReportContext); const { report } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { data } = report || {}; const { data } = report || {};

View file

@ -1,7 +1,6 @@
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import { useContext } from 'react';
import { import {
Button, Button,
Form, Form,
@ -15,12 +14,11 @@ import {
} from '@umami/react-zen'; } from '@umami/react-zen';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
import { ParameterList } from '../[reportId]/ParameterList'; import { ParameterList } from '../[reportId]/ParameterList';
import { ReportContext } from '../[reportId]/Report';
import { GoalsAddForm } from './GoalsAddForm'; import { GoalsAddForm } from './GoalsAddForm';
import styles from './GoalsParameters.module.css'; import styles from './GoalsParameters.module.css';
export function GoalsParameters() { export function GoalsParameters() {
const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { report, runReport, updateReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};
@ -76,7 +74,7 @@ export function GoalsParameters() {
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}> <Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
<BaseParameters allowWebsiteSelect={!id} /> <BaseParameters allowWebsiteSelect={!id} />
<AddGoalsButton /> <AddGoalsButton />
<FormField label={formatMessage(labels.goals)}> <FormField name="goal" label={formatMessage(labels.goals)}>
<ParameterList> <ParameterList>
{goals.map( {goals.map(
( (

View file

@ -1,13 +1,11 @@
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { useContext } from 'react';
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen'; import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
import { ReportContext } from '../[reportId]/Report';
import { FieldParameters } from '../[reportId]/FieldParameters'; import { FieldParameters } from '../[reportId]/FieldParameters';
import { FilterParameters } from '../[reportId]/FilterParameters'; import { FilterParameters } from '../[reportId]/FilterParameters';
export function InsightsParameters() { export function InsightsParameters() {
const { report, runReport, isRunning } = useContext(ReportContext); const { report, runReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};
const { websiteId, dateRange, fields, filters } = parameters || {}; const { websiteId, dateRange, fields, filters } = parameters || {};
@ -20,7 +18,7 @@ export function InsightsParameters() {
}; };
return ( return (
<Form values={parameters} onSubmit={handleSubmit}> <Form values={parameters} onSubmit={handleSubmit} gap="6">
<BaseParameters allowWebsiteSelect={!id} /> <BaseParameters allowWebsiteSelect={!id} />
{parametersSelected && <FieldParameters />} {parametersSelected && <FieldParameters />}
{parametersSelected && <FilterParameters />} {parametersSelected && <FilterParameters />}

View file

@ -1,13 +1,12 @@
import { useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { DataTable, DataColumn } from '@umami/react-zen'; import { DataTable, DataColumn } from '@umami/react-zen';
import { useFormat, useMessages } from '@/components/hooks'; import { useFormat, useMessages, useReport } from '@/components/hooks';
import { ReportContext } from '../[reportId]/Report';
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
import { formatShortTime } from '@/lib/format'; import { formatShortTime } from '@/lib/format';
export function InsightsTable() { export function InsightsTable() {
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
const { report } = useContext(ReportContext); const { report } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat(); const { formatValue } = useFormat();

View file

@ -1,5 +1,4 @@
import { useContext } from 'react'; import { useMessages, useReport } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { import {
Select, Select,
Form, Form,
@ -9,11 +8,10 @@ import {
FormSubmitButton, FormSubmitButton,
TextField, TextField,
} from '@umami/react-zen'; } from '@umami/react-zen';
import { ReportContext } from '../[reportId]/Report';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
export function JourneyParameters() { export function JourneyParameters() {
const { report, runReport, isRunning } = useContext(ReportContext); const { report, runReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};

View file

@ -1,10 +1,9 @@
import { useContext, useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { TooltipTrigger, Tooltip, Focusable } from '@umami/react-zen'; import { TooltipTrigger, Tooltip, Focusable } from '@umami/react-zen';
import { firstBy } from 'thenby'; import { firstBy } from 'thenby';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEscapeKey, useMessages } from '@/components/hooks'; import { useEscapeKey, useMessages, useReport } from '@/components/hooks';
import { objectToArray } from '@/lib/data'; import { objectToArray } from '@/lib/data';
import { ReportContext } from '../[reportId]/Report';
import styles from './JourneyView.module.css'; import styles from './JourneyView.module.css';
import { formatLongNumber } from '@/lib/format'; import { formatLongNumber } from '@/lib/format';
@ -15,7 +14,7 @@ const LINE_WIDTH = 3;
export function JourneyView() { export function JourneyView() {
const [selectedNode, setSelectedNode] = useState(null); const [selectedNode, setSelectedNode] = useState(null);
const [activeNode, setActiveNode] = useState(null); const [activeNode, setActiveNode] = useState(null);
const { report } = useContext(ReportContext); const { report } = useReport();
const { data, parameters } = report || {}; const { data, parameters } = report || {};
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();

View file

@ -1,12 +1,9 @@
import { useContext } from 'react'; import { useMessages, useReport } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen'; import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
import { ReportContext } from '../[reportId]/Report';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
import { parseDateRange } from '@/lib/date';
export function RetentionParameters() { export function RetentionParameters() {
const { report, runReport, isRunning, updateReport } = useContext(ReportContext); const { report, runReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};

View file

@ -1,8 +1,6 @@
import { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ReportContext } from '../[reportId]/Report';
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
import { useMessages, useLocale } from '@/components/hooks'; import { useMessages, useLocale, useReport } from '@/components/hooks';
import { formatDate } from '@/lib/date'; import { formatDate } from '@/lib/date';
import styles from './RetentionTable.module.css'; import styles from './RetentionTable.module.css';
@ -11,7 +9,7 @@ const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
export function RetentionTable({ days = DAYS }) { export function RetentionTable({ days = DAYS }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { locale } = useLocale(); const { locale } = useLocale();
const { report } = useContext(ReportContext); const { report } = useReport();
const { data } = report || {}; const { data } = report || {};
if (!data) { if (!data) {

View file

@ -1,12 +1,10 @@
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { useRevenueValuesQuery } from '@/components/hooks/queries/useRevenueValuesQuery'; import { useRevenueValuesQuery } from '@/components/hooks/queries/useRevenueValuesQuery';
import { useContext } from 'react';
import { Select, Form, FormButtons, FormField, ListItem, FormSubmitButton } from '@umami/react-zen'; import { Select, Form, FormButtons, FormField, ListItem, FormSubmitButton } from '@umami/react-zen';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
import { ReportContext } from '../[reportId]/Report';
export function RevenueParameters() { export function RevenueParameters() {
const { report, runReport, isRunning } = useContext(ReportContext); const { report, runReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};
const { websiteId, dateRange } = parameters || {}; const { websiteId, dateRange } = parameters || {};

View file

@ -1,12 +1,10 @@
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
import { useMessages } from '@/components/hooks'; import { useMessages, useReport } from '@/components/hooks';
import { useContext } from 'react';
import { DataColumn, DataTable } from '@umami/react-zen'; import { DataColumn, DataTable } from '@umami/react-zen';
import { ReportContext } from '../[reportId]/Report';
import { formatLongCurrency } from '@/lib/format'; import { formatLongCurrency } from '@/lib/format';
export function RevenueTable() { export function RevenueTable() {
const { report } = useContext(ReportContext); const { report } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { data } = report || {}; const { data } = report || {};

View file

@ -3,16 +3,15 @@ import { colord } from 'colord';
import { BarChart } from '@/components/charts/BarChart'; import { BarChart } from '@/components/charts/BarChart';
import { PieChart } from '@/components/charts/PieChart'; import { PieChart } from '@/components/charts/PieChart';
import { TypeIcon } from '@/components/common/TypeIcon'; import { TypeIcon } from '@/components/common/TypeIcon';
import { useCountryNames, useLocale, useMessages } from '@/components/hooks'; import { useCountryNames, useLocale, useMessages, useReport } from '@/components/hooks';
import { GridRow } from '@/components/layout/GridRow'; import { GridRow } from '@/components/common/GridRow';
import { ListTable } from '@/components/metrics/ListTable'; import { ListTable } from '@/components/metrics/ListTable';
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar'; import { MetricsBar } from '@/components/metrics/MetricsBar';
import { renderDateLabels } from '@/lib/charts'; import { renderDateLabels } from '@/lib/charts';
import { CHART_COLORS } from '@/lib/constants'; import { CHART_COLORS } from '@/lib/constants';
import { formatLongCurrency, formatLongNumber } from '@/lib/format'; import { formatLongCurrency, formatLongNumber } from '@/lib/format';
import { useCallback, useContext, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { ReportContext } from '../[reportId]/Report';
import { RevenueTable } from './RevenueTable'; import { RevenueTable } from './RevenueTable';
import styles from './RevenueView.module.css'; import styles from './RevenueView.module.css';
@ -24,7 +23,7 @@ export function RevenueView({ isLoading }: RevenueViewProps) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { locale } = useLocale(); const { locale } = useLocale();
const { countryNames } = useCountryNames(locale); const { countryNames } = useCountryNames(locale);
const { report } = useContext(ReportContext); const { report } = useReport();
const { const {
data, data,
parameters: { dateRange, currency }, parameters: { dateRange, currency },

View file

@ -1,11 +1,9 @@
import { useContext } from 'react'; import { useMessages, useReport } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen'; import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
import { ReportContext } from '../[reportId]/Report';
import { BaseParameters } from '../[reportId]/BaseParameters'; import { BaseParameters } from '../[reportId]/BaseParameters';
export function UTMParameters() { export function UTMParameters() {
const { report, runReport, isRunning } = useContext(ReportContext); const { report, runReport, isRunning } = useReport();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { id, parameters } = report || {}; const { id, parameters } = report || {};

View file

@ -1,7 +1,6 @@
import { useContext } from 'react';
import { firstBy } from 'thenby'; import { firstBy } from 'thenby';
import { ReportContext } from '../[reportId]/Report';
import { CHART_COLORS, UTM_PARAMS } from '@/lib/constants'; import { CHART_COLORS, UTM_PARAMS } from '@/lib/constants';
import { useReport } from '@/components/hooks';
import { PieChart } from '@/components/charts/PieChart'; import { PieChart } from '@/components/charts/PieChart';
import { ListTable } from '@/components/metrics/ListTable'; import { ListTable } from '@/components/metrics/ListTable';
import styles from './UTMView.module.css'; import styles from './UTMView.module.css';
@ -17,7 +16,7 @@ function toArray(data: { [key: string]: number } = {}) {
export function UTMView() { export function UTMView() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { report } = useContext(ReportContext); const { report } = useReport();
const { data } = report || {}; const { data } = report || {};
if (!data) { if (!data) {

View file

@ -2,7 +2,7 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Grid, Column } from '@umami/react-zen'; import { Grid, Column } from '@umami/react-zen';
import { useLoginQuery, useMessages } from '@/components/hooks'; import { useLoginQuery, useMessages } from '@/components/hooks';
import { SideBar } from '@/components/layout/SideBar'; import { SideBar } from '@/components/common/SideBar';
export function SettingsLayout({ children }: { children: ReactNode }) { export function SettingsLayout({ children }: { children: ReactNode }) {
const { user } = useLoginQuery(); const { user } = useLoginQuery();

View file

@ -1,5 +1,5 @@
import { Row } from '@umami/react-zen'; import { Row } from '@umami/react-zen';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { ROLES } from '@/lib/constants'; import { ROLES } from '@/lib/constants';
import { useLoginQuery, useMessages } from '@/components/hooks'; import { useLoginQuery, useMessages } from '@/components/hooks';
import { TeamsJoinButton } from './TeamsJoinButton'; import { TeamsJoinButton } from './TeamsJoinButton';

View file

@ -1,4 +1,4 @@
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { UserAddButton } from './UserAddButton'; import { UserAddButton } from './UserAddButton';

View file

@ -2,7 +2,7 @@ import { useContext } from 'react';
import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen'; import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { UserEditForm } from './UserEditForm'; import { UserEditForm } from './UserEditForm';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { UserWebsites } from './UserWebsites'; import { UserWebsites } from './UserWebsites';
import { UserContext } from './UserProvider'; import { UserContext } from './UserProvider';

View file

@ -1,5 +1,5 @@
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { WebsiteAddButton } from './WebsiteAddButton'; import { WebsiteAddButton } from './WebsiteAddButton';
export interface WebsitesHeaderProps { export interface WebsitesHeaderProps {

View file

@ -10,7 +10,7 @@ import {
import { WebsiteDeleteForm } from './WebsiteDeleteForm'; import { WebsiteDeleteForm } from './WebsiteDeleteForm';
import { WebsiteResetForm } from './WebsiteResetForm'; import { WebsiteResetForm } from './WebsiteResetForm';
import { WebsiteTransferForm } from './WebsiteTransferForm'; import { WebsiteTransferForm } from './WebsiteTransferForm';
import { ActionForm } from '@/components/layout/ActionForm'; import { ActionForm } from '@/components/common/ActionForm';
import { ROLES } from '@/lib/constants'; import { ROLES } from '@/lib/constants';
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) { export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {

View file

@ -4,7 +4,7 @@ import Link from 'next/link';
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider'; import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { ShareUrl } from './ShareUrl'; import { ShareUrl } from './ShareUrl';
import { TrackingCode } from './TrackingCode'; import { TrackingCode } from './TrackingCode';
import { WebsiteData } from './WebsiteData'; import { WebsiteData } from './WebsiteData';

View file

@ -2,7 +2,7 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { Grid, Column } from '@umami/react-zen'; import { Grid, Column } from '@umami/react-zen';
import { SideBar } from '@/components/layout/SideBar'; import { SideBar } from '@/components/common/SideBar';
export function TeamSettingsLayout({ children }: { children: ReactNode }) { export function TeamSettingsLayout({ children }: { children: ReactNode }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();

View file

@ -1,7 +1,7 @@
'use client'; 'use client';
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
import { TeamMembersDataTable } from './TeamMembersDataTable'; import { TeamMembersDataTable } from './TeamMembersDataTable';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { useLoginQuery, useMessages } from '@/components/hooks'; import { useLoginQuery, useMessages } from '@/components/hooks';
import { ROLES } from '@/lib/constants'; import { ROLES } from '@/lib/constants';
import { useContext } from 'react'; import { useContext } from 'react';

View file

@ -1,14 +1,14 @@
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
import { useLoginQuery, useMessages } from '@/components/hooks'; import { useLoginQuery, useMessages } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { ROLES } from '@/lib/constants'; import { ROLES } from '@/lib/constants';
import { useContext, useState } from 'react'; import { useContext, useState } from 'react';
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen'; import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton'; import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
import { TeamManage } from './TeamManage'; import { TeamManage } from './TeamManage';
import { TeamEditForm } from './TeamEditForm'; import { TeamEditForm } from './TeamEditForm';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
export function TeamDetails({ teamId }: { teamId: string }) { export function TeamDetails({ teamId }: { teamId: string }) {
const team = useContext(TeamContext); const team = useContext(TeamContext);

View file

@ -1,7 +1,7 @@
import { useMessages, useModified } from '@/components/hooks'; import { useMessages, useModified } from '@/components/hooks';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Button, Modal, DialogTrigger, Dialog } from '@umami/react-zen'; import { Button, Modal, DialogTrigger, Dialog } from '@umami/react-zen';
import { ActionForm } from '@/components/layout/ActionForm'; import { ActionForm } from '@/components/common/ActionForm';
import { TeamDeleteForm } from './TeamDeleteForm'; import { TeamDeleteForm } from './TeamDeleteForm';
export function TeamManage({ teamId }: { teamId: string }) { export function TeamManage({ teamId }: { teamId: string }) {

View file

@ -2,7 +2,7 @@
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton'; import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
import { useLoginQuery, useMessages } from '@/components/hooks'; import { useLoginQuery, useMessages } from '@/components/hooks';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { TeamWebsitesDataTable } from './TeamWebsitesDataTable'; import { TeamWebsitesDataTable } from './TeamWebsitesDataTable';
import { ROLES } from '@/lib/constants'; import { ROLES } from '@/lib/constants';
import { useContext } from 'react'; import { useContext } from 'react';

View file

@ -1,9 +1,7 @@
'use client'; 'use client';
import { Column } from '@umami/react-zen'; import { Column } from '@umami/react-zen';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
import { FilterTags } from '@/components/metrics/FilterTags';
import { useNavigation } from '@/components/hooks'; import { useNavigation } from '@/components/hooks';
import { FILTER_COLUMNS } from '@/lib/constants';
import { WebsiteChart } from './WebsiteChart'; import { WebsiteChart } from './WebsiteChart';
import { WebsiteExpandedView } from './WebsiteExpandedView'; import { WebsiteExpandedView } from './WebsiteExpandedView';
import { WebsiteHeader } from './WebsiteHeader'; import { WebsiteHeader } from './WebsiteHeader';
@ -11,21 +9,16 @@ import { WebsiteMetricsBar } from './WebsiteMetricsBar';
import { WebsiteTableView } from './WebsiteTableView'; import { WebsiteTableView } from './WebsiteTableView';
export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) { export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
const { query } = useNavigation(); const {
const { view } = query; query: { view },
} = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
return ( return (
<Column gap="3"> <Column gap="3">
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<FilterTags websiteId={websiteId} params={params} /> <Panel>
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} /> <WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
</Panel>
<Panel> <Panel>
<WebsiteChart websiteId={websiteId} /> <WebsiteChart websiteId={websiteId} />
</Panel> </Panel>

View file

@ -1,7 +1,7 @@
import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen'; import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen';
import { LinkButton } from '@/components/common/LinkButton'; import { LinkButton } from '@/components/common/LinkButton';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { SideBar } from '@/components/layout/SideBar'; import { SideBar } from '@/components/common/SideBar';
import { BrowsersTable } from '@/components/metrics/BrowsersTable'; import { BrowsersTable } from '@/components/metrics/BrowsersTable';
import { CitiesTable } from '@/components/metrics/CitiesTable'; import { CitiesTable } from '@/components/metrics/CitiesTable';
import { CountriesTable } from '@/components/metrics/CountriesTable'; import { CountriesTable } from '@/components/metrics/CountriesTable';

View file

@ -1,31 +1,80 @@
import { ReactNode } from 'react'; import {
import { Row, Heading } from '@umami/react-zen'; Column,
Row,
Heading,
MenuTrigger,
Button,
Icon,
Icons,
Popover,
Menu,
MenuItem,
MenuSeparator,
} from '@umami/react-zen';
import { Favicon } from '@/components/common/Favicon'; import { Favicon } from '@/components/common/Favicon';
import { ActiveUsers } from '@/components/metrics/ActiveUsers'; import { ActiveUsers } from '@/components/metrics/ActiveUsers';
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs'; import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
import { useWebsite } from '@/components/hooks/useWebsite'; import { useWebsite } from '@/components/hooks/useWebsite';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
import { FilterTags } from '@/components/metrics/FilterTags';
import { useMessages } from '@/components/hooks';
export function WebsiteHeader({ export function WebsiteHeader({
websiteId, websiteId,
children, showFilter = true,
allowEdit = true,
compareMode = false,
}: { }: {
websiteId: string; websiteId: string;
children?: ReactNode; showFilter?: boolean;
allowEdit?: boolean;
compareMode?: boolean;
}) { }) {
const website = useWebsite(); const website = useWebsite();
const { formatMessage, labels } = useMessages();
const { name, domain } = website || {}; const { name, domain } = website || {};
const items = [
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
{ label: formatMessage(labels.previousYear), value: 'yoy' },
];
return ( return (
<> <Column marginY="6" gap="6">
<Row alignItems="center" gap="3" marginY="6"> <Row alignItems="center" justifyContent="space-between" gap="3">
<Favicon domain={domain} /> <Row alignItems="center" gap="3">
<Heading> <Favicon domain={domain} />
{name} <Heading>
<ActiveUsers websiteId={websiteId} /> {name}
</Heading> <ActiveUsers websiteId={websiteId} />
{children} </Heading>
</Row>
<Row alignItems="center" gap="3">
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} />
{allowEdit && (
<MenuTrigger>
<Button variant="quiet">
<Icon>
<Icons.More />
</Icon>
</Button>
<Popover placement="bottom end">
<Menu>
<MenuItem>Compare dates</MenuItem>
<MenuItem>Share</MenuItem>
<MenuSeparator />
<MenuItem>Settings</MenuItem>
</Menu>
</Popover>
</MenuTrigger>
)}
</Row>
</Row> </Row>
{compareMode && items.map(item => <div key={item.value}>{item.label}</div>)}
<FilterTags websiteId={websiteId} />
<WebsiteTabs websiteId={websiteId} /> <WebsiteTabs websiteId={websiteId} />
</> </Column>
); );
} }

View file

@ -1,24 +1,16 @@
import { Select, ListItem } from '@umami/react-zen'; import { useDateRange, useMessages } from '@/components/hooks';
import classNames from 'classnames';
import { useDateRange, useMessages, useSticky } from '@/components/hooks';
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar'; import { MetricsBar } from '@/components/metrics/MetricsBar';
import { formatShortTime, formatLongNumber } from '@/lib/format'; import { formatShortTime, formatLongNumber } from '@/lib/format';
import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery'; import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
import { useWebsites, setWebsiteDateCompare } from '@/store/websites'; import { useWebsites } from '@/store/websites';
import { WebsiteFilterButton } from './WebsiteFilterButton';
import styles from './WebsiteMetricsBar.module.css';
export function WebsiteMetricsBar({ export function WebsiteMetricsBar({
websiteId, websiteId,
sticky,
showChange = false, showChange = false,
compareMode = false, compareMode = false,
showFilter = false,
}: { }: {
websiteId: string; websiteId: string;
sticky?: boolean;
showChange?: boolean; showChange?: boolean;
compareMode?: boolean; compareMode?: boolean;
showFilter?: boolean; showFilter?: boolean;
@ -26,7 +18,6 @@ export function WebsiteMetricsBar({
const { dateRange } = useDateRange(websiteId); const { dateRange } = useDateRange(websiteId);
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare); const dateCompare = useWebsites(state => state[websiteId]?.dateCompare);
const { ref } = useSticky({ enabled: sticky });
const { data, isLoading, isFetched, error } = useWebsiteStatsQuery( const { data, isLoading, isFetched, error } = useWebsiteStatsQuery(
websiteId, websiteId,
compareMode && dateCompare, compareMode && dateCompare,
@ -76,51 +67,23 @@ export function WebsiteMetricsBar({
] ]
: []; : [];
const items = [
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
{ label: formatMessage(labels.previousYear), value: 'yoy' },
];
return ( return (
<div ref={ref} className={classNames(styles.container)}> <MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<div> {metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => {
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}> return (
{metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => { <MetricCard
return ( key={label}
<MetricCard value={value}
key={label} previousValue={prev}
value={value} label={label}
previousValue={prev} change={change}
label={label} formatValue={formatValue}
change={change} reverseColors={reverseColors}
formatValue={formatValue} showChange={!isAllTime && (compareMode || showChange)}
reverseColors={reverseColors} showPrevious={!isAllTime && compareMode}
showChange={!isAllTime && (compareMode || showChange)} />
showPrevious={!isAllTime && compareMode} );
/> })}
); </MetricsBar>
})}
</MetricsBar>
</div>
<div className={styles.actions}>
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} showAllTime={!compareMode} />
{compareMode && (
<div className={styles.vs}>
<b>VS</b>
<Select
className={styles.dropdown}
items={items}
value={dateCompare || 'prev'}
onChange={(value: any) => setWebsiteDateCompare(websiteId, value)}
>
{items.map(({ label, value }) => (
<ListItem key={value}>{label}</ListItem>
))}
</Select>
</div>
)}
</div>
</div>
); );
} }

View file

@ -1,5 +1,5 @@
import { Grid } from '@umami/react-zen'; import { Grid } from '@umami/react-zen';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
import { PagesTable } from '@/components/metrics/PagesTable'; import { PagesTable } from '@/components/metrics/PagesTable';
import { ReferrersTable } from '@/components/metrics/ReferrersTable'; import { ReferrersTable } from '@/components/metrics/ReferrersTable';
import { BrowsersTable } from '@/components/metrics/BrowsersTable'; import { BrowsersTable } from '@/components/metrics/BrowsersTable';

View file

@ -1,28 +1,17 @@
'use client'; 'use client';
import { Grid } from '@umami/react-zen'; import { Grid } from '@umami/react-zen';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
import { WebsiteHeader } from '../WebsiteHeader'; import { WebsiteHeader } from '../WebsiteHeader';
import { WebsiteMetricsBar } from '../WebsiteMetricsBar'; import { WebsiteMetricsBar } from '../WebsiteMetricsBar';
import { FilterTags } from '@/components/metrics/FilterTags'; import { FilterTags } from '@/components/metrics/FilterTags';
import { useNavigation } from '@/components/hooks';
import { FILTER_COLUMNS } from '@/lib/constants';
import { WebsiteChart } from '../WebsiteChart'; import { WebsiteChart } from '../WebsiteChart';
import { WebsiteCompareTables } from './WebsiteCompareTables'; import { WebsiteCompareTables } from './WebsiteCompareTables';
export function WebsiteComparePage({ websiteId }) { export function WebsiteComparePage({ websiteId }) {
const { query } = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
return ( return (
<Grid gap="3"> <Grid gap="3">
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<FilterTags websiteId={websiteId} params={params} /> <FilterTags websiteId={websiteId} />
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} /> <WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
<Panel> <Panel>
<WebsiteChart websiteId={websiteId} compareMode={true} /> <WebsiteChart websiteId={websiteId} compareMode={true} />

View file

@ -1,6 +1,6 @@
import { Grid, Heading, Column } from '@umami/react-zen'; import { Grid, Heading, Column } from '@umami/react-zen';
import { useDateRange, useMessages, useNavigation } from '@/components/hooks'; import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
import { SideBar } from '@/components/layout/SideBar'; import { SideBar } from '@/components/common/SideBar';
import { BrowsersTable } from '@/components/metrics/BrowsersTable'; import { BrowsersTable } from '@/components/metrics/BrowsersTable';
import { ChangeLabel } from '@/components/metrics/ChangeLabel'; import { ChangeLabel } from '@/components/metrics/ChangeLabel';
import { CitiesTable } from '@/components/metrics/CitiesTable'; import { CitiesTable } from '@/components/metrics/CitiesTable';

View file

@ -1,40 +1,35 @@
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery'; import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar'; import { MetricsBar } from '@/components/metrics/MetricsBar';
import { formatLongNumber } from '@/lib/format'; import { formatLongNumber } from '@/lib/format';
import { Flexbox } from '@umami/react-zen';
export function EventsMetricsBar({ websiteId }: { websiteId: string }) { export function EventsMetricsBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId); const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
return ( return (
<Flexbox direction="row" justifyContent="space-between" style={{ minHeight: 120 }}> <MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}> <MetricCard
<MetricCard value={data?.visitors?.value}
value={data?.visitors?.value} label={formatMessage(labels.visitors)}
label={formatMessage(labels.visitors)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.visits?.value}
value={data?.visits?.value} label={formatMessage(labels.visits)}
label={formatMessage(labels.visits)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.pageviews?.value}
value={data?.pageviews?.value} label={formatMessage(labels.views)}
label={formatMessage(labels.views)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.events?.value}
value={data?.events?.value} label={formatMessage(labels.events)}
label={formatMessage(labels.events)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> </MetricsBar>
</MetricsBar>
<WebsiteDateFilter websiteId={websiteId} />
</Flexbox>
); );
} }

View file

@ -1,12 +1,12 @@
'use client'; 'use client';
import { TabList, Tab, Tabs, TabPanel, Grid } from '@umami/react-zen'; import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { useState } from 'react'; import { useState } from 'react';
import { WebsiteHeader } from '../WebsiteHeader'; import { WebsiteHeader } from '../WebsiteHeader';
import { EventsDataTable } from './EventsDataTable'; import { EventsDataTable } from './EventsDataTable';
import { EventsMetricsBar } from './EventsMetricsBar'; import { EventsMetricsBar } from './EventsMetricsBar';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
import { EventsChart } from '@/components/metrics/EventsChart'; import { EventsChart } from '@/components/metrics/EventsChart';
import { GridRow } from '@/components/layout/GridRow'; import { GridRow } from '@/components/common/GridRow';
import { MetricsTable } from '@/components/metrics/MetricsTable'; import { MetricsTable } from '@/components/metrics/MetricsTable';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { EventProperties } from './EventProperties'; import { EventProperties } from './EventProperties';
@ -16,9 +16,11 @@ export function EventsPage({ websiteId }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<Grid gap="3"> <Column gap="3">
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<EventsMetricsBar websiteId={websiteId} /> <Panel>
<EventsMetricsBar websiteId={websiteId} />
</Panel>
<GridRow layout="two-one"> <GridRow layout="two-one">
<Panel> <Panel>
<EventsChart websiteId={websiteId} /> <EventsChart websiteId={websiteId} />
@ -46,6 +48,6 @@ export function EventsPage({ websiteId }) {
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Panel> </Panel>
</Grid> </Column>
); );
} }

View file

@ -1,36 +1,18 @@
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { RealtimeData } from '@/lib/types'; import { RealtimeData } from '@/lib/types';
import styles from './RealtimeHeader.module.css'; import { MetricsBar } from '@/components/metrics/MetricsBar';
export function RealtimeHeader({ data }: { data: RealtimeData }) { export function RealtimeHeader({ data }: { data: RealtimeData }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { totals }: any = data || {}; const { totals }: any = data || {};
return ( return (
<div className={styles.header}> <MetricsBar isFetched={true}>
<div className={styles.metrics}> <MetricCard label={formatMessage(labels.views)} value={totals.views} />
<MetricCard <MetricCard label={formatMessage(labels.visitors)} value={totals.visitors} />
className={styles.card} <MetricCard label={formatMessage(labels.events)} value={totals.events} />
label={formatMessage(labels.views)} <MetricCard label={formatMessage(labels.countries)} value={totals.countries} />
value={totals.views} </MetricsBar>
/>
<MetricCard
className={styles.card}
label={formatMessage(labels.visitors)}
value={totals.visitors}
/>
<MetricCard
className={styles.card}
label={formatMessage(labels.events)}
value={totals.events}
/>
<MetricCard
className={styles.card}
label={formatMessage(labels.countries)}
value={totals.countries}
/>
</div>
</div>
); );
} }

View file

@ -1,7 +1,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Page } from '@/components/layout/Page'; import { Page } from '@/components/common/Page';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { useApi, useMessages } from '@/components/hooks'; import { useApi, useMessages } from '@/components/hooks';
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';

View file

@ -1,9 +1,9 @@
'use client'; 'use client';
import { firstBy } from 'thenby'; import { firstBy } from 'thenby';
import { Grid } from '@umami/react-zen'; import { Grid } from '@umami/react-zen';
import { GridRow } from '@/components/layout/GridRow'; import { GridRow } from '@/components/common/GridRow';
import { Page } from '@/components/layout/Page'; import { Page } from '@/components/common/Page';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
import { RealtimeChart } from '@/components/metrics/RealtimeChart'; import { RealtimeChart } from '@/components/metrics/RealtimeChart';
import { WorldMap } from '@/components/metrics/WorldMap'; import { WorldMap } from '@/components/metrics/WorldMap';
import { useRealtimeQuery } from '@/components/hooks'; import { useRealtimeQuery } from '@/components/hooks';
@ -30,7 +30,9 @@ export function WebsiteRealtimePage({ websiteId }) {
return ( return (
<Grid gap="3"> <Grid gap="3">
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<RealtimeHeader data={data} /> <Panel>
<RealtimeHeader data={data} />
</Panel>
<Panel> <Panel>
<RealtimeChart data={data} unit="minute" /> <RealtimeChart data={data} unit="minute" />
</Panel> </Panel>

View file

@ -1,40 +1,35 @@
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery'; import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar'; import { MetricsBar } from '@/components/metrics/MetricsBar';
import { formatLongNumber } from '@/lib/format'; import { formatLongNumber } from '@/lib/format';
import { Flexbox } from '@umami/react-zen';
export function SessionsMetricsBar({ websiteId }: { websiteId: string }) { export function SessionsMetricsBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId); const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
return ( return (
<Flexbox direction="row" justifyContent="space-between" style={{ minHeight: 120 }}> <MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}> <MetricCard
<MetricCard value={data?.visitors?.value}
value={data?.visitors?.value} label={formatMessage(labels.visitors)}
label={formatMessage(labels.visitors)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.visits?.value}
value={data?.visits?.value} label={formatMessage(labels.visits)}
label={formatMessage(labels.visits)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.pageviews?.value}
value={data?.pageviews?.value} label={formatMessage(labels.views)}
label={formatMessage(labels.views)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> <MetricCard
<MetricCard value={data?.countries?.value}
value={data?.countries?.value} label={formatMessage(labels.countries)}
label={formatMessage(labels.countries)} formatValue={formatLongNumber}
formatValue={formatLongNumber} />
/> </MetricsBar>
</MetricsBar>
<WebsiteDateFilter websiteId={websiteId} />
</Flexbox>
); );
} }

View file

@ -1,24 +1,26 @@
'use client'; 'use client';
import { useState } from 'react';
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { WebsiteHeader } from '../WebsiteHeader'; import { WebsiteHeader } from '../WebsiteHeader';
import { SessionsDataTable } from './SessionsDataTable'; import { SessionsDataTable } from './SessionsDataTable';
import { SessionsMetricsBar } from './SessionsMetricsBar'; import { SessionsMetricsBar } from './SessionsMetricsBar';
import { SessionProperties } from './SessionProperties'; import { SessionProperties } from './SessionProperties';
import { WorldMap } from '@/components/metrics/WorldMap'; import { WorldMap } from '@/components/metrics/WorldMap';
import { GridRow } from '@/components/layout/GridRow'; import { GridRow } from '@/components/common/GridRow';
import { TabList, Tab, Tabs, TabPanel } from '@umami/react-zen';
import { useState } from 'react';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { SessionsWeekly } from './SessionsWeekly'; import { SessionsWeekly } from './SessionsWeekly';
import { Panel } from '@/components/layout/Panel'; import { Panel } from '@/components/common/Panel';
export function SessionsPage({ websiteId }) { export function SessionsPage({ websiteId }) {
const [tab, setTab] = useState('activity'); const [tab, setTab] = useState('activity');
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<> <Column gap="3">
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<SessionsMetricsBar websiteId={websiteId} /> <Panel>
<SessionsMetricsBar websiteId={websiteId} />
</Panel>
<GridRow layout="two-one"> <GridRow layout="two-one">
<Panel padding="0"> <Panel padding="0">
<WorldMap websiteId={websiteId} /> <WorldMap websiteId={websiteId} />
@ -41,6 +43,6 @@ export function SessionsPage({ websiteId }) {
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Panel> </Panel>
</> </Column>
); );
} }

View file

@ -1,7 +1,7 @@
'use client'; 'use client';
import { WebsiteDetailsPage } from '../../(main)/websites/[websiteId]/WebsiteDetailsPage'; import { WebsiteDetailsPage } from '../../(main)/websites/[websiteId]/WebsiteDetailsPage';
import { useShareTokenQuery } from '@/components/hooks'; import { useShareTokenQuery } from '@/components/hooks';
import { Page } from '@/components/layout/Page'; import { Page } from '@/components/common/Page';
import { Header } from './Header'; import { Header } from './Header';
import { Footer } from './Footer'; import { Footer } from './Footer';
import styles from './SharePage.module.css'; import styles from './SharePage.module.css';

View file

@ -1,7 +1,9 @@
import { Column } from '@umami/react-zen'; import { Column } from '@umami/react-zen';
export interface BoardProps {} export interface BoardProps {
children?: React.ReactNode;
export function Board(props: BoardProps) { }
return <Column>{}</Column>;
export function Board({ children }: BoardProps) {
return <Column>{children}</Column>;
} }

View file

@ -26,7 +26,6 @@ export function Chart({
onCreate, onCreate,
onUpdate, onUpdate,
onTooltip, onTooltip,
className,
chartOptions, chartOptions,
tooltip, tooltip,
...props ...props

View file

@ -2,7 +2,7 @@ import { Row, Column, Text } from '@umami/react-zen';
export function ActionForm({ label, description, children }) { export function ActionForm({ label, description, children }) {
return ( return (
<Row padding="6" borderSize="1" borderRadius="3" justifyContent="space-between" shadow="2"> <Row padding="6" border borderRadius="3" justifyContent="space-between" shadow="2">
<Column> <Column>
<Text weight="bold">{label}</Text> <Text weight="bold">{label}</Text>
<Text>{description}</Text> <Text>{description}</Text>

View file

@ -1,21 +1,25 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Heading, Icon, Row } from '@umami/react-zen'; import { Heading, Icon, Row, Text } from '@umami/react-zen';
export function PageHeader({ export function PageHeader({
title, title,
description,
icon, icon,
children, children,
}: { }: {
title?: ReactNode; title: string;
description?: string;
icon?: ReactNode; icon?: ReactNode;
allowEdit?: boolean;
className?: string; className?: string;
children?: ReactNode; children?: ReactNode;
}) { }) {
return ( return (
<Row justifyContent="space-between" alignItems="center"> <Row justifyContent="space-between" alignItems="center" marginY="6">
<Row gap="3"> <Row gap="3">
{icon && <Icon size="lg">{icon}</Icon>} {icon && <Icon size="lg">{icon}</Icon>}
{title && <Heading>{title}</Heading>} {title && <Heading size="2">{title}</Heading>}
{description && <Text color="muted">{description}</Text>}
</Row> </Row>
<Row justifyContent="flex-end">{children}</Row> <Row justifyContent="flex-end">{children}</Row>
</Row> </Row>

View file

@ -0,0 +1,6 @@
import { Box } from '@umami/react-zen';
import type { BoxProps } from '@umami/react-zen/Box';
export function Panel(props: BoxProps) {
return <Box padding="6" border borderRadius="3" backgroundColor="solid" shadow="4" {...props} />;
}

View file

@ -41,6 +41,7 @@ export * from './useMessages';
export * from './useModified'; export * from './useModified';
export * from './usePagedQuery'; export * from './usePagedQuery';
export * from './useRegionNames'; export * from './useRegionNames';
export * from './useReport';
export * from './useSticky'; export * from './useSticky';
export * from './useNavigation'; export * from './useNavigation';
export * from './useTheme'; export * from './useTheme';

View file

@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ReportContext } from '@/app/(main)/reports/[reportId]/Report';
export function useReport() {
return useContext(ReportContext);
}

View file

@ -8,6 +8,7 @@ const selector = (state: { theme: string }) => state.theme;
export function useTheme() { export function useTheme() {
const theme = useApp(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME; const theme = useApp(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME;
const { primary, text, line, fill } = THEME_COLORS[theme];
const primaryColor = colord(THEME_COLORS[theme].primary); const primaryColor = colord(THEME_COLORS[theme].primary);
const colors = useMemo(() => { const colors = useMemo(() => {
@ -16,8 +17,8 @@ export function useTheme() {
...THEME_COLORS[theme], ...THEME_COLORS[theme],
}, },
chart: { chart: {
text: THEME_COLORS[theme].gray700, text,
line: THEME_COLORS[theme].gray200, line,
views: { views: {
hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(), hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
backgroundColor: primaryColor.alpha(0.4).toRgbString(), backgroundColor: primaryColor.alpha(0.4).toRgbString(),
@ -32,10 +33,10 @@ export function useTheme() {
}, },
}, },
map: { map: {
baseColor: THEME_COLORS[theme].primary, baseColor: primary,
fillColor: THEME_COLORS[theme].gray100, fillColor: fill,
strokeColor: THEME_COLORS[theme].primary, strokeColor: primary,
hoverColor: THEME_COLORS[theme].primary, hoverColor: primary,
}, },
}; };
}, [theme]); }, [theme]);

View file

@ -103,7 +103,9 @@ export function DateFilter({
return ( return (
<> <>
{divider && <ListSeparator />} {divider && <ListSeparator />}
<ListItem id={value}>{label}</ListItem> <ListItem key={value} id={value}>
{label}
</ListItem>
</> </>
); );
})} })}

View file

@ -1,9 +1,8 @@
import { useDateRange } from '@/components/hooks'; import { useDateRange } from '@/components/hooks';
import { isAfter } from 'date-fns'; import { isAfter } from 'date-fns';
import { getOffsetDateRange } from '@/lib/date'; import { getOffsetDateRange } from '@/lib/date';
import { Button, Icon, Icons } from '@umami/react-zen'; import { Button, Icon, Icons, Row } from '@umami/react-zen';
import { DateFilter } from './DateFilter'; import { DateFilter } from './DateFilter';
import styles from './WebsiteDateFilter.module.css';
import { DateRange } from '@/lib/types'; import { DateRange } from '@/lib/types';
export function WebsiteDateFilter({ export function WebsiteDateFilter({
@ -27,9 +26,22 @@ export function WebsiteDateFilter({
}; };
return ( return (
<div className={styles.container}> <Row gap="3">
{value !== 'all' && !value.startsWith('range') && (
<Row gap="1">
<Button onPress={() => handleIncrement(-1)} variant="quiet">
<Icon size="xs" rotate={180}>
<Icons.Chevron />
</Icon>
</Button>
<Button onPress={() => handleIncrement(1)} variant="quiet" isDisabled={disableForward}>
<Icon size="xs">
<Icons.Chevron />
</Icon>
</Button>
</Row>
)}
<DateFilter <DateFilter
className={styles.dropdown}
value={value} value={value}
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
@ -37,20 +49,6 @@ export function WebsiteDateFilter({
onChange={handleChange} onChange={handleChange}
showAllTime={showAllTime} showAllTime={showAllTime}
/> />
{value !== 'all' && !value.startsWith('range') && ( </Row>
<div className={styles.buttons}>
<Button onPress={() => handleIncrement(-1)}>
<Icon size="sm" rotate={180}>
<Icons.Chevron />
</Icon>
</Button>
<Button onPress={() => handleIncrement(1)} isDisabled={disableForward}>
<Icon size="sm">
<Icons.Chevron />
</Icon>
</Button>
</div>
)}
</div>
); );
} }

View file

@ -1,15 +0,0 @@
import { Box } from '@umami/react-zen';
import type { BoxProps } from '@umami/react-zen/Box';
export function Panel(props: BoxProps) {
return (
<Box
padding="6"
borderSize="1"
borderRadius="3"
backgroundColor="solid"
shadow="4"
{...props}
/>
);
}

View file

@ -9,18 +9,12 @@ import {
useFilters, useFilters,
} from '@/components/hooks'; } from '@/components/hooks';
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm'; import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
import { OPERATOR_PREFIXES } from '@/lib/constants'; import { FILTER_COLUMNS, OPERATOR_PREFIXES } from '@/lib/constants';
import { isSearchOperator, parseParameterValue } from '@/lib/params'; import { isSearchOperator, parseParameterValue } from '@/lib/params';
import styles from './FilterTags.module.css'; import styles from './FilterTags.module.css';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton'; import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
export function FilterTags({ export function FilterTags({ websiteId }: { websiteId: string }) {
websiteId,
params,
}: {
websiteId: string;
params: { [key: string]: string };
}) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat(); const { formatValue } = useFormat();
const { dateRange } = useDateRange(websiteId); const { dateRange } = useDateRange(websiteId);
@ -32,6 +26,14 @@ export function FilterTags({
const { fields } = useFields(); const { fields } = useFields();
const { operatorLabels } = useFilters(); const { operatorLabels } = useFilters();
const { startDate, endDate } = dateRange; const { startDate, endDate } = dateRange;
const { query } = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
if (Object.keys(params).filter(key => params[key]).length === 0) { if (Object.keys(params).filter(key => params[key]).length === 0) {
return null; return null;
@ -60,13 +62,13 @@ export function FilterTags({
return ( return (
<Row <Row
gap="3" gap="3"
backgroundColor="1" backgroundColor="2"
alignItems="center" alignItems="center"
paddingX="3" paddingX="5"
paddingY="2" paddingY="3"
border
borderRadius="2" borderRadius="2"
borderSize="1" wrap="wrap"
marginBottom="6"
> >
<Text weight="bold">{formatMessage(labels.filters)}</Text> <Text weight="bold">{formatMessage(labels.filters)}</Text>
{Object.keys(params).map(key => { {Object.keys(params).map(key => {

View file

@ -1,37 +1,8 @@
.card { .card {
display: flex; border-right: 1px solid var(--border-color);
flex-direction: column; padding: 0 50px;
justify-content: center;
min-width: 150px;
}
.card.compare .change {
font-size: 16px;
margin: 10px 0;
}
.card:first-child {
padding-left: 0;
} }
.card:last-child { .card:last-child {
border: 0; border-right: 0;
}
.value {
font-size: 36px;
font-weight: 700;
white-space: nowrap;
color: var(--base900);
line-height: 1.5;
}
.value.prev {
color: var(--base800);
}
.label {
font-weight: 700;
white-space: nowrap;
color: var(--base800);
} }

View file

@ -1,4 +1,4 @@
import classNames from 'classnames'; import { Text, Column } from '@umami/react-zen';
import { useSpring } from '@react-spring/web'; import { useSpring } from '@react-spring/web';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import { AnimatedDiv } from '@/components/common/AnimatedDiv'; import { AnimatedDiv } from '@/components/common/AnimatedDiv';
@ -15,7 +15,6 @@ export interface MetricCardProps {
showLabel?: boolean; showLabel?: boolean;
showChange?: boolean; showChange?: boolean;
showPrevious?: boolean; showPrevious?: boolean;
className?: string;
} }
export const MetricCard = ({ export const MetricCard = ({
@ -27,7 +26,6 @@ export const MetricCard = ({
showLabel = true, showLabel = true,
showChange = false, showChange = false,
showPrevious = false, showPrevious = false,
className,
}: MetricCardProps) => { }: MetricCardProps) => {
const diff = value - change; const diff = value - change;
const pct = ((value - diff) / diff) * 100; const pct = ((value - diff) / diff) * 100;
@ -36,26 +34,19 @@ export const MetricCard = ({
const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } }); const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } });
return ( return (
<div className={classNames(styles.card, className, showPrevious && styles.compare)}> <Column className={styles.card} justifyContent="center">
{showLabel && <div className={styles.label}>{label}</div>} {showLabel && <Text weight="bold">{label}</Text>}
<AnimatedDiv className={styles.value} title={value?.toString()}> <Text size="8" weight="bold" wrap="nowrap">
{props?.x?.to(x => formatValue(x))} <AnimatedDiv title={value?.toString()}>{props?.x?.to(x => formatValue(x))}</AnimatedDiv>
</AnimatedDiv> </Text>
{showChange && ( {showChange && (
<ChangeLabel <ChangeLabel value={change} title={formatValue(change)} reverseColors={reverseColors}>
className={styles.change}
value={change}
title={formatValue(change)}
reverseColors={reverseColors}
>
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv> <AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
</ChangeLabel> </ChangeLabel>
)} )}
{showPrevious && ( {showPrevious && (
<AnimatedDiv className={classNames(styles.value, styles.prev)} title={diff.toString()}> <AnimatedDiv title={diff.toString()}>{prevProps?.x?.to(x => formatValue(x))}</AnimatedDiv>
{prevProps?.x?.to(x => formatValue(x))}
</AnimatedDiv>
)} )}
</div> </Column>
); );
}; };

View file

@ -1,13 +0,0 @@
.bar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
gap: 20px;
width: 100%;
position: relative;
}
@media screen and (max-width: 768px) {
.bar {
grid-template-columns: 1fr 1fr;
}
}

View file

@ -1,8 +1,6 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Loading, Row } from '@umami/react-zen'; import { Grid, Loading } from '@umami/react-zen';
import { cloneChildren } from '@/lib/react';
import { ErrorMessage } from '@/components/common/ErrorMessage'; import { ErrorMessage } from '@/components/common/ErrorMessage';
import { formatLongNumber } from '@/lib/format';
export interface MetricsBarProps { export interface MetricsBarProps {
isLoading?: boolean; isLoading?: boolean;
@ -12,18 +10,15 @@ export interface MetricsBarProps {
} }
export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) { export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) {
const formatFunc = n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`);
return ( return (
<Row> <>
{isLoading && !isFetched && <Loading icon="dots" />} {isLoading && !isFetched && <Loading icon="dots" />}
{error && <ErrorMessage />} {error && <ErrorMessage />}
{!isLoading && {!isLoading && !error && isFetched && (
!error && <Grid columns="repeat(auto-fill, minmax(200px, 1fr))" width="100%" gapY="3">
isFetched && {children}
cloneChildren(children, child => { </Grid>
return { format: child?.props['format'] || formatFunc }; )}
})} </>
</Row>
); );
} }

View file

@ -52,7 +52,6 @@ export * from '@/components/common/Favicon';
export * from '@/components/common/FilterButtons'; export * from '@/components/common/FilterButtons';
export * from '@/components/common/FilterLink'; export * from '@/components/common/FilterLink';
export * from '@/components/common/HamburgerButton'; export * from '@/components/common/HamburgerButton';
export * from '@/components/common/HoverTooltip';
export * from '@/components/common/LinkButton'; export * from '@/components/common/LinkButton';
export * from '@/components/common/MobileMenu'; export * from '@/components/common/MobileMenu';
export * from '@/components/common/Pager'; export * from '@/components/common/Pager';

View file

@ -195,31 +195,15 @@ export const ROLE_PERMISSIONS = {
export const THEME_COLORS = { export const THEME_COLORS = {
light: { light: {
primary: '#2680eb', primary: '#2680eb',
gray50: '#ffffff', text: '#838383',
gray75: '#fafafa', line: '#d9d9d9',
gray100: '#f5f5f5', fill: '#f9f9f9',
gray200: '#eaeaea',
gray300: '#e1e1e1',
gray400: '#cacaca',
gray500: '#b3b3b3',
gray600: '#8e8e8e',
gray700: '#6e6e6e',
gray800: '#4b4b4b',
gray900: '#2c2c2c',
}, },
dark: { dark: {
primary: '#2680eb', primary: '#2680eb',
gray50: '#252525', text: '#7b7b7b',
gray75: '#2f2f2f', line: '#3a3a3a',
gray100: '#323232', fill: '#191919',
gray200: '#3e3e3e',
gray300: '#4a4a4a',
gray400: '#5a5a5a',
gray500: '#6e6e6e',
gray600: '#909090',
gray700: '#b9b9b9',
gray800: '#e3e3e3',
gray900: '#ffffff',
}, },
}; };

View file

@ -108,7 +108,7 @@ export function isValidTimezone(timezone: string) {
try { try {
Intl.DateTimeFormat(undefined, { timeZone: timezone }); Intl.DateTimeFormat(undefined, { timeZone: timezone });
return true; return true;
} catch (error) { } catch {
return false; return false;
} }
} }

View file

@ -90,7 +90,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US'
style: 'currency', style: 'currency',
currency: currency, currency: currency,
}); });
} catch (error) { } catch {
// Fallback to default currency format if an error occurs // Fallback to default currency format if an error occurs
formattedValue = new Intl.NumberFormat(locale, { formattedValue = new Intl.NumberFormat(locale, {
style: 'currency', style: 'currency',

View file

@ -22,7 +22,7 @@ export function safeDecodeURI(s: string | undefined | null): string | undefined
try { try {
return decodeURI(s); return decodeURI(s);
} catch (e) { } catch {
return s; return s;
} }
} }
@ -34,7 +34,7 @@ export function safeDecodeURIComponent(s: string | undefined | null): string | u
try { try {
return decodeURIComponent(s); return decodeURIComponent(s);
} catch (e) { } catch {
return s; return s;
} }
} }

View file

@ -217,6 +217,7 @@
disabled = !!data.disabled; disabled = !!data.disabled;
cache = data.cache; cache = data.cache;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) { } catch (e) {
/* empty */ /* empty */
} }