From 9cb604684448259b3d97162ad73711000f6510d1 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 20 Oct 2023 19:58:24 -0700 Subject: [PATCH 01/62] Upgraded prisma and next. --- package.json | 6 +- yarn.lock | 165 +++++++++++++++++++++++---------------------------- 2 files changed, 78 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 95dba5f3..08038ed7 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "dependencies": { "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", - "@prisma/client": "5.3.1", + "@prisma/client": "5.4.2", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.3.0", @@ -92,11 +92,11 @@ "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", - "next": "13.5.3", + "next": "13.5.6", "next-basics": "^0.37.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", - "prisma": "5.3.1", + "prisma": "5.4.2", "react": "^18.2.0", "react-basics": "^0.105.0", "react-beautiful-dnd": "^13.1.0", diff --git a/yarn.lock b/yarn.lock index d3321146..6dd8b93b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1771,10 +1771,10 @@ "@netlify/node-cookies" "^0.1.0" urlpattern-polyfill "8.0.2" -"@next/env@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.3.tgz#402da9a0af87f93d853519f0c2a602b1ab637c2c" - integrity sha512-X4te86vsbjsB7iO4usY9jLPtZ827Mbx+WcwNBGUOIuswuTAKQtzsuoxc/6KLxCMvogKG795MhrR1LDhYgDvasg== +"@next/env@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" + integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== "@next/eslint-plugin-next@12.3.4": version "12.3.4" @@ -1783,50 +1783,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.3.tgz#f72eac8c7b71d33e0768bd3c8baf68b00fea0160" - integrity sha512-6hiYNJxJmyYvvKGrVThzo4nTcqvqUTA/JvKim7Auaj33NexDqSNwN5YrrQu+QhZJCIpv2tULSHt+lf+rUflLSw== +"@next/swc-darwin-arm64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz#b15d139d8971360fca29be3bdd703c108c9a45fb" + integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== -"@next/swc-darwin-x64@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.3.tgz#96eda3a1247a713579eb241d76d3f503291c8938" - integrity sha512-UpBKxu2ob9scbpJyEq/xPgpdrgBgN3aLYlxyGqlYX5/KnwpJpFuIHU2lx8upQQ7L+MEmz+fA1XSgesoK92ppwQ== +"@next/swc-darwin-x64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" + integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== -"@next/swc-linux-arm64-gnu@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.3.tgz#132e155a029310fffcdfd3e3c4255f7ce9fd2714" - integrity sha512-5AzM7Yx1Ky+oLY6pHs7tjONTF22JirDPd5Jw/3/NazJ73uGB05NqhGhB4SbeCchg7SlVYVBeRMrMSZwJwq/xoA== +"@next/swc-linux-arm64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" + integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== -"@next/swc-linux-arm64-musl@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.3.tgz#981d7d8fdcf040bd0c89588ef4139c28805f5cf1" - integrity sha512-A/C1shbyUhj7wRtokmn73eBksjTM7fFQoY2v/0rTM5wehpkjQRLOXI8WJsag2uLhnZ4ii5OzR1rFPwoD9cvOgA== +"@next/swc-linux-arm64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" + integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== -"@next/swc-linux-x64-gnu@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.3.tgz#b8263663acda7b84bc2c4ffa39ca4b0172a78060" - integrity sha512-FubPuw/Boz8tKkk+5eOuDHOpk36F80rbgxlx4+xty/U71e3wZZxVYHfZXmf0IRToBn1Crb8WvLM9OYj/Ur815g== +"@next/swc-linux-x64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" + integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== -"@next/swc-linux-x64-musl@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.3.tgz#cd0bed8ee92032c25090bed9d95602ac698d925f" - integrity sha512-DPw8nFuM1uEpbX47tM3wiXIR0Qa+atSzs9Q3peY1urkhofx44o7E1svnq+a5Q0r8lAcssLrwiM+OyJJgV/oj7g== +"@next/swc-linux-x64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" + integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== -"@next/swc-win32-arm64-msvc@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.3.tgz#7f556674ca97e6936220d10c58252cc36522d80a" - integrity sha512-zBPSP8cHL51Gub/YV8UUePW7AVGukp2D8JU93IHbVDu2qmhFAn9LWXiOOLKplZQKxnIPUkJTQAJDCWBWU4UWUA== +"@next/swc-win32-arm64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" + integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== -"@next/swc-win32-ia32-msvc@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.3.tgz#4912721fb8695f11daec4cde42e73dc57bcc479f" - integrity sha512-ONcL/lYyGUj4W37D4I2I450SZtSenmFAvapkJQNIJhrPMhzDU/AdfLkW98NvH1D2+7FXwe7yclf3+B7v28uzBQ== +"@next/swc-win32-ia32-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" + integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== -"@next/swc-win32-x64-msvc@13.5.3": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.3.tgz#97340a709febb60ff73003566b99d127d4e5b881" - integrity sha512-2Vz2tYWaLqJvLcWbbTlJ5k9AN6JD7a5CN2pAeIzpbecK8ZF/yobA39cXtv6e+Z8c5UJuVOmaTldEAIxvsIux/Q== +"@next/swc-win32-x64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" + integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1941,22 +1941,22 @@ "@parcel/watcher-win32-ia32" "2.3.0" "@parcel/watcher-win32-x64" "2.3.0" -"@prisma/client@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.3.1.tgz#fc7fc2d91e814cc4fe18a4bc5e78bf851c26985e" - integrity sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q== +"@prisma/client@5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.4.2.tgz#786f9c1d8f06d955933004ac638d14da4bf14025" + integrity sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA== dependencies: - "@prisma/engines-version" "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" + "@prisma/engines-version" "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" -"@prisma/engines-version@5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59": - version "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz#7eb6f5c6b7628b8b39df55c903f411528a6f761c" - integrity sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w== +"@prisma/engines-version@5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574": + version "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz#ff14f2926890edee47e8f1d08df7b4f392ee34bf" + integrity sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA== -"@prisma/engines@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.3.1.tgz#53cc72a5ed176dc27d22305fe5569c64cc78b381" - integrity sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA== +"@prisma/engines@5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f" + integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g== "@react-spring/animated@~9.7.3": version "9.7.3" @@ -6274,7 +6274,7 @@ nanoclone@^0.2.1: resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== -nanoid@^3.3.4, nanoid@^3.3.6: +nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== @@ -6303,29 +6303,28 @@ next-basics@^0.37.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@13.5.3: - version "13.5.3" - resolved "https://registry.yarnpkg.com/next/-/next-13.5.3.tgz#631efcbcc9d756c610855d9b94f3d8c4e73ee131" - integrity sha512-4Nt4HRLYDW/yRpJ/QR2t1v63UOMS55A38dnWv3UDOWGezuY0ZyFO1ABNbD7mulVzs9qVhgy2+ppjdsANpKP1mg== +next@13.5.6: + version "13.5.6" + resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" + integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== dependencies: - "@next/env" "13.5.3" + "@next/env" "13.5.6" "@swc/helpers" "0.5.2" busboy "1.6.0" caniuse-lite "^1.0.30001406" - postcss "8.4.14" + postcss "8.4.31" styled-jsx "5.1.1" watchpack "2.4.0" - zod "3.21.4" optionalDependencies: - "@next/swc-darwin-arm64" "13.5.3" - "@next/swc-darwin-x64" "13.5.3" - "@next/swc-linux-arm64-gnu" "13.5.3" - "@next/swc-linux-arm64-musl" "13.5.3" - "@next/swc-linux-x64-gnu" "13.5.3" - "@next/swc-linux-x64-musl" "13.5.3" - "@next/swc-win32-arm64-msvc" "13.5.3" - "@next/swc-win32-ia32-msvc" "13.5.3" - "@next/swc-win32-x64-msvc" "13.5.3" + "@next/swc-darwin-arm64" "13.5.6" + "@next/swc-darwin-x64" "13.5.6" + "@next/swc-linux-arm64-gnu" "13.5.6" + "@next/swc-linux-arm64-musl" "13.5.6" + "@next/swc-linux-x64-gnu" "13.5.6" + "@next/swc-linux-x64-musl" "13.5.6" + "@next/swc-win32-arm64-msvc" "13.5.6" + "@next/swc-win32-ia32-msvc" "13.5.6" + "@next/swc-win32-x64-msvc" "13.5.6" nice-try@^1.0.4: version "1.0.5" @@ -7308,16 +7307,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.1.10, postcss@^8.4.21, postcss@^8.4.28, postcss@^8.4.31: +postcss@8.4.31, postcss@^8.1.10, postcss@^8.4.21, postcss@^8.4.28, postcss@^8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -7366,12 +7356,12 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -prisma@5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.3.1.tgz#a0932c1c1a5ed4ff449d064b193d9c7e94e8bf77" - integrity sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A== +prisma@5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.4.2.tgz#7eac9276439ec7073ec697c6c0dfa259d96e955e" + integrity sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg== dependencies: - "@prisma/engines" "5.3.1" + "@prisma/engines" "5.4.2" promise.series@^0.2.0: version "0.2.0" @@ -9268,11 +9258,6 @@ yup@^0.32.11: property-expr "^2.0.4" toposort "^2.0.2" -zod@3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== - zustand@^4.3.8: version "4.4.3" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.3.tgz#1d54cf7fa4507ad8bf58e2f13e08ddc8a6730128" From 02c3d2bab3ed58d6b7cdbcb585af51394f7e9ae7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 30 Oct 2023 17:18:48 -0500 Subject: [PATCH 02/62] Moved files. New components build. --- src/app/(main)/NavBar.js | 37 +++++++++++++++++- src/app/(main)/Shell.tsx | 3 +- .../common => app/(main)}/UpdateNotice.js | 0 .../(main)}/UpdateNotice.module.css | 0 src/components/common/HamburgerButton.js | 39 +------------------ .../{common => metrics}/WorldMap.js | 0 .../{common => metrics}/WorldMap.module.css | 0 src/index.ts | 23 ++++++++--- 8 files changed, 56 insertions(+), 46 deletions(-) rename src/{components/common => app/(main)}/UpdateNotice.js (100%) rename src/{components/common => app/(main)}/UpdateNotice.module.css (100%) rename src/components/{common => metrics}/WorldMap.js (100%) rename src/components/{common => metrics}/WorldMap.module.css (100%) diff --git a/src/app/(main)/NavBar.js b/src/app/(main)/NavBar.js index 211adf5f..e241a059 100644 --- a/src/app/(main)/NavBar.js +++ b/src/app/(main)/NavBar.js @@ -14,6 +14,7 @@ import styles from './NavBar.module.css'; export function NavBar() { const pathname = usePathname(); const { formatMessage, labels } = useMessages(); + const cloudMode = Boolean(process.env.cloudMode); const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard' }, @@ -22,6 +23,40 @@ export function NavBar() { { label: formatMessage(labels.settings), url: '/settings' }, ].filter(n => n); + const menuItems = [ + { + label: formatMessage(labels.dashboard), + url: '/dashboard', + }, + !cloudMode && { + label: formatMessage(labels.settings), + url: '/settings', + children: [ + { + label: formatMessage(labels.websites), + url: '/settings/websites', + }, + { + label: formatMessage(labels.teams), + url: '/settings/teams', + }, + { + label: formatMessage(labels.users), + url: '/settings/users', + }, + { + label: formatMessage(labels.profile), + url: '/settings/profile', + }, + ], + }, + cloudMode && { + label: formatMessage(labels.profile), + url: '/settings/profile', + }, + !cloudMode && { label: formatMessage(labels.logout), url: '/logout' }, + ].filter(n => n); + return (
@@ -49,7 +84,7 @@ export function NavBar() {
- +
); diff --git a/src/app/(main)/Shell.tsx b/src/app/(main)/Shell.tsx index 980abb62..69ceff1f 100644 --- a/src/app/(main)/Shell.tsx +++ b/src/app/(main)/Shell.tsx @@ -1,9 +1,10 @@ 'use client'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; -import UpdateNotice from 'components/common/UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; +import UpdateNotice from './UpdateNotice'; + export function Shell({ children }) { const { user } = useRequireLogin(); const config = useConfig(); diff --git a/src/components/common/UpdateNotice.js b/src/app/(main)/UpdateNotice.js similarity index 100% rename from src/components/common/UpdateNotice.js rename to src/app/(main)/UpdateNotice.js diff --git a/src/components/common/UpdateNotice.module.css b/src/app/(main)/UpdateNotice.module.css similarity index 100% rename from src/components/common/UpdateNotice.module.css rename to src/app/(main)/UpdateNotice.module.css diff --git a/src/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.js index f97006ef..0eddad0f 100644 --- a/src/components/common/HamburgerButton.js +++ b/src/components/common/HamburgerButton.js @@ -2,46 +2,9 @@ import { Button, Icon } from 'react-basics'; import { useState } from 'react'; import MobileMenu from './MobileMenu'; import Icons from 'components/icons'; -import useMessages from 'components/hooks/useMessages'; -export function HamburgerButton() { - const { formatMessage, labels } = useMessages(); +export function HamburgerButton({ menuItems }) { const [active, setActive] = useState(false); - const cloudMode = Boolean(process.env.cloudMode); - - const menuItems = [ - { - label: formatMessage(labels.dashboard), - url: '/dashboard', - }, - !cloudMode && { - label: formatMessage(labels.settings), - url: '/settings', - children: [ - { - label: formatMessage(labels.websites), - url: '/settings/websites', - }, - { - label: formatMessage(labels.teams), - url: '/settings/teams', - }, - { - label: formatMessage(labels.users), - url: '/settings/users', - }, - { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - ], - }, - cloudMode && { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - !cloudMode && { label: formatMessage(labels.logout), url: '/logout' }, - ].filter(n => n); const handleClick = () => setActive(state => !state); const handleClose = () => setActive(false); diff --git a/src/components/common/WorldMap.js b/src/components/metrics/WorldMap.js similarity index 100% rename from src/components/common/WorldMap.js rename to src/components/metrics/WorldMap.js diff --git a/src/components/common/WorldMap.module.css b/src/components/metrics/WorldMap.module.css similarity index 100% rename from src/components/common/WorldMap.module.css rename to src/components/metrics/WorldMap.module.css diff --git a/src/index.ts b/src/index.ts index a6bb4c6c..01ceb7d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,15 @@ export * from 'components/hooks/useApi'; export * from 'components/hooks/useConfig'; -export * from 'components/hooks/useCountryNames'; export * from 'components/hooks/useDateRange'; export * from 'components/hooks/useDocumentClick'; export * from 'components/hooks/useEscapeKey'; +export * from 'components/hooks/useFilterQuery'; export * from 'components/hooks/useFilters'; export * from 'components/hooks/useForceUpdate'; export * from 'components/hooks/useFormat'; -export * from 'components/hooks/useLanguageNames'; export * from 'components/hooks/useLocale'; export * from 'components/hooks/useMessages'; export * from 'components/hooks/useNavigation'; -export * from 'components/hooks/useReport'; -export * from 'components/hooks/useReports'; export * from 'components/hooks/useRequireLogin'; export * from 'components/hooks/useShareToken'; export * from 'components/hooks/useSticky'; @@ -21,7 +18,7 @@ export * from 'components/hooks/useTimezone'; export * from 'components/hooks/useUser'; export * from 'components/hooks/useWebsite'; -export * from './app/(main)/settings/teams/[id]/TeamWebsiteAddForm'; +export * from 'app/(main)/settings/teams/[id]/TeamWebsiteAddForm'; export * from 'app/(main)/settings/teams/[id]/TeamEditForm'; export * from 'app/(main)/settings/teams/[id]/TeamMemberRemoveButton'; export * from 'app/(main)/settings/teams/[id]/TeamMembers'; @@ -44,8 +41,22 @@ export * from 'app/(main)/settings/websites/[id]/TrackingCode'; export * from 'app/(main)/settings/websites/[id]/WebsiteDeleteForm'; export * from 'app/(main)/settings/websites/[id]/WebsiteEditForm'; export * from 'app/(main)/settings/websites/[id]/WebsiteResetForm'; + export * from 'app/(main)/settings/websites/WebsiteAddForm'; export * from 'app/(main)/settings/websites/WebsitesHeader'; export * from 'app/(main)/settings/websites/WebsiteSettings'; -export * from './app/(main)/settings/websites/WebsitesDataTable'; +export * from 'app/(main)/settings/websites/WebsitesDataTable'; export * from 'app/(main)/settings/websites/WebsitesTable'; + +export * from 'components/common/ConfirmDeleteForm'; +export * from 'components/common/DataTable'; +export * from 'components/common/Empty'; +export * from 'components/common/ErrorBoundary'; +export * from 'components/common/Favicon'; +export * from 'components/common/FilterButtons'; +export * from 'components/common/FilterLink'; +export * from 'components/common/HamburgerButton'; +export * from 'components/common/HoverTooltip'; +export * from 'components/common/LinkButton'; +export * from 'components/common/MobileMenu'; +export * from 'components/common/Pager'; From 423dfbb5469934b90c9ce33d9a14d476f1571d9b Mon Sep 17 00:00:00 2001 From: Gouttfi Date: Tue, 31 Oct 2023 18:54:49 +0100 Subject: [PATCH 03/62] Solve issue : https://github.com/umami-software/umami/issues/2358 --- src/lib/date.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/date.ts b/src/lib/date.ts index a6c2b17b..51057309 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -194,7 +194,7 @@ export function incrementDateRange(value, increment) { const { num, unit } = selectedUnit; - const sub = num * increment; + const sub = Math.abs(num) * increment; switch (unit) { case 'hour': From 04309034836028802b5bd7be4e7535b8804f6f6e Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Thu, 2 Nov 2023 14:47:28 +0000 Subject: [PATCH 04/62] localize filter tags --- src/components/metrics/FilterTags.js | 4 +++- src/components/metrics/FilterTags.module.css | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/metrics/FilterTags.js b/src/components/metrics/FilterTags.js index 554c223a..db8fdcbd 100644 --- a/src/components/metrics/FilterTags.js +++ b/src/components/metrics/FilterTags.js @@ -2,10 +2,12 @@ import { safeDecodeURI } from 'next-basics'; import { Button, Icon, Icons, Text } from 'react-basics'; import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; +import useFormat from 'components/hooks/useFormat'; import styles from './FilterTags.module.css'; export function FilterTags({ params }) { const { formatMessage, labels } = useMessages(); + const { formatValue } = useFormat(); const { router, makeUrl, @@ -34,7 +36,7 @@ export function FilterTags({ params }) { return (
handleCloseFilter(key)}> - {`${key}`} = {`${safeDecodeURI(params[key])}`} + {formatMessage(labels[key])} = {formatValue(safeDecodeURI(params[key]), key)} diff --git a/src/components/metrics/FilterTags.module.css b/src/components/metrics/FilterTags.module.css index c228dc4e..32bc2f6f 100644 --- a/src/components/metrics/FilterTags.module.css +++ b/src/components/metrics/FilterTags.module.css @@ -24,3 +24,7 @@ .tag:hover { background: var(--blue200); } + +.tag b { + text-transform: lowercase; +} From 76a5ac8e46449aaaf2f70ac31b4fae453964e0ac Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Thu, 2 Nov 2023 17:34:06 +0000 Subject: [PATCH 05/62] match cloud stats in self hosted --- src/lib/prisma.ts | 16 ++++++++-------- src/queries/analytics/getWebsiteStats.ts | 14 +++++++++----- src/queries/analytics/reports/getFunnel.ts | 6 +++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 4b910f00..2abf230c 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -23,15 +23,15 @@ const POSTGRESQL_DATE_FORMATS = { year: 'YYYY-01-01', }; -function getAddMinutesQuery(field: string, minutes: number): string { +function getAddIntervalQuery(field: string, interval: string): string { const db = getDatabaseType(process.env.DATABASE_URL); if (db === POSTGRESQL) { - return `${field} + interval '${minutes} minute'`; + return `${field} + interval '${interval}'`; } if (db === MYSQL) { - return `DATE_ADD(${field}, interval ${minutes} minute)`; + return `DATE_ADD(${field}, interval ${interval})`; } } @@ -80,15 +80,15 @@ function getDateQuery(field: string, unit: string, timezone?: string): string { } } -function getTimestampIntervalQuery(field: string): string { +function getTimestampDiffQuery(field1: string, field2: string): string { const db = getDatabaseType(); if (db === POSTGRESQL) { - return `floor(extract(epoch from max(${field}) - min(${field})))`; + return `floor(extract(epoch from (${field2} - ${field1})))`; } if (db === MYSQL) { - return `floor(unix_timestamp(max(${field})) - unix_timestamp(min(${field})))`; + return `timestampdiff(second, ${field1}, ${field2})`; } } @@ -216,11 +216,11 @@ function getSearchMode(): { mode?: Prisma.QueryMode } { export default { ...prisma, - getAddMinutesQuery, + getAddIntervalQuery, getDayDiffQuery, getCastColumnQuery, getDateQuery, - getTimestampIntervalQuery, + getTimestampDiffQuery, getFilterQuery, parseFilters, getPageFilters, diff --git a/src/queries/analytics/getWebsiteStats.ts b/src/queries/analytics/getWebsiteStats.ts index 654a09a9..4dbdb462 100644 --- a/src/queries/analytics/getWebsiteStats.ts +++ b/src/queries/analytics/getWebsiteStats.ts @@ -12,7 +12,8 @@ export async function getWebsiteStats(...args: [websiteId: string, filters: Quer } async function relationalQuery(websiteId: string, filters: QueryFilters) { - const { getDateQuery, getTimestampIntervalQuery, parseFilters, rawQuery } = prisma; + const { getDateQuery, getAddIntervalQuery, getTimestampDiffQuery, parseFilters, rawQuery } = + prisma; const { filterQuery, joinSession, params } = await parseFilters(websiteId, { ...filters, eventType: EVENT_TYPE.pageView, @@ -24,13 +25,16 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { sum(t.c) as "pageviews", count(distinct t.session_id) as "uniques", sum(case when t.c = 1 then 1 else 0 end) as "bounces", - sum(t.time) as "totaltime" + sum(case when t.max_time < ${getAddIntervalQuery('t.min_time', '1 hour')} + then ${getTimestampDiffQuery('t.min_time', 't.max_time')} + else 0 end) as "totaltime" from ( select website_event.session_id, - ${getDateQuery('website_event.created_at', 'hour')}, - count(*) as c, - ${getTimestampIntervalQuery('website_event.created_at')} as "time" + ${getDateQuery('website_event.created_at', 'day')}, + count(*) as "c", + min(website_event.created_at) as "min_time", + max(website_event.created_at) as "max_time" from website_event join website on website_event.website_id = website.website_id diff --git a/src/queries/analytics/reports/getFunnel.ts b/src/queries/analytics/reports/getFunnel.ts index 8dbd8d45..4387cf09 100644 --- a/src/queries/analytics/reports/getFunnel.ts +++ b/src/queries/analytics/reports/getFunnel.ts @@ -35,7 +35,7 @@ async function relationalQuery( }[] > { const { windowMinutes, startDate, endDate, urls } = criteria; - const { rawQuery, getAddMinutesQuery } = prisma; + const { rawQuery, getAddIntervalQuery } = prisma; const { levelQuery, sumQuery } = getFunnelQuery(urls, windowMinutes); function getFunnelQuery( @@ -58,9 +58,9 @@ async function relationalQuery( join website_event we on l.session_id = we.session_id where we.website_id = {{websiteId::uuid}} - and we.created_at between l.created_at and ${getAddMinutesQuery( + and we.created_at between l.created_at and ${getAddIntervalQuery( `l.created_at `, - windowMinutes, + `${windowMinutes} minute`, )} and we.referrer_path = {{${i - 1}}} and we.url_path = {{${i}}} From d5eddeae2476c376c8e393542c6ceb929db584fc Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Fri, 3 Nov 2023 11:53:23 +0000 Subject: [PATCH 06/62] sort formatted filter values --- .../(main)/reports/[id]/FieldFilterForm.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/reports/[id]/FieldFilterForm.js b/src/app/(main)/reports/[id]/FieldFilterForm.js index 96ac06b0..dd889fc8 100644 --- a/src/app/(main)/reports/[id]/FieldFilterForm.js +++ b/src/app/(main)/reports/[id]/FieldFilterForm.js @@ -1,6 +1,6 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics'; -import { useMessages, useFilters, useFormat } from 'components/hooks'; +import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks'; import styles from './FieldFilterForm.module.css'; export default function FieldFilterForm({ @@ -16,14 +16,26 @@ export default function FieldFilterForm({ const [value, setValue] = useState(); const { getFilters } = useFilters(); const { formatValue } = useFormat(); + const { locale } = useLocale(); const filters = getFilters(type); + const formattedValues = useMemo(() => { + const formatted = {}; + const { compare } = new Intl.Collator(locale, { numeric: true }); + const format = val => { + formatted[val] = formatValue(val, name); + return formatted[val]; + }; + values.sort((a, b) => compare(formatted[a] ?? format(a), formatted[b] ?? format(b))); + return formatted; + }, [values]); + const renderFilterValue = value => { return filters.find(f => f.value === value)?.label; }; const renderValue = value => { - return formatValue(value, name); + return formattedValues[value]; }; const handleAdd = () => { @@ -59,7 +71,7 @@ export default function FieldFilterForm({ }} > {value => { - return {formatValue(value, name)}; + return {formattedValues[value]}; }} From 92f32ce7faa59e4420473009608fd817a1f48868 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 14:23:48 -0700 Subject: [PATCH 07/62] rename url filter to url_path --- src/lib/constants.ts | 2 +- src/lib/types.ts | 2 +- src/pages/api/websites/[id]/events.ts | 8 ++++---- src/pages/api/websites/[id]/metrics.ts | 8 ++++---- src/pages/api/websites/[id]/pageviews.ts | 8 ++++---- src/pages/api/websites/[id]/stats.ts | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4c468c1c..ca9cc5b0 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -45,7 +45,7 @@ export const SESSION_COLUMNS = [ ]; export const FILTER_COLUMNS = { - url: 'url_path', + url_path: 'url_path', referrer: 'referrer_domain', title: 'page_title', query: 'url_query', diff --git a/src/lib/types.ts b/src/lib/types.ts index 98fbc29b..c3d07b52 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -189,7 +189,7 @@ export interface QueryFilters { timezone?: string; unit?: string; eventType?: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 05a651ab..98b92d31 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -14,7 +14,7 @@ export interface WebsiteEventsRequestQuery { endAt: string; unit?: string; timezone?: string; - url: string; + url_path: string; } const schema = { @@ -24,7 +24,7 @@ const schema = { endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), unit: UnitTypeTest, timezone: TimezoneTest, - url: yup.string(), + url_path: yup.string(), }), }; @@ -36,7 +36,7 @@ export default async ( await useAuth(req, res); await useValidate(schema, req, res); - const { id: websiteId, timezone, url } = req.query; + const { id: websiteId, timezone, url_path } = req.query; const { startDate, endDate, unit } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -49,7 +49,7 @@ export default async ( endDate, timezone, unit, - url, + url_path, }); return ok(res, events); diff --git a/src/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts index 56b0b066..cf3ef06a 100644 --- a/src/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -13,7 +13,7 @@ export interface WebsiteMetricsRequestQuery { type: string; startAt: number; endAt: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; @@ -33,7 +33,7 @@ const schema = { type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -59,7 +59,7 @@ export default async ( const { id: websiteId, type, - url, + url_path, referrer, title, query, @@ -83,7 +83,7 @@ export default async ( const filters = { startDate, endDate, - url, + url_path, referrer, title, query, diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 7356c504..428913a5 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -12,7 +12,7 @@ export interface WebsitePageviewRequestQuery { endAt: number; unit?: string; timezone?: string; - url?: string; + url_path?: string; referrer?: string; title?: string; os?: string; @@ -32,7 +32,7 @@ const schema = { endAt: yup.number().required(), unit: UnitTypeTest, timezone: TimezoneTest, - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), os: yup.string(), @@ -55,7 +55,7 @@ export default async ( const { id: websiteId, timezone, - url, + url_path, referrer, title, os, @@ -78,7 +78,7 @@ export default async ( endDate, timezone, unit, - url, + url_path, referrer, title, os, diff --git a/src/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts index 4e8d2a88..094e860d 100644 --- a/src/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -11,7 +11,7 @@ export interface WebsiteStatsRequestQuery { id: string; startAt: number; endAt: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; @@ -30,7 +30,7 @@ const schema = { id: yup.string().uuid().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -54,7 +54,7 @@ export default async ( const { id: websiteId, - url, + url_path, referrer, title, query, @@ -78,7 +78,7 @@ export default async ( const prevEndDate = subMinutes(endDate, diff); const filters = { - url, + url_path, referrer, title, query, From cd1a98f51b5a3f81e5ddc94d8848fa7b310f3afd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 15:46:12 -0700 Subject: [PATCH 08/62] Fix worldmap mapping --- src/app/(main)/websites/[id]/WebsiteTableView.js | 2 +- src/app/(main)/websites/[id]/realtime/Realtime.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/websites/[id]/WebsiteTableView.js b/src/app/(main)/websites/[id]/WebsiteTableView.js index 7c71b84b..28a8fad6 100644 --- a/src/app/(main)/websites/[id]/WebsiteTableView.js +++ b/src/app/(main)/websites/[id]/WebsiteTableView.js @@ -5,7 +5,7 @@ import ReferrersTable from 'components/metrics/ReferrersTable'; import BrowsersTable from 'components/metrics/BrowsersTable'; import OSTable from 'components/metrics/OSTable'; import DevicesTable from 'components/metrics/DevicesTable'; -import WorldMap from 'components/common/WorldMap'; +import WorldMap from 'components/metrics/WorldMap'; import CountriesTable from 'components/metrics/CountriesTable'; import EventsTable from 'components/metrics/EventsTable'; import EventsChart from 'components/metrics/EventsChart'; diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.js index b4219b0a..737bcd1b 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.js @@ -5,7 +5,7 @@ import firstBy from 'thenby'; import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; -import WorldMap from 'components/common/WorldMap'; +import WorldMap from 'components/metrics/WorldMap'; import RealtimeLog from './RealtimeLog'; import RealtimeHeader from './RealtimeHeader'; import RealtimeUrls from './RealtimeUrls'; From d6ee8ee86966a03429bcb136f0d14d7730da5f58 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 15:48:25 -0700 Subject: [PATCH 09/62] Revert "rename url filter to url_path" This reverts commit 92f32ce7faa59e4420473009608fd817a1f48868. --- src/lib/constants.ts | 2 +- src/lib/types.ts | 2 +- src/pages/api/websites/[id]/events.ts | 8 ++++---- src/pages/api/websites/[id]/metrics.ts | 8 ++++---- src/pages/api/websites/[id]/pageviews.ts | 8 ++++---- src/pages/api/websites/[id]/stats.ts | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index ca9cc5b0..4c468c1c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -45,7 +45,7 @@ export const SESSION_COLUMNS = [ ]; export const FILTER_COLUMNS = { - url_path: 'url_path', + url: 'url_path', referrer: 'referrer_domain', title: 'page_title', query: 'url_query', diff --git a/src/lib/types.ts b/src/lib/types.ts index c3d07b52..98fbc29b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -189,7 +189,7 @@ export interface QueryFilters { timezone?: string; unit?: string; eventType?: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 98b92d31..05a651ab 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -14,7 +14,7 @@ export interface WebsiteEventsRequestQuery { endAt: string; unit?: string; timezone?: string; - url_path: string; + url: string; } const schema = { @@ -24,7 +24,7 @@ const schema = { endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), unit: UnitTypeTest, timezone: TimezoneTest, - url_path: yup.string(), + url: yup.string(), }), }; @@ -36,7 +36,7 @@ export default async ( await useAuth(req, res); await useValidate(schema, req, res); - const { id: websiteId, timezone, url_path } = req.query; + const { id: websiteId, timezone, url } = req.query; const { startDate, endDate, unit } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -49,7 +49,7 @@ export default async ( endDate, timezone, unit, - url_path, + url, }); return ok(res, events); diff --git a/src/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts index cf3ef06a..56b0b066 100644 --- a/src/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -13,7 +13,7 @@ export interface WebsiteMetricsRequestQuery { type: string; startAt: number; endAt: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; @@ -33,7 +33,7 @@ const schema = { type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -59,7 +59,7 @@ export default async ( const { id: websiteId, type, - url_path, + url, referrer, title, query, @@ -83,7 +83,7 @@ export default async ( const filters = { startDate, endDate, - url_path, + url, referrer, title, query, diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 428913a5..7356c504 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -12,7 +12,7 @@ export interface WebsitePageviewRequestQuery { endAt: number; unit?: string; timezone?: string; - url_path?: string; + url?: string; referrer?: string; title?: string; os?: string; @@ -32,7 +32,7 @@ const schema = { endAt: yup.number().required(), unit: UnitTypeTest, timezone: TimezoneTest, - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), os: yup.string(), @@ -55,7 +55,7 @@ export default async ( const { id: websiteId, timezone, - url_path, + url, referrer, title, os, @@ -78,7 +78,7 @@ export default async ( endDate, timezone, unit, - url_path, + url, referrer, title, os, diff --git a/src/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts index 094e860d..4e8d2a88 100644 --- a/src/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -11,7 +11,7 @@ export interface WebsiteStatsRequestQuery { id: string; startAt: number; endAt: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; @@ -30,7 +30,7 @@ const schema = { id: yup.string().uuid().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -54,7 +54,7 @@ export default async ( const { id: websiteId, - url_path, + url, referrer, title, query, @@ -78,7 +78,7 @@ export default async ( const prevEndDate = subMinutes(endDate, diff); const filters = { - url_path, + url, referrer, title, query, From f01697018611056ecf8934c7c33a97d79f4b3424 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Sun, 5 Nov 2023 17:11:50 +0000 Subject: [PATCH 10/62] fix single filter value --- src/app/(main)/reports/[id]/FieldFilterForm.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/reports/[id]/FieldFilterForm.js b/src/app/(main)/reports/[id]/FieldFilterForm.js index dd889fc8..ea80f82a 100644 --- a/src/app/(main)/reports/[id]/FieldFilterForm.js +++ b/src/app/(main)/reports/[id]/FieldFilterForm.js @@ -21,12 +21,16 @@ export default function FieldFilterForm({ const formattedValues = useMemo(() => { const formatted = {}; - const { compare } = new Intl.Collator(locale, { numeric: true }); const format = val => { formatted[val] = formatValue(val, name); return formatted[val]; }; - values.sort((a, b) => compare(formatted[a] ?? format(a), formatted[b] ?? format(b))); + if (values.length !== 1) { + const { compare } = new Intl.Collator(locale, { numeric: true }); + values.sort((a, b) => compare(formatted[a] ?? format(a), formatted[b] ?? format(b))); + } else { + format(values[0]); + } return formatted; }, [values]); From a34747578f850b5745ca0f986017e90fb8b3698f Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Wed, 8 Nov 2023 18:49:30 +0000 Subject: [PATCH 11/62] ensure country in region code --- src/lib/detect.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 3b2f9021..dab08312 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -107,11 +107,16 @@ export async function getLocation(ip, req) { const result = lookup.get(ip); if (result) { + const country = result.country?.iso_code ?? result?.registered_country?.iso_code; + const subdivision1 = result.subdivisions?.[0]?.iso_code; + const subdivision2 = result.subdivisions?.[1]?.names?.en; + const city = result.city?.names?.en; + return { - country: result.country?.iso_code ?? result?.registered_country?.iso_code, - subdivision1: result.subdivisions?.[0]?.iso_code, - subdivision2: result.subdivisions?.[1]?.names?.en, - city: result.city?.names?.en, + country, + subdivision1: getRegionCode(country, subdivision1), + subdivision2, + city, }; } } From eda2c07ea3480c5e5f5e7833a4f447c8f9406dce Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 11 Nov 2023 20:45:09 -0800 Subject: [PATCH 12/62] Allow embedding of share page. --- next.config.js | 51 +++++++++++-------- src/app/(main)/{Shell.tsx => App.tsx} | 4 +- src/app/(main)/dashboard/page.tsx | 2 +- src/app/(main)/layout.tsx | 8 +-- ...portsDataTable.js => ReportsDataTable.tsx} | 4 +- src/app/(main)/reports/page.tsx | 2 +- src/app/layout.tsx | 14 ++--- src/app/logout/page.tsx | 5 ++ src/app/share/[...id]/page.tsx | 5 ++ src/pages/api/send.ts | 2 +- 10 files changed, 58 insertions(+), 39 deletions(-) rename src/app/(main)/{Shell.tsx => App.tsx} (90%) rename src/app/(main)/reports/{ReportsDataTable.js => ReportsDataTable.tsx} (80%) diff --git a/next.config.js b/next.config.js index cf7dce7f..03c30c55 100644 --- a/next.config.js +++ b/next.config.js @@ -3,29 +3,32 @@ require('dotenv').config(); const path = require('path'); const pkg = require('./package.json'); -const contentSecurityPolicy = ` - default-src 'self'; - img-src *; - script-src 'self' 'unsafe-eval' 'unsafe-inline'; - style-src 'self' 'unsafe-inline'; - connect-src 'self' api.umami.is; - frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS}; -`; +const contentSecurityPolicy = [ + `default-src 'self'`, + `img-src *`, + `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, + `style-src 'self' 'unsafe-inline'`, + `connect-src 'self' api.umami.is`, +]; const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, - { + !process.env.ALLOWED_FRAME_URLS && { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, - { - key: 'Content-Security-Policy', - value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(), - }, -]; +].filter(n => n); + +const cspHeader = (values = []) => ({ + key: 'Content-Security-Policy', + value: [...contentSecurityPolicy, ...values] + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), +}); if (process.env.FORCE_SSL) { headers.push({ @@ -81,14 +84,13 @@ const config = { reactStrictMode: false, env: { basePath: basePath || '', - cloudMode: !!process.env.CLOUD_MODE, - cloudUrl: process.env.CLOUD_URL, + cloudMode: process.env.CLOUD_MODE || '', + cloudUrl: process.env.CLOUD_URL || '', configUrl: '/config', currentVersion: pkg.version, - defaultLocale: process.env.DEFAULT_LOCALE, - disableLogin: process.env.DISABLE_LOGIN, - disableUI: process.env.DISABLE_UI, - isProduction: process.env.NODE_ENV === 'production', + defaultLocale: process.env.DEFAULT_LOCALE || '', + disableLogin: process.env.DISABLE_LOGIN || '', + disableUI: process.env.DISABLE_UI || '', }, basePath, output: 'standalone', @@ -125,7 +127,14 @@ const config = { return [ { source: '/:path*', - headers, + headers: [ + ...headers, + cspHeader([`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`]), + ], + }, + { + source: '/share/:path*', + headers: [...headers, cspHeader()], }, ]; }, diff --git a/src/app/(main)/Shell.tsx b/src/app/(main)/App.tsx similarity index 90% rename from src/app/(main)/Shell.tsx rename to src/app/(main)/App.tsx index 980abb62..daf98fb1 100644 --- a/src/app/(main)/Shell.tsx +++ b/src/app/(main)/App.tsx @@ -4,7 +4,7 @@ import { usePathname } from 'next/navigation'; import UpdateNotice from 'components/common/UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; -export function Shell({ children }) { +export function App({ children }) { const { user } = useRequireLogin(); const config = useConfig(); const pathname = usePathname(); @@ -24,4 +24,4 @@ export function Shell({ children }) { ); } -export default Shell; +export default App; diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx index 91cc9c6e..1853a9f5 100644 --- a/src/app/(main)/dashboard/page.tsx +++ b/src/app/(main)/dashboard/page.tsx @@ -1,7 +1,7 @@ import Dashboard from 'app/(main)/dashboard/Dashboard'; import { Metadata } from 'next'; -export default function DashboardPage() { +export default function () { return ; } diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 1c9cc277..f5aeab67 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -1,11 +1,11 @@ -import Shell from './Shell'; +import App from './App'; import NavBar from './NavBar'; import Page from 'components/layout/Page'; import styles from './layout.module.css'; -export default function AppLayout({ children }) { +export default function ({ children }) { return ( - +
-
+ ); } diff --git a/src/app/(main)/reports/ReportsDataTable.js b/src/app/(main)/reports/ReportsDataTable.tsx similarity index 80% rename from src/app/(main)/reports/ReportsDataTable.js rename to src/app/(main)/reports/ReportsDataTable.tsx index 0daa3d06..0ca853dc 100644 --- a/src/app/(main)/reports/ReportsDataTable.js +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -5,9 +5,9 @@ import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; import useCache from 'store/cache'; -export default function ReportsDataTable({ websiteId }) { +export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { const { get } = useApi(); - const modified = useCache(state => state?.reports); + const modified = useCache(state => (state as any)?.reports); const queryResult = useFilterQuery(['reports', { websiteId, modified }], params => get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), ); diff --git a/src/app/(main)/reports/page.tsx b/src/app/(main)/reports/page.tsx index aba59db2..22e6e2a7 100644 --- a/src/app/(main)/reports/page.tsx +++ b/src/app/(main)/reports/page.tsx @@ -1,7 +1,7 @@ import ReportsHeader from './ReportsHeader'; import ReportsDataTable from './ReportsDataTable'; -export default function ReportsPage() { +export default function () { return ( <> diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e2478a95..970c33f0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,16 +8,16 @@ import 'styles/locale.css'; import 'styles/index.css'; import 'styles/variables.css'; -export default function RootLayout({ children }) { +export default function ({ children }) { return ( - - - - - - + + + + + + diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx index bce24736..89a3bce9 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -1,5 +1,10 @@ import Logout from './Logout'; +import { Metadata } from 'next'; export default function () { return ; } + +export const metadata: Metadata = { + title: 'Logout | umami', +}; diff --git a/src/app/share/[...id]/page.tsx b/src/app/share/[...id]/page.tsx index ca154165..2a69f406 100644 --- a/src/app/share/[...id]/page.tsx +++ b/src/app/share/[...id]/page.tsx @@ -1,5 +1,10 @@ import Share from './Share'; +import { Metadata } from 'next'; export default function ({ params: { id } }) { return ; } + +export const metadata: Metadata = { + title: 'umami', +}; diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index e8d3e386..cf3004f3 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -74,7 +74,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { await useCors(req, res); if (req.method === 'POST') { - if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) { + if (!process.env.DISABLE_BOT_CHECK && isbot(req.headers['user-agent'])) { return ok(res); } From bdf2fa4f05d0c1b39e11df2e9f0f336721834398 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 11 Nov 2023 20:47:06 -0800 Subject: [PATCH 13/62] Fixed import. --- src/app/(main)/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index daf98fb1..01da9a6a 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,7 +1,7 @@ 'use client'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; -import UpdateNotice from 'components/common/UpdateNotice'; +import UpdateNotice from './UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; export function App({ children }) { From 366ef35d3d86e409419ca7f84740dc07b5460a2a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 14:12:05 -0800 Subject: [PATCH 14/62] Updated types. --- next.config.js | 37 +++++++++------- package.json | 8 ++-- .../{WebsiteAddForm.js => WebsiteAddForm.tsx} | 18 ++++---- ...irmDeleteForm.js => ConfirmDeleteForm.tsx} | 8 +++- src/components/common/Empty.tsx | 2 +- ...ptyPlaceholder.js => EmptyPlaceholder.tsx} | 6 +++ .../{ErrorBoundary.js => ErrorBoundary.tsx} | 9 +++- .../{ErrorMessage.js => ErrorMessage.tsx} | 0 .../common/{Favicon.js => Favicon.tsx} | 2 +- src/components/common/FilterButtons.js | 13 ------ src/components/common/FilterButtons.tsx | 20 +++++++++ .../common/{FilterLink.js => FilterLink.tsx} | 19 ++++++++- .../common/{LinkButton.js => LinkButton.tsx} | 11 ++++- yarn.lock | 42 +++++++++++++------ 14 files changed, 136 insertions(+), 59 deletions(-) rename src/app/(main)/settings/websites/{WebsiteAddForm.js => WebsiteAddForm.tsx} (76%) rename src/components/common/{ConfirmDeleteForm.js => ConfirmDeleteForm.tsx} (80%) rename src/components/common/{EmptyPlaceholder.js => EmptyPlaceholder.tsx} (78%) rename src/components/common/{ErrorBoundary.js => ErrorBoundary.tsx} (78%) rename src/components/common/{ErrorMessage.js => ErrorMessage.tsx} (100%) rename src/components/common/{Favicon.js => Favicon.tsx} (93%) delete mode 100644 src/components/common/FilterButtons.js create mode 100644 src/components/common/FilterButtons.tsx rename src/components/common/{FilterLink.js => FilterLink.tsx} (79%) rename src/components/common/{LinkButton.js => LinkButton.tsx} (69%) diff --git a/next.config.js b/next.config.js index 03c30c55..a1e30c36 100644 --- a/next.config.js +++ b/next.config.js @@ -11,24 +11,36 @@ const contentSecurityPolicy = [ `connect-src 'self' api.umami.is`, ]; +const cspHeader = (values = []) => ({ + key: 'Content-Security-Policy', + value: values + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), +}); + const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, - !process.env.ALLOWED_FRAME_URLS && { + { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, -].filter(n => n); + cspHeader(contentSecurityPolicy), +]; -const cspHeader = (values = []) => ({ - key: 'Content-Security-Policy', - value: [...contentSecurityPolicy, ...values] - .join(';') - .replace(/\s{2,}/g, ' ') - .trim(), -}); +const shareHeaders = [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on', + }, + cspHeader([ + ...contentSecurityPolicy, + `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, + ]), +]; if (process.env.FORCE_SSL) { headers.push({ @@ -127,14 +139,11 @@ const config = { return [ { source: '/:path*', - headers: [ - ...headers, - cspHeader([`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`]), - ], + headers, }, { source: '/share/:path*', - headers: [...headers, cspHeader()], + headers: shareHeaders, }, ]; }, diff --git a/package.json b/package.json index 08038ed7..5d8fff5e 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.4.2", "react": "^18.2.0", - "react-basics": "^0.105.0", + "react-basics": "^0.106.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", @@ -125,9 +125,9 @@ "@rollup/plugin-replace": "^5.0.2", "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^8.1.0", - "@types/node": "^18.11.9", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.8", + "@types/node": "^20.9.0", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.js b/src/app/(main)/settings/websites/WebsiteAddForm.tsx similarity index 76% rename from src/app/(main)/settings/websites/WebsiteAddForm.js rename to src/app/(main)/settings/websites/WebsiteAddForm.tsx index 371343ba..99624103 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.js +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -11,22 +11,22 @@ import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteAddForm({ onSave, onClose }) { +export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { - onSave(); - onClose(); + onSave?.(); + onClose?.(); }, }); }; return ( -
+ @@ -47,9 +47,11 @@ export function WebsiteAddForm({ onSave, onClose }) { {formatMessage(labels.save)} - + {onClose && ( + + )} ); diff --git a/src/components/common/ConfirmDeleteForm.js b/src/components/common/ConfirmDeleteForm.tsx similarity index 80% rename from src/components/common/ConfirmDeleteForm.js rename to src/components/common/ConfirmDeleteForm.tsx index 3d2c383d..d4cbf203 100644 --- a/src/components/common/ConfirmDeleteForm.js +++ b/src/components/common/ConfirmDeleteForm.tsx @@ -2,7 +2,13 @@ import { useState } from 'react'; import { Button, LoadingButton, Form, FormButtons } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; -export function ConfirmDeleteForm({ name, onConfirm, onClose }) { +export interface ConfirmDeleteFormProps { + name: string; + onConfirm?: () => void; + onClose?: () => void; +} + +export function ConfirmDeleteForm({ name, onConfirm, onClose }: ConfirmDeleteFormProps) { const [loading, setLoading] = useState(false); const { formatMessage, labels, messages, FormattedMessage } = useMessages(); diff --git a/src/components/common/Empty.tsx b/src/components/common/Empty.tsx index 2c7fcd4a..4e2677f8 100644 --- a/src/components/common/Empty.tsx +++ b/src/components/common/Empty.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import styles from './Empty.module.css'; import useMessages from 'components/hooks/useMessages'; +import styles from './Empty.module.css'; export interface EmptyProps { message?: string; diff --git a/src/components/common/EmptyPlaceholder.js b/src/components/common/EmptyPlaceholder.tsx similarity index 78% rename from src/components/common/EmptyPlaceholder.js rename to src/components/common/EmptyPlaceholder.tsx index 8834a1db..27282edc 100644 --- a/src/components/common/EmptyPlaceholder.js +++ b/src/components/common/EmptyPlaceholder.tsx @@ -1,6 +1,12 @@ +import { ReactNode } from 'react'; import { Icon, Text, Flexbox } from 'react-basics'; import Logo from 'assets/logo.svg'; +export interface EmptyPlaceholderProps { + message: string; + children?: ReactNode; +} + export function EmptyPlaceholder({ message, children }) { return ( diff --git a/src/components/common/ErrorBoundary.js b/src/components/common/ErrorBoundary.tsx similarity index 78% rename from src/components/common/ErrorBoundary.js rename to src/components/common/ErrorBoundary.tsx index 32cedb39..4eb2700f 100644 --- a/src/components/common/ErrorBoundary.js +++ b/src/components/common/ErrorBoundary.tsx @@ -1,14 +1,19 @@ /* eslint-disable no-console */ +import { ErrorInfo, ReactNode } from 'react'; import { ErrorBoundary as Boundary } from 'react-error-boundary'; import { Button } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import styles from './ErrorBoundry.module.css'; -const logError = (error, info) => { +const logError = (error: Error, info: ErrorInfo) => { console.error(error, info.componentStack); }; -export function ErrorBoundary({ children }) { +export interface ErrorBoundaryProps { + children: ReactNode; +} + +export function ErrorBoundary({ children }: ErrorBoundaryProps) { const { formatMessage, messages } = useMessages(); const fallbackRender = ({ error, resetErrorBoundary }) => { diff --git a/src/components/common/ErrorMessage.js b/src/components/common/ErrorMessage.tsx similarity index 100% rename from src/components/common/ErrorMessage.js rename to src/components/common/ErrorMessage.tsx diff --git a/src/components/common/Favicon.js b/src/components/common/Favicon.tsx similarity index 93% rename from src/components/common/Favicon.js rename to src/components/common/Favicon.tsx index 55059cc0..2bf43c77 100644 --- a/src/components/common/Favicon.js +++ b/src/components/common/Favicon.tsx @@ -1,6 +1,6 @@ import styles from './Favicon.module.css'; -function getHostName(url) { +function getHostName(url: string) { const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im); return match && match.length > 1 ? match[1] : null; } diff --git a/src/components/common/FilterButtons.js b/src/components/common/FilterButtons.js deleted file mode 100644 index f5a54fb6..00000000 --- a/src/components/common/FilterButtons.js +++ /dev/null @@ -1,13 +0,0 @@ -import { ButtonGroup, Button, Flexbox } from 'react-basics'; - -export function FilterButtons({ items, selectedKey, onSelect }) { - return ( - - - {({ key, label }) => } - - - ); -} - -export default FilterButtons; diff --git a/src/components/common/FilterButtons.tsx b/src/components/common/FilterButtons.tsx new file mode 100644 index 00000000..e1860c78 --- /dev/null +++ b/src/components/common/FilterButtons.tsx @@ -0,0 +1,20 @@ +import { Key } from 'react'; +import { ButtonGroup, Button, Flexbox } from 'react-basics'; + +export interface FilterButtonsProps { + items: any[]; + selectedKey?: Key; + onSelect: () => void; +} + +export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) { + return ( + + + {({ key, label }) => } + + + ); +} + +export default FilterButtons; diff --git a/src/components/common/FilterLink.js b/src/components/common/FilterLink.tsx similarity index 79% rename from src/components/common/FilterLink.js rename to src/components/common/FilterLink.tsx index 89648255..f91e1459 100644 --- a/src/components/common/FilterLink.js +++ b/src/components/common/FilterLink.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { Icon, Icons } from 'react-basics'; import classNames from 'classnames'; import Link from 'next/link'; @@ -6,7 +7,23 @@ import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import styles from './FilterLink.module.css'; -export function FilterLink({ id, value, label, externalUrl, children, className }) { +export interface FilterLinkProps { + id: string; + value: string; + label: string; + externalUrl: string; + className: string; + children: ReactNode; +} + +export function FilterLink({ + id, + value, + label, + externalUrl, + children, + className, +}: FilterLinkProps) { const { formatMessage, labels } = useMessages(); const { makeUrl, query } = useNavigation(); const active = query[id] !== undefined; diff --git a/src/components/common/LinkButton.js b/src/components/common/LinkButton.tsx similarity index 69% rename from src/components/common/LinkButton.js rename to src/components/common/LinkButton.tsx index a9a8562d..c9366e5c 100644 --- a/src/components/common/LinkButton.js +++ b/src/components/common/LinkButton.tsx @@ -2,8 +2,17 @@ import classNames from 'classnames'; import Link from 'next/link'; import { useLocale } from 'components/hooks'; import styles from './LinkButton.module.css'; +import { ReactNode } from 'react'; -export function LinkButton({ href, className, variant, scroll = true, children }) { +export interface LinkButtonProps { + href: string; + className: string; + variant?: string; + scroll?: boolean; + children?: ReactNode; +} + +export function LinkButton({ href, className, variant, scroll = true, children }: LinkButtonProps) { const { dir } = useLocale(); return ( diff --git a/yarn.lock b/yarn.lock index 6dd8b93b..da4ce1ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2393,10 +2393,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== -"@types/node@^18.11.9": - version "18.18.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e" - integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w== +"@types/node@^20.9.0": + version "20.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" + integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== + dependencies: + undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0": version "2.4.3" @@ -2408,10 +2410,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d" integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== -"@types/react-dom@^18.0.8": - version "18.2.14" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" - integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ== +"@types/react-dom@^18.2.15": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522" + integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg== dependencies: "@types/react" "*" @@ -2425,7 +2427,7 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.0.25": +"@types/react@*", "@types/react@16 || 17 || 18": version "18.2.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b" integrity sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg== @@ -2434,6 +2436,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18.2.37": + version "18.2.37" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.37.tgz#0f03af69e463c0f19a356c2660dbca5d19c44cae" + integrity sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" @@ -7455,10 +7466,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.105.0: - version "0.105.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.105.0.tgz#94eda703b3c0728e817b6e9d086e5d1c6c68f25c" - integrity sha512-iKYtfB0A2vsmO+X4jaX64XdmHE836w8TG2jFQ0pi5Qp0ktL0lAL9/q0IrWUjNr86hi0apg46aeJWxY+qQO+T1g== +react-basics@^0.106.0: + version "0.106.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.106.0.tgz#28ba95a06e6d36adcdb303e1556e6c731b505991" + integrity sha512-CD1qxFu4wrBeNubNo/SkBfWH0BuTErBueNJCCk04IC3qM9poUr3evYfs2S4sfql7dlorcOJ2GflKC1NJ8qPvmw== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" @@ -8893,6 +8904,11 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unenv@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.7.4.tgz#a0e5a78de2c7c3c4563c06ba9763c96c59db3333" From 8775d696b8d0bcece7c667cf892526da7a7449ad Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 21:36:52 -0800 Subject: [PATCH 15/62] Typescript conversion. --- package.json | 2 +- ...HamburgerButton.js => HamburgerButton.tsx} | 2 +- .../{HoverTooltip.js => HoverTooltip.tsx} | 4 ++-- src/components/common/LinkButton.tsx | 2 +- .../common/{MobileMenu.js => MobileMenu.tsx} | 13 +++++++++--- src/components/common/{Pager.js => Pager.tsx} | 12 +++++++++-- src/components/declarations.d.ts | 1 + .../hooks/{useConfig.js => useConfig.ts} | 0 ...{useCountryNames.js => useCountryNames.ts} | 4 ++-- .../{useDateRange.js => useDateRange.ts} | 4 ++-- ...seDocumentClick.js => useDocumentClick.ts} | 2 +- src/components/hooks/useEscapeKey.js | 21 ------------------- src/components/hooks/useEscapeKey.ts | 21 +++++++++++++++++++ .../hooks/{useFilters.js => useFilters.ts} | 0 .../{useForceUpdate.js => useForceUpdate.ts} | 0 .../hooks/{useFormat.js => useFormat.ts} | 10 ++++----- ...seLanguageNames.js => useLanguageNames.ts} | 0 .../hooks/{useLocale.js => useLocale.ts} | 0 .../hooks/{useMessages.js => useMessages.ts} | 4 ++-- .../{useNavigation.js => useNavigation.ts} | 0 .../hooks/{useReport.js => useReport.ts} | 6 +++--- .../hooks/{useReports.js => useReports.ts} | 0 .../{useShareToken.js => useShareToken.ts} | 4 ++-- .../hooks/{useSticky.js => useSticky.ts} | 5 +++-- .../hooks/{useTheme.js => useTheme.ts} | 2 +- .../hooks/{useTimezone.js => useTimezone.ts} | 0 .../hooks/{useWebsite.js => useWebsite.ts} | 2 +- tsconfig.json | 2 +- yarn.lock | 8 +++---- 29 files changed, 74 insertions(+), 57 deletions(-) rename src/components/common/{HamburgerButton.js => HamburgerButton.tsx} (89%) rename src/components/common/{HoverTooltip.js => HoverTooltip.tsx} (82%) rename src/components/common/{MobileMenu.js => MobileMenu.tsx} (74%) rename src/components/common/{Pager.js => Pager.tsx} (86%) rename src/components/hooks/{useConfig.js => useConfig.ts} (100%) rename src/components/hooks/{useCountryNames.js => useCountryNames.ts} (87%) rename src/components/hooks/{useDateRange.js => useDateRange.ts} (91%) rename src/components/hooks/{useDocumentClick.js => useDocumentClick.ts} (77%) delete mode 100644 src/components/hooks/useEscapeKey.js create mode 100644 src/components/hooks/useEscapeKey.ts rename src/components/hooks/{useFilters.js => useFilters.ts} (100%) rename src/components/hooks/{useForceUpdate.js => useForceUpdate.ts} (100%) rename src/components/hooks/{useFormat.js => useFormat.ts} (81%) rename src/components/hooks/{useLanguageNames.js => useLanguageNames.ts} (100%) rename src/components/hooks/{useLocale.js => useLocale.ts} (100%) rename src/components/hooks/{useMessages.js => useMessages.ts} (81%) rename src/components/hooks/{useNavigation.js => useNavigation.ts} (100%) rename src/components/hooks/{useReport.js => useReport.ts} (94%) rename src/components/hooks/{useReports.js => useReports.ts} (100%) rename src/components/hooks/{useShareToken.js => useShareToken.ts} (77%) rename src/components/hooks/{useSticky.js => useSticky.ts} (76%) rename src/components/hooks/{useTheme.js => useTheme.ts} (97%) rename src/components/hooks/{useTimezone.js => useTimezone.ts} (100%) rename src/components/hooks/{useWebsite.js => useWebsite.ts} (81%) diff --git a/package.json b/package.json index 5d8fff5e..a483362e 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.4.2", "react": "^18.2.0", - "react-basics": "^0.106.0", + "react-basics": "^0.107.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/src/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.tsx similarity index 89% rename from src/components/common/HamburgerButton.js rename to src/components/common/HamburgerButton.tsx index 0eddad0f..380392c8 100644 --- a/src/components/common/HamburgerButton.js +++ b/src/components/common/HamburgerButton.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import MobileMenu from './MobileMenu'; import Icons from 'components/icons'; -export function HamburgerButton({ menuItems }) { +export function HamburgerButton({ menuItems }: { menuItems: any[] }) { const [active, setActive] = useState(false); const handleClick = () => setActive(state => !state); diff --git a/src/components/common/HoverTooltip.js b/src/components/common/HoverTooltip.tsx similarity index 82% rename from src/components/common/HoverTooltip.js rename to src/components/common/HoverTooltip.tsx index 614841df..e5e31219 100644 --- a/src/components/common/HoverTooltip.js +++ b/src/components/common/HoverTooltip.tsx @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { Tooltip } from 'react-basics'; import styles from './HoverTooltip.module.css'; -export function HoverTooltip({ children }) { +export function HoverTooltip({ children }: { children: ReactNode }) { const [position, setPosition] = useState({ x: -1000, y: -1000 }); useEffect(() => { diff --git a/src/components/common/LinkButton.tsx b/src/components/common/LinkButton.tsx index c9366e5c..83d95151 100644 --- a/src/components/common/LinkButton.tsx +++ b/src/components/common/LinkButton.tsx @@ -6,7 +6,7 @@ import { ReactNode } from 'react'; export interface LinkButtonProps { href: string; - className: string; + className?: string; variant?: string; scroll?: boolean; children?: ReactNode; diff --git a/src/components/common/MobileMenu.js b/src/components/common/MobileMenu.tsx similarity index 74% rename from src/components/common/MobileMenu.js rename to src/components/common/MobileMenu.tsx index 83a05dff..251085a4 100644 --- a/src/components/common/MobileMenu.js +++ b/src/components/common/MobileMenu.tsx @@ -4,12 +4,19 @@ import { usePathname } from 'next/navigation'; import Link from 'next/link'; import styles from './MobileMenu.module.css'; -export function MobileMenu({ items = [], onClose }) { +export function MobileMenu({ + items = [], + onClose, +}: { + items: any[]; + className?: string; + onClose: () => void; +}) { const pathname = usePathname(); - const Items = ({ items, className }) => ( + const Items = ({ items, className }: { items: any[]; className?: string }) => (
- {items.map(({ label, url, children }) => { + {items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => { const selected = pathname.startsWith(url); return ( diff --git a/src/components/common/Pager.js b/src/components/common/Pager.tsx similarity index 86% rename from src/components/common/Pager.js rename to src/components/common/Pager.tsx index a21d35d9..2fe7c6db 100644 --- a/src/components/common/Pager.js +++ b/src/components/common/Pager.tsx @@ -3,7 +3,15 @@ import { Button, Icon, Icons } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import styles from './Pager.module.css'; -export function Pager({ page, pageSize, count, onPageChange, className }) { +export interface PagerProps { + page: number; + pageSize: number; + count: number; + onPageChange: (nextPage: number) => void; + className?: string; +} + +export function Pager({ page, pageSize, count, onPageChange, className }: PagerProps) { const { formatMessage, labels } = useMessages(); const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0; const lastPage = page === maxPage; @@ -13,7 +21,7 @@ export function Pager({ page, pageSize, count, onPageChange, className }) { return null; } - const handlePageChange = value => { + const handlePageChange = (value: number) => { const nextPage = page + value; if (nextPage > 0 && nextPage <= maxPage) { onPageChange(nextPage); diff --git a/src/components/declarations.d.ts b/src/components/declarations.d.ts index 31e44ff3..81533301 100644 --- a/src/components/declarations.d.ts +++ b/src/components/declarations.d.ts @@ -1,2 +1,3 @@ declare module '*.css'; declare module '*.svg'; +declare module '*.json'; diff --git a/src/components/hooks/useConfig.js b/src/components/hooks/useConfig.ts similarity index 100% rename from src/components/hooks/useConfig.js rename to src/components/hooks/useConfig.ts diff --git a/src/components/hooks/useCountryNames.js b/src/components/hooks/useCountryNames.ts similarity index 87% rename from src/components/hooks/useCountryNames.js rename to src/components/hooks/useCountryNames.ts index 40611865..22f20666 100644 --- a/src/components/hooks/useCountryNames.js +++ b/src/components/hooks/useCountryNames.ts @@ -6,10 +6,10 @@ const countryNames = { 'en-US': enUS, }; -export function useCountryNames(locale) { +export function useCountryNames(locale: string) { const [list, setList] = useState(countryNames[locale] || enUS); - async function loadData(locale) { + async function loadData(locale: string) { const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`); if (data) { diff --git a/src/components/hooks/useDateRange.js b/src/components/hooks/useDateRange.ts similarity index 91% rename from src/components/hooks/useDateRange.js rename to src/components/hooks/useDateRange.ts index 1e1b0616..6e70a368 100644 --- a/src/components/hooks/useDateRange.js +++ b/src/components/hooks/useDateRange.ts @@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; import useApi from './useApi'; -export function useDateRange(websiteId) { +export function useDateRange(websiteId: string) { const { get } = useApi(); const { locale } = useLocale(); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); @@ -20,7 +20,7 @@ export function useDateRange(websiteId) { if (typeof value === 'string') { if (value === 'all') { - const result = await get(`/websites/${websiteId}/daterange`); + const result: any = await get(`/websites/${websiteId}/daterange`); const { mindate, maxdate } = result; const startDate = new Date(mindate); diff --git a/src/components/hooks/useDocumentClick.js b/src/components/hooks/useDocumentClick.ts similarity index 77% rename from src/components/hooks/useDocumentClick.js rename to src/components/hooks/useDocumentClick.ts index be3d09be..eefd9366 100644 --- a/src/components/hooks/useDocumentClick.js +++ b/src/components/hooks/useDocumentClick.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -export function useDocumentClick(handler) { +export function useDocumentClick(handler: (event: MouseEvent) => any) { useEffect(() => { document.addEventListener('click', handler); diff --git a/src/components/hooks/useEscapeKey.js b/src/components/hooks/useEscapeKey.js deleted file mode 100644 index 1a17f18f..00000000 --- a/src/components/hooks/useEscapeKey.js +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useCallback } from 'react'; - -export function useEscapeKey(handler) { - const escFunction = useCallback(event => { - if (event.keyCode === 27) { - handler(event); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', escFunction, false); - - return () => { - document.removeEventListener('keydown', escFunction, false); - }; - }, [escFunction]); - - return null; -} - -export default useEscapeKey; diff --git a/src/components/hooks/useEscapeKey.ts b/src/components/hooks/useEscapeKey.ts new file mode 100644 index 00000000..5c3350e7 --- /dev/null +++ b/src/components/hooks/useEscapeKey.ts @@ -0,0 +1,21 @@ +import { useEffect, useCallback, KeyboardEvent } from 'react'; + +export function useEscapeKey(handler: (event: KeyboardEvent) => void) { + const escFunction = useCallback((event: KeyboardEvent) => { + if (event.key === 'Escape') { + handler(event); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', escFunction as any, false); + + return () => { + document.removeEventListener('keydown', escFunction as any, false); + }; + }, [escFunction]); + + return null; +} + +export default useEscapeKey; diff --git a/src/components/hooks/useFilters.js b/src/components/hooks/useFilters.ts similarity index 100% rename from src/components/hooks/useFilters.js rename to src/components/hooks/useFilters.ts diff --git a/src/components/hooks/useForceUpdate.js b/src/components/hooks/useForceUpdate.ts similarity index 100% rename from src/components/hooks/useForceUpdate.js rename to src/components/hooks/useForceUpdate.ts diff --git a/src/components/hooks/useFormat.js b/src/components/hooks/useFormat.ts similarity index 81% rename from src/components/hooks/useFormat.js rename to src/components/hooks/useFormat.ts index 0e609c48..c1160162 100644 --- a/src/components/hooks/useFormat.js +++ b/src/components/hooks/useFormat.ts @@ -9,23 +9,23 @@ export function useFormat() { const { locale } = useLocale(); const countryNames = useCountryNames(locale); - const formatBrowser = value => { + const formatBrowser = (value: string) => { return BROWSERS[value] || value; }; - const formatCountry = value => { + const formatCountry = (value: string) => { return countryNames[value] || value; }; - const formatRegion = value => { + const formatRegion = (value: string) => { return regions[value] ? regions[value] : value; }; - const formatDevice = value => { + const formatDevice = (value: string) => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value, type) => { + const formatValue = (value: string, type: string) => { switch (type) { case 'browser': return formatBrowser(value); diff --git a/src/components/hooks/useLanguageNames.js b/src/components/hooks/useLanguageNames.ts similarity index 100% rename from src/components/hooks/useLanguageNames.js rename to src/components/hooks/useLanguageNames.ts diff --git a/src/components/hooks/useLocale.js b/src/components/hooks/useLocale.ts similarity index 100% rename from src/components/hooks/useLocale.js rename to src/components/hooks/useLocale.ts diff --git a/src/components/hooks/useMessages.js b/src/components/hooks/useMessages.ts similarity index 81% rename from src/components/hooks/useMessages.js rename to src/components/hooks/useMessages.ts index e3a6c20b..0801c7d9 100644 --- a/src/components/hooks/useMessages.js +++ b/src/components/hooks/useMessages.ts @@ -4,13 +4,13 @@ import { messages, labels } from 'components/messages'; export function useMessages() { const intl = useIntl(); - const getMessage = id => { + const getMessage = (id: string) => { const message = Object.values(messages).find(value => value.id === id); return message ? formatMessage(message) : id; }; - const formatMessage = (descriptor, values, opts) => { + const formatMessage = (descriptor: any, values?: any, opts?: any) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; diff --git a/src/components/hooks/useNavigation.js b/src/components/hooks/useNavigation.ts similarity index 100% rename from src/components/hooks/useNavigation.js rename to src/components/hooks/useNavigation.ts diff --git a/src/components/hooks/useReport.js b/src/components/hooks/useReport.ts similarity index 94% rename from src/components/hooks/useReport.js rename to src/components/hooks/useReport.ts index 7c698b4e..1686e222 100644 --- a/src/components/hooks/useReport.js +++ b/src/components/hooks/useReport.ts @@ -18,7 +18,7 @@ export function useReport(reportId, defaultParameters) { }; const loadReport = async id => { - const data = await get(`/reports/${id}`); + const data: any = await get(`/reports/${id}`); const { dateRange } = data?.parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -40,7 +40,7 @@ export function useReport(reportId, defaultParameters) { const data = await post(`/reports/${type}`, { ...parameters, timezone }); setReport( - produce(state => { + produce((state: any) => { state.parameters = parameters; state.data = data; @@ -56,7 +56,7 @@ export function useReport(reportId, defaultParameters) { const updateReport = useCallback( async data => { setReport( - produce(state => { + produce((state: any) => { const { parameters, ...rest } = data; if (parameters) { diff --git a/src/components/hooks/useReports.js b/src/components/hooks/useReports.ts similarity index 100% rename from src/components/hooks/useReports.js rename to src/components/hooks/useReports.ts diff --git a/src/components/hooks/useShareToken.js b/src/components/hooks/useShareToken.ts similarity index 77% rename from src/components/hooks/useShareToken.js rename to src/components/hooks/useShareToken.ts index 5062c73e..088f643e 100644 --- a/src/components/hooks/useShareToken.js +++ b/src/components/hooks/useShareToken.ts @@ -1,9 +1,9 @@ import useStore, { setShareToken } from 'store/app'; import useApi from './useApi'; -const selector = state => state.shareToken; +const selector = (state: { shareToken: string }) => state.shareToken; -export function useShareToken(shareId) { +export function useShareToken(shareId: string) { const shareToken = useStore(selector); const { get, useQuery } = useApi(); const { isLoading, error } = useQuery(['share', shareId], async () => { diff --git a/src/components/hooks/useSticky.js b/src/components/hooks/useSticky.ts similarity index 76% rename from src/components/hooks/useSticky.js rename to src/components/hooks/useSticky.ts index be33f6ed..459c489a 100644 --- a/src/components/hooks/useSticky.js +++ b/src/components/hooks/useSticky.ts @@ -5,8 +5,9 @@ export function useSticky({ enabled = true, threshold = 1 }) { const ref = useRef(null); useEffect(() => { - let observer; - const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold); + let observer: IntersectionObserver | undefined; + const handler: IntersectionObserverCallback = ([entry]) => + setIsSticky(entry.intersectionRatio < threshold); if (enabled && ref.current) { observer = new IntersectionObserver(handler, { threshold: [threshold] }); diff --git a/src/components/hooks/useTheme.js b/src/components/hooks/useTheme.ts similarity index 97% rename from src/components/hooks/useTheme.js rename to src/components/hooks/useTheme.ts index 7e40f601..099bf962 100644 --- a/src/components/hooks/useTheme.js +++ b/src/components/hooks/useTheme.ts @@ -4,7 +4,7 @@ import { getItem, setItem } from 'next-basics'; import { THEME_COLORS, THEME_CONFIG } from 'lib/constants'; import { colord } from 'colord'; -const selector = state => state.theme; +const selector = (state: { theme: string }) => state.theme; export function useTheme() { const defaultTheme = diff --git a/src/components/hooks/useTimezone.js b/src/components/hooks/useTimezone.ts similarity index 100% rename from src/components/hooks/useTimezone.js rename to src/components/hooks/useTimezone.ts diff --git a/src/components/hooks/useWebsite.js b/src/components/hooks/useWebsite.ts similarity index 81% rename from src/components/hooks/useWebsite.js rename to src/components/hooks/useWebsite.ts index 5315f0dc..7b68335a 100644 --- a/src/components/hooks/useWebsite.js +++ b/src/components/hooks/useWebsite.ts @@ -1,6 +1,6 @@ import useApi from './useApi'; -export function useWebsite(websiteId) { +export function useWebsite(websiteId: string) { const { get, useQuery } = useApi(); return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), { enabled: !!websiteId, diff --git a/tsconfig.json b/tsconfig.json index 9b8b6033..1807e947 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2021", "outDir": "./build", "module": "esnext", "moduleResolution": "node", diff --git a/yarn.lock b/yarn.lock index da4ce1ca..f2ed9264 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7466,10 +7466,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.106.0: - version "0.106.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.106.0.tgz#28ba95a06e6d36adcdb303e1556e6c731b505991" - integrity sha512-CD1qxFu4wrBeNubNo/SkBfWH0BuTErBueNJCCk04IC3qM9poUr3evYfs2S4sfql7dlorcOJ2GflKC1NJ8qPvmw== +react-basics@^0.107.0: + version "0.107.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3" + integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" From a78d11e352ffae77a57aad9845ccc2ad6923214c Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 21:58:23 -0800 Subject: [PATCH 16/62] Fixed types. --- package.json | 2 +- src/components/common/MobileMenu.tsx | 4 +- src/components/hooks/useMessages.ts | 16 +++- src/components/hooks/useNavigation.ts | 2 +- yarn.lock | 114 +++++++++++++------------- 5 files changed, 74 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index a483362e..fe3fa664 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", - "react-intl": "^6.4.7", + "react-intl": "^6.5.5", "react-simple-maps": "^2.3.0", "react-use-measure": "^2.0.4", "react-window": "^1.8.6", diff --git a/src/components/common/MobileMenu.tsx b/src/components/common/MobileMenu.tsx index 251085a4..e14f0b83 100644 --- a/src/components/common/MobileMenu.tsx +++ b/src/components/common/MobileMenu.tsx @@ -11,10 +11,10 @@ export function MobileMenu({ items: any[]; className?: string; onClose: () => void; -}) { +}): any { const pathname = usePathname(); - const Items = ({ items, className }: { items: any[]; className?: string }) => ( + const Items = ({ items, className }: { items: any[]; className?: string }): any => (
{items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => { const selected = pathname.startsWith(url); diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index 0801c7d9..594a3c61 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -1,7 +1,8 @@ -import { useIntl, FormattedMessage } from 'react-intl'; +import { useIntl, FormattedMessage, MessageDescriptor, PrimitiveType } from 'react-intl'; import { messages, labels } from 'components/messages'; +import { FormatXMLElementFn, Options } from 'intl-messageformat'; -export function useMessages() { +export function useMessages(): any { const intl = useIntl(); const getMessage = (id: string) => { @@ -10,7 +11,16 @@ export function useMessages() { return message ? formatMessage(message) : id; }; - const formatMessage = (descriptor: any, values?: any, opts?: any) => { + const formatMessage = ( + descriptor: + | MessageDescriptor + | { + id: string; + defaultMessage: string; + }, + values?: Record>, + opts?: Options, + ) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 658e81ed..9f01cd80 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -17,7 +17,7 @@ export function useNavigation() { return obj; }, [params]); - function makeUrl(params, reset) { + function makeUrl(params: any, reset?: boolean) { return reset ? pathname : buildUrl(pathname, { ...query, ...params }); } diff --git a/yarn.lock b/yarn.lock index f2ed9264..d7596fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1353,12 +1353,12 @@ "@formatjs/intl-localematcher" "0.2.25" tslib "^2.1.0" -"@formatjs/ecma402-abstract@1.17.2": - version "1.17.2" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3" - integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg== +"@formatjs/ecma402-abstract@1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2" + integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA== dependencies: - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" "@formatjs/ecma402-abstract@1.4.0": @@ -1391,13 +1391,13 @@ "@formatjs/icu-skeleton-parser" "1.3.6" tslib "^2.1.0" -"@formatjs/icu-messageformat-parser@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.0.tgz#9b13f2710a3b4efddfeb544480f684f27a53483b" - integrity sha512-7uqC4C2RqOaBQtcjqXsSpGRYVn+ckjhNga5T/otFh6MgxRrCJQqvjfbrGLpX1Lcbxdm5WH3Z2WZqt1+Tm/cn/Q== +"@formatjs/icu-messageformat-parser@2.7.3": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz#c8c95e7c9f8141bdb93bea0e92e4fcace19d3c9f" + integrity sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/icu-skeleton-parser" "1.6.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/icu-skeleton-parser" "1.7.0" tslib "^2.4.0" "@formatjs/icu-skeleton-parser@1.3.6": @@ -1408,30 +1408,30 @@ "@formatjs/ecma402-abstract" "1.11.4" tslib "^2.1.0" -"@formatjs/icu-skeleton-parser@1.6.2": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d" - integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA== +"@formatjs/icu-skeleton-parser@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz#796938d6d0ba8fc75bb9edee038d1350bfee32cb" + integrity sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" tslib "^2.4.0" -"@formatjs/intl-displaynames@6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.0.tgz#6f590784b1bcdc1b96d4dba158bdce350f876804" - integrity sha512-bskUou9boZOzTqI8JdNCNkDavXf8uWWz/6NG1og/XJKpn4zsfiLdQ9EYKhVe/CfbCjlSyieJYn7/NztdoprHjw== +"@formatjs/intl-displaynames@6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz#dd9ca9bb2d1f4b140cc8814667bc830802621674" + integrity sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" -"@formatjs/intl-listformat@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.0.tgz#dbccf2e0f07792aa1c273702796bdad061dc27ae" - integrity sha512-n9FsXGl1T2ZbX6wSyrzCDJHrbJR0YJ9ZNsAqUvHXfbY3nsOmGnSTf5+bkuIp1Xiywu7m1X1Pfm/Ngp/yK1H84A== +"@formatjs/intl-listformat@7.5.3": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz#c6f028471839cd1014760498f783fdfe011422d5" + integrity sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" "@formatjs/intl-localematcher@0.2.25": @@ -1441,10 +1441,10 @@ dependencies: tslib "^2.1.0" -"@formatjs/intl-localematcher@0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a" - integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA== +"@formatjs/intl-localematcher@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532" + integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw== dependencies: tslib "^2.4.0" @@ -1456,17 +1456,17 @@ "@formatjs/ecma402-abstract" "1.4.0" tslib "^2.0.1" -"@formatjs/intl@2.9.4": - version "2.9.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.4.tgz#6f97a8e6e282086c39c8e502face5b2839f47b6f" - integrity sha512-hY0UlbDz8jY12RkQtkzxe3OfUmsIcUcsvVYyr1TFue6oTrUHqpkmYLdQ626V3BCSLc90EZDXdvmsPfMd3hTcYQ== +"@formatjs/intl@2.9.9": + version "2.9.9" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.9.tgz#866629b565e20dd7490f9e77ee41a00748913e8f" + integrity sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.0" - "@formatjs/intl-displaynames" "6.6.0" - "@formatjs/intl-listformat" "7.5.0" - intl-messageformat "10.5.4" + "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/intl-displaynames" "6.6.4" + "@formatjs/intl-listformat" "7.5.3" + intl-messageformat "10.5.8" tslib "^2.4.0" "@formatjs/ts-transformer@3.9.4": @@ -5169,14 +5169,14 @@ intl-messageformat-parser@^5.3.7: dependencies: "@formatjs/intl-numberformat" "^5.5.2" -intl-messageformat@10.5.4: - version "10.5.4" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.4.tgz#7b212b083f1b354d7e282518e78057e025134af9" - integrity sha512-z+hrFdiJ/heRYlzegrdFYqU1m/KOMOVMqNilIArj+PbsuU8TNE7v4TWdQgSoxlxbT4AcZH3Op3/Fu15QTp+W1w== +intl-messageformat@10.5.8: + version "10.5.8" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.8.tgz#7184da425f360a53a5483a6194e16d666b011fc0" + integrity sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.0" + "@formatjs/icu-messageformat-parser" "2.7.3" tslib "^2.4.0" ioredis@^5.3.2: @@ -7510,20 +7510,20 @@ react-hook-form@^7.34.2: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31" integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg== -react-intl@^6.4.7: - version "6.5.0" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.0.tgz#0ff04170f91e1bcbcd3301dfb2ae39a258827ec7" - integrity sha512-ZnBYFlFUU1ivhvWBA87XJLAr9nR8yeC1/83e6AL7yiHbWH7xQE7tyMyIyw6or78EvU9Hx8Sh8LUDC4bGrNxXOA== +react-intl@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.5.tgz#d2de7bfd79718a7e3d8031e2599e94e0c8638377" + integrity sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/icu-messageformat-parser" "2.7.0" - "@formatjs/intl" "2.9.4" - "@formatjs/intl-displaynames" "6.6.0" - "@formatjs/intl-listformat" "7.5.0" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/intl" "2.9.9" + "@formatjs/intl-displaynames" "6.6.4" + "@formatjs/intl-listformat" "7.5.3" "@types/hoist-non-react-statics" "^3.3.1" "@types/react" "16 || 17 || 18" hoist-non-react-statics "^3.3.2" - intl-messageformat "10.5.4" + intl-messageformat "10.5.8" tslib "^2.4.0" react-is@^16.13.1, react-is@^16.7.0: From 9b54b3a3c76344a5fc4504d89727cc8ec38f7bd1 Mon Sep 17 00:00:00 2001 From: Zach Hawtof Date: Tue, 14 Nov 2023 22:44:32 -0500 Subject: [PATCH 17/62] Update moment to moment-timezone --- src/lib/yup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/yup.ts b/src/lib/yup.ts index 5bd0aa18..4008e44f 100644 --- a/src/lib/yup.ts +++ b/src/lib/yup.ts @@ -1,4 +1,4 @@ -import moment from 'moment'; +import moment from 'moment-timezone'; import * as yup from 'yup'; import { UNIT_TYPES } from './constants'; From 27f5c8fceb6ceafad0482d22c1b416390f176694 Mon Sep 17 00:00:00 2001 From: Philip Schmidt Date: Fri, 17 Nov 2023 16:01:36 +0100 Subject: [PATCH 18/62] execute onSave only if exists / update cache and show toast --- src/app/(main)/settings/users/UserAddButton.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/(main)/settings/users/UserAddButton.js b/src/app/(main)/settings/users/UserAddButton.js index 8b691362..0f4bf734 100644 --- a/src/app/(main)/settings/users/UserAddButton.js +++ b/src/app/(main)/settings/users/UserAddButton.js @@ -1,12 +1,16 @@ -import { Button, Icon, Text, Modal, Icons, ModalTrigger } from 'react-basics'; +import { Button, Icon, Text, Modal, Icons, ModalTrigger, useToasts } from 'react-basics'; import UserAddForm from './UserAddForm'; import useMessages from 'components/hooks/useMessages'; +import { setValue } from 'store/cache'; export function UserAddButton({ onSave }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, messages } = useMessages(); + const { showToast } = useToasts(); const handleSave = () => { - onSave(); + showToast({ message: formatMessage(messages.saved), variant: 'success' }); + setValue('users', Date.now()); + onSave?.(); }; return ( From 4dedc57d0a9b6ff1847ea0951d376698e637270a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 22 Nov 2023 18:03:48 -0800 Subject: [PATCH 19/62] Updated clients. --- .eslintrc.json | 16 ++++++++-------- package.json | 4 ++-- .../settings/teams/[id]/TeamWebsiteAddForm.js | 6 ++++-- src/app/(main)/settings/websites/Websites.tsx | 15 +++++++++++++++ .../settings/websites/WebsitesDataTable.tsx | 12 ++++++------ src/app/(main)/settings/websites/page.tsx | 10 ++-------- src/app/(main)/websites/WebsitesBrowse.js | 12 +++++++++--- src/components/hooks/useRequireLogin.ts | 6 +++--- src/declaration.d.ts | 1 + src/lib/auth.ts | 9 +++++---- src/lib/client.ts | 2 +- src/lib/constants.ts | 2 +- src/pages/api/auth/verify.ts | 2 +- yarn.lock | 16 ++++++++-------- 14 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 src/app/(main)/settings/websites/Websites.tsx create mode 100644 src/declaration.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index a77ed5bd..9d747b87 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,14 +4,6 @@ "es2020": true, "node": true }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 11, - "sourceType": "module" - }, "extends": [ "eslint:recommended", "plugin:prettier/recommended", @@ -19,6 +11,14 @@ "plugin:@typescript-eslint/recommended", "next" ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 11, + "sourceType": "module" + }, "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/resolver": { diff --git a/package.json b/package.json index fe3fa664..edc6b1e0 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "@prisma/client": "5.4.2", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.3.0", - "@umami/redis-client": "^0.16.0", + "@umami/prisma-client": "^0.5.0", + "@umami/redis-client": "^0.17.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", "chartjs-adapter-date-fns": "^3.0.0", diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js index 9c2ae7bd..c83ec3d0 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js @@ -2,11 +2,13 @@ import useApi from 'components/hooks/useApi'; import { useState } from 'react'; import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; -import WebsitesDataTable from '../../websites/WebsitesDataTable'; +import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable'; import Empty from 'components/common/Empty'; import { setValue } from 'store/cache'; +import { useUser } from 'components/hooks'; export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { + const { user } = useUser(); const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); @@ -37,7 +39,7 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { {!isLoading && !hasData && } {hasData && (
- + {row => ( + + + + ); +} diff --git a/src/app/(main)/settings/websites/WebsitesDataTable.tsx b/src/app/(main)/settings/websites/WebsitesDataTable.tsx index 441ae56d..fc6dd0c0 100644 --- a/src/app/(main)/settings/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesDataTable.tsx @@ -1,13 +1,13 @@ 'use client'; import { ReactNode } from 'react'; import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; -import useUser from 'components/hooks/useUser'; import useApi from 'components/hooks/useApi'; import DataTable from 'components/common/DataTable'; import useFilterQuery from 'components/hooks/useFilterQuery'; import useCache from 'store/cache'; export interface WebsitesDataTableProps { + userId: string; allowEdit?: boolean; allowView?: boolean; showActions?: boolean; @@ -17,25 +17,25 @@ export interface WebsitesDataTableProps { children?: ReactNode; } -function useWebsites({ includeTeams, onlyTeams }) { - const { user } = useUser(); +function useWebsites(userId: string, { includeTeams, onlyTeams }) { const { get } = useApi(); const modified = useCache((state: any) => state?.websites); return useFilterQuery( ['websites', { includeTeams, onlyTeams, modified }], (params: any) => { - return get(`/users/${user?.id}/websites`, { + return get(`/users/${userId}/websites`, { includeTeams, onlyTeams, ...params, }); }, - { enabled: !!user }, + { enabled: !!userId }, ); } export function WebsitesDataTable({ + userId, allowEdit = true, allowView = true, showActions = true, @@ -44,7 +44,7 @@ export function WebsitesDataTable({ onlyTeams, children, }: WebsitesDataTableProps) { - const queryResult = useWebsites({ includeTeams, onlyTeams }); + const queryResult = useWebsites(userId, { includeTeams, onlyTeams }); return ( diff --git a/src/app/(main)/settings/websites/page.tsx b/src/app/(main)/settings/websites/page.tsx index 2c83dce0..d6d11898 100644 --- a/src/app/(main)/settings/websites/page.tsx +++ b/src/app/(main)/settings/websites/page.tsx @@ -1,14 +1,8 @@ -import WebsitesDataTable from './WebsitesDataTable'; -import WebsitesHeader from './WebsitesHeader'; import { Metadata } from 'next'; +import Websites from './Websites'; export default function () { - return ( - <> - - - - ); + return ; } export const metadata: Metadata = { diff --git a/src/app/(main)/websites/WebsitesBrowse.js b/src/app/(main)/websites/WebsitesBrowse.js index f1bab7bf..3e8df2b2 100644 --- a/src/app/(main)/websites/WebsitesBrowse.js +++ b/src/app/(main)/websites/WebsitesBrowse.js @@ -1,6 +1,6 @@ 'use client'; import WebsitesDataTable from '../settings/websites/WebsitesDataTable'; -import { useMessages } from 'components/hooks'; +import { useMessages, useUser } from 'components/hooks'; import { useState } from 'react'; import { Item, Tabs } from 'react-basics'; @@ -10,6 +10,7 @@ const TABS = { }; export function WebsitesBrowse() { + const { user } = useUser(); const { formatMessage, labels } = useMessages(); const [tab, setTab] = useState(TABS.myWebsites); const allowEdit = !process.env.cloudMode; @@ -20,9 +21,14 @@ export function WebsitesBrowse() { {formatMessage(labels.myWebsites)} {formatMessage(labels.teamWebsites)} - {tab === TABS.myWebsites && } + {tab === TABS.myWebsites && } {tab === TABS.teamWebsites && ( - + )} ); diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts index 76460a55..68de411b 100644 --- a/src/components/hooks/useRequireLogin.ts +++ b/src/components/hooks/useRequireLogin.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import useApi from 'components/hooks/useApi'; import useUser from 'components/hooks/useUser'; -export function useRequireLogin(handler?: (data?: object) => void) { +export function useRequireLogin() { const { get } = useApi(); const { user, setUser } = useUser(); @@ -11,9 +11,9 @@ export function useRequireLogin(handler?: (data?: object) => void) { try { const data = await get('/auth/verify'); - setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user); + setUser(data); } catch { - location.href = `${process.env.basePath || ''}/login`; + window.location.href = `${process.env.basePath || ''}/login`; } } diff --git a/src/declaration.d.ts b/src/declaration.d.ts new file mode 100644 index 00000000..42a5eeed --- /dev/null +++ b/src/declaration.d.ts @@ -0,0 +1 @@ +declare module 'debug'; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 4a42d85d..c218cef9 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -7,14 +7,15 @@ import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries'; import { loadWebsite } from './load'; import { Auth } from './types'; +import { NextApiRequest } from 'next'; const log = debug('umami:auth'); const cloudMode = process.env.CLOUD_MODE; -export async function setAuthKey(user, expire = 0) { +export async function setAuthKey(data: any, expire = 0) { const authKey = `auth:${getRandomChars(32)}`; - await redis.set(authKey, user); + await redis.set(authKey, data); if (expire) { await redis.expire(authKey, expire); @@ -23,7 +24,7 @@ export async function setAuthKey(user, expire = 0) { return createSecureToken({ authKey }, secret()); } -export function getAuthToken(req) { +export function getAuthToken(req: NextApiRequest) { try { return req.headers.authorization.split(' ')[1]; } catch { @@ -31,7 +32,7 @@ export function getAuthToken(req) { } } -export function parseShareToken(req) { +export function parseShareToken(req: Request) { try { return parseToken(req.headers[SHARE_TOKEN_HEADER], secret()); } catch (e) { diff --git a/src/lib/client.ts b/src/lib/client.ts index 8c69d23d..7810c44a 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -5,7 +5,7 @@ export function getClientAuthToken() { return getItem(AUTH_TOKEN); } -export function setClientAuthToken(token) { +export function setClientAuthToken(token: string) { setItem(AUTH_TOKEN, token); } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4c468c1c..0c894634 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -13,7 +13,7 @@ export const REPO_URL = 'https://github.com/umami-software/umami'; export const UPDATES_URL = 'https://api.umami.is/v1/updates'; export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png'; -export const DEFAULT_LOCALE = process.env.defaultLocale ?? 'en-US'; +export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US'; export const DEFAULT_THEME = 'light'; export const DEFAULT_ANIMATION_DURATION = 300; export const DEFAULT_DATE_RANGE = '24hour'; diff --git a/src/pages/api/auth/verify.ts b/src/pages/api/auth/verify.ts index 9eb9ea48..a302c69b 100644 --- a/src/pages/api/auth/verify.ts +++ b/src/pages/api/auth/verify.ts @@ -6,5 +6,5 @@ import { ok } from 'next-basics'; export default async (req: NextApiRequestAuth, res: NextApiResponse) => { await useAuth(req, res); - return ok(res, req.auth); + return ok(res, req.auth.user); }; diff --git a/yarn.lock b/yarn.lock index d7596fa4..aa13ffda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2596,18 +2596,18 @@ "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" -"@umami/prisma-client@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.3.0.tgz#fea35a44c76af0e4ce58288107cda3ee76fc80ba" - integrity sha512-88y/WJX2TEZaUfP+PTretGUL6YdwZCBbhaoeC87eTF3l1aG0Lv3TsmW0lJy5rbKpVqrFJ8zrtvCMP/vt7WeIjg== +"@umami/prisma-client@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf" + integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg== dependencies: chalk "^4.1.2" debug "^4.3.4" -"@umami/redis-client@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.16.0.tgz#0050d1f93338d88691c983f3c0cd4a62da20212b" - integrity sha512-fE08lkMvhXbkXSdSRpG0R/9a3xIiTvwD6f+hKERFZrpfvJJlH3Uf4Jod8Ahg/+TmD03ihSQPooUT3T9Ig3dfaQ== +"@umami/redis-client@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.17.0.tgz#a24db0cb561a9c18b0ecaf6a3342c28525abe1e5" + integrity sha512-rX0xB+QkhMoHnKcj8jzbp1CUMKB/qk2HaYWpNP0Stztkvw7wjFYXsLTLidW3t1a06H5qApx6mJvUjxefLUsX7w== dependencies: debug "^4.3.4" redis "^4.5.1" From e1cd8eac8351b39171b3a08ab69b659464ffc40a Mon Sep 17 00:00:00 2001 From: vndroid Date: Mon, 27 Nov 2023 16:25:53 +0800 Subject: [PATCH 20/62] Update Health Check - Node container support healthcheck. --- Dockerfile | 4 +++- docker-compose.yml | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 12951a73..801b2bc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,9 @@ ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -RUN yarn add npm-run-all dotenv prisma semver +RUN set -x \ + && apk add --no-cache curl \ + && yarn add npm-run-all dotenv prisma semver # You only need to copy next.config.js if you are NOT using the default configuration COPY --from=builder /app/next.config.js . diff --git a/docker-compose.yml b/docker-compose.yml index b8da9373..08f00b7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,11 @@ services: db: condition: service_healthy restart: always + healthcheck: + test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] + interval: 5s + timeout: 5s + retries: 5 db: image: postgres:15-alpine environment: From 4c0beaee14cb1482428f10f0cfe61bd70c0bd1e8 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 27 Nov 2023 16:11:09 -0800 Subject: [PATCH 21/62] add extension-read-replicas package --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index edc6b1e0..367bcf9b 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", "@prisma/client": "5.4.2", + "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.5.0", diff --git a/yarn.lock b/yarn.lock index aa13ffda..9927b8ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1958,6 +1958,11 @@ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f" integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g== +"@prisma/extension-read-replicas@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@prisma/extension-read-replicas/-/extension-read-replicas-0.3.0.tgz#2842a7c928f957c1dd58a6256104797596d43426" + integrity sha512-F9+rSmYday6GT2qjhJtkZcBOpLO5LtpvFcMGqrBDHf+78LEdSuxfFjOxYlNuqk4B+th62yxpbhfpmB9/Mca14Q== + "@react-spring/animated@~9.7.3": version "9.7.3" resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.3.tgz#4211b1a6d48da0ff474a125e93c0f460ff816e0f" From f7dd0464e07c23c8c837847a4b8c0fd929a7ac1b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 27 Nov 2023 20:47:35 -0800 Subject: [PATCH 22/62] Updated redis calls. --- package.json | 2 +- src/declaration.d.ts | 1 + src/lib/auth.ts | 20 ++++++-------------- src/lib/cache.ts | 32 ++++++++++++++++++-------------- src/lib/middleware.ts | 10 ++++++---- src/pages/api/auth/login.ts | 6 +++--- src/pages/api/auth/logout.ts | 4 ++-- src/pages/api/auth/sso.ts | 6 +++--- yarn.lock | 8 ++++---- 9 files changed, 44 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 367bcf9b..cf0398f7 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.5.0", - "@umami/redis-client": "^0.17.0", + "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", "chartjs-adapter-date-fns": "^3.0.0", diff --git a/src/declaration.d.ts b/src/declaration.d.ts index 42a5eeed..3523e9fa 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -1 +1,2 @@ +declare module 'cors'; declare module 'debug'; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index c218cef9..1757f05e 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,6 @@ import { Report } from '@prisma/client'; -import redis from '@umami/redis-client'; import debug from 'debug'; +import redis from '@umami/redis-client'; import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants'; import { secret } from 'lib/crypto'; import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics'; @@ -12,13 +12,13 @@ import { NextApiRequest } from 'next'; const log = debug('umami:auth'); const cloudMode = process.env.CLOUD_MODE; -export async function setAuthKey(data: any, expire = 0) { +export async function saveAuth(data: any, expire = 0) { const authKey = `auth:${getRandomChars(32)}`; - await redis.set(authKey, data); + await redis.client.set(authKey, data); if (expire) { - await redis.expire(authKey, expire); + await redis.client.expire(authKey, expire); } return createSecureToken({ authKey }, secret()); @@ -61,11 +61,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri export async function canCreateWebsite({ user, grant }: Auth) { if (cloudMode) { - if (grant?.find(a => a === PERMISSIONS.websiteCreate)) { - return true; - } - - return false; + return !!grant?.find(a => a === PERMISSIONS.websiteCreate); } if (user.isAdmin) { @@ -121,11 +117,7 @@ export async function canDeleteReport(auth: Auth, report: Report) { export async function canCreateTeam({ user, grant }: Auth) { if (cloudMode) { - if (grant?.find(a => a === PERMISSIONS.teamCreate)) { - return true; - } - - return false; + return !!grant?.find(a => a === PERMISSIONS.teamCreate); } if (user.isAdmin) { diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 2b577bf2..11fd9f5c 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -3,67 +3,71 @@ import redis from '@umami/redis-client'; import { getSession, getUserById, getWebsiteById } from '../queries'; async function fetchWebsite(id): Promise { - return redis.getCache(`website:${id}`, () => getWebsiteById(id), 86400); + return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400); } async function storeWebsite(data) { const { id } = data; const key = `website:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteWebsite(id) { - return redis.deleteCache(`website:${id}`); + return redis.client.deleteCache(`website:${id}`); } async function fetchUser(id): Promise { - return redis.getCache(`user:${id}`, () => getUserById(id, { includePassword: true }), 86400); + return redis.client.getCache( + `user:${id}`, + () => getUserById(id, { includePassword: true }), + 86400, + ); } async function storeUser(data) { const { id } = data; const key = `user:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteUser(id) { - return redis.deleteCache(`user:${id}`); + return redis.client.deleteCache(`user:${id}`); } async function fetchSession(id) { - return redis.getCache(`session:${id}`, () => getSession(id), 86400); + return redis.client.getCache(`session:${id}`, () => getSession(id), 86400); } async function storeSession(data) { const { id } = data; const key = `session:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteSession(id) { - return redis.deleteCache(`session:${id}`); + return redis.client.deleteCache(`session:${id}`); } async function fetchUserBlock(userId: string) { const key = `user:block:${userId}`; - return redis.get(key); + return redis.client.get(key); } async function incrementUserBlock(userId: string) { const key = `user:block:${userId}`; - return redis.incr(key); + return redis.client.incr(key); } export default { diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index 5a12eb6a..b54f6d3a 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -1,6 +1,6 @@ -import redis from '@umami/redis-client'; import cors from 'cors'; import debug from 'debug'; +import redis from '@umami/redis-client'; import { getAuthToken, parseShareToken } from 'lib/auth'; import { ROLES } from 'lib/constants'; import { isUuid, secret } from 'lib/crypto'; @@ -47,15 +47,17 @@ export const useSession = createMiddleware(async (req, res, next) => { export const useAuth = createMiddleware(async (req, res, next) => { const token = getAuthToken(req); const payload = parseSecureToken(token, secret()); - const shareToken = await parseShareToken(req); + const shareToken = await parseShareToken(req as any); let user = null; const { userId, authKey, grant } = payload || {}; if (isUuid(userId)) { user = await getUserById(userId); - } else if (redis && authKey) { - user = await redis.get(authKey); + } else if (redis.enabled && authKey) { + const key = await redis.client.get(authKey); + + user = await getUserById(key.userId); } if (process.env.NODE_ENV === 'development') { diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts index 0946ae75..e1007b3c 100644 --- a/src/pages/api/auth/login.ts +++ b/src/pages/api/auth/login.ts @@ -1,6 +1,6 @@ import redis from '@umami/redis-client'; import debug from 'debug'; -import { setAuthKey } from 'lib/auth'; +import { saveAuth } from 'lib/auth'; import { secret } from 'lib/crypto'; import { useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, User } from 'lib/types'; @@ -52,8 +52,8 @@ export default async ( const user = await getUserByUsername(username, { includePassword: true }); if (user && checkPassword(password, user.password)) { - if (redis) { - const token = await setAuthKey(user); + if (redis.enabled) { + const token = await saveAuth({ userId: user.id }); return ok(res, { token, user }); } diff --git a/src/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts index e6222e49..715fda62 100644 --- a/src/pages/api/auth/logout.ts +++ b/src/pages/api/auth/logout.ts @@ -8,8 +8,8 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { await useAuth(req, res); if (req.method === 'POST') { - if (redis) { - await redis.del(getAuthToken(req)); + if (redis.enabled) { + await redis.client.del(getAuthToken(req)); } return ok(res); diff --git a/src/pages/api/auth/sso.ts b/src/pages/api/auth/sso.ts index a7992666..7b1eef60 100644 --- a/src/pages/api/auth/sso.ts +++ b/src/pages/api/auth/sso.ts @@ -3,13 +3,13 @@ import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, ok } from 'next-basics'; import redis from '@umami/redis-client'; -import { setAuthKey } from 'lib/auth'; +import { saveAuth } from 'lib/auth'; export default async (req: NextApiRequestAuth, res: NextApiResponse) => { await useAuth(req, res); - if (redis && req.auth.user) { - const token = await setAuthKey(req.auth.user, 86400); + if (redis.enabled && req.auth.user) { + const token = await saveAuth({ userId: req.auth.user.id }, 86400); return ok(res, { user: req.auth.user, token }); } diff --git a/yarn.lock b/yarn.lock index 9927b8ca..2c95f0d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2609,10 +2609,10 @@ chalk "^4.1.2" debug "^4.3.4" -"@umami/redis-client@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.17.0.tgz#a24db0cb561a9c18b0ecaf6a3342c28525abe1e5" - integrity sha512-rX0xB+QkhMoHnKcj8jzbp1CUMKB/qk2HaYWpNP0Stztkvw7wjFYXsLTLidW3t1a06H5qApx6mJvUjxefLUsX7w== +"@umami/redis-client@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.18.0.tgz#6a2315a878f2688dae162d93e88dfc4e097fc48e" + integrity sha512-uDuX5w7ydlOZWrq0h6fADG3XWOhto9fAqrUVu85FUhdijWoGlv5f8adaL8FAah5jD+/Byw2VyGQaZO4VhboEZw== dependencies: debug "^4.3.4" redis "^4.5.1" From ea28511b3c9bc5bec48e9612dfa9691dc74d1f8d Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 27 Nov 2023 22:20:47 -0800 Subject: [PATCH 23/62] fix redis always being enabled --- src/lib/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 11fd9f5c..69d749d0 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -82,5 +82,5 @@ export default { deleteSession, fetchUserBlock, incrementUserBlock, - enabled: !!redis, + enabled: !!redis.enabled, }; From 02c9e0115ef52fdfee80b337734280d53ea0a0aa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 28 Nov 2023 10:22:24 -0800 Subject: [PATCH 24/62] Updated user get. --- src/lib/middleware.ts | 6 +++--- src/queries/admin/user.ts | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index b54f6d3a..a6da65c0 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -3,7 +3,7 @@ import debug from 'debug'; import redis from '@umami/redis-client'; import { getAuthToken, parseShareToken } from 'lib/auth'; import { ROLES } from 'lib/constants'; -import { isUuid, secret } from 'lib/crypto'; +import { secret } from 'lib/crypto'; import { findSession } from 'lib/session'; import { badRequest, @@ -52,7 +52,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { let user = null; const { userId, authKey, grant } = payload || {}; - if (isUuid(userId)) { + if (userId) { user = await getUserById(userId); } else if (redis.enabled && authKey) { const key = await redis.client.get(authKey); @@ -61,7 +61,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { } if (process.env.NODE_ENV === 'development') { - log({ token, shareToken, payload, user, grant }); + log('useAuth:', { token, shareToken, payload, user, grant }); } if (!user?.id && !shareToken) { diff --git a/src/queries/admin/user.ts b/src/queries/admin/user.ts index b7319942..11f1c846 100644 --- a/src/queries/admin/user.ts +++ b/src/queries/admin/user.ts @@ -11,13 +11,17 @@ export interface GetUserOptions { } async function getUser( - where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput, + where: Prisma.UserWhereUniqueInput, options: GetUserOptions = {}, ): Promise { const { includePassword = false, showDeleted = false } = options; - return prisma.client.user.findFirst({ - where: { ...where, ...(showDeleted ? {} : { deletedAt: null }) }, + if (showDeleted) { + where.deletedAt = null; + } + + return prisma.client.user.findUnique({ + where, select: { id: true, username: true, @@ -28,8 +32,8 @@ async function getUser( }); } -export async function getUserById(userId: string, options: GetUserOptions = {}) { - return getUser({ id: userId }, options); +export async function getUserById(id: string, options: GetUserOptions = {}) { + return getUser({ id }, options); } export async function getUserByUsername(username: string, options: GetUserOptions = {}) { From e074394b134f7b04ca32fe7019b5f1978d1ee18b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 28 Nov 2023 11:03:55 -0800 Subject: [PATCH 25/62] Removed hostname lookup. --- src/pages/api/send.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index cf3004f3..1698d858 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -1,4 +1,3 @@ -import { Resolver } from 'dns/promises'; import ipaddr from 'ipaddr.js'; import isbot from 'isbot'; import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants'; @@ -78,14 +77,14 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { return ok(res); } - const { type, payload } = req.body; - await useValidate(schema, req, res); - if (await hasBlockedIp(req)) { + if (hasBlockedIp(req)) { return forbidden(res); } + const { type, payload } = req.body; + const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload; await useSession(req, res); @@ -143,28 +142,16 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { return methodNotAllowed(res); }; -async function hasBlockedIp(req: NextApiRequestCollect) { +function hasBlockedIp(req: NextApiRequestCollect) { const ignoreIps = process.env.IGNORE_IP; - const ignoreHostnames = process.env.IGNORE_HOSTNAME; - if (ignoreIps || ignoreHostnames) { + if (ignoreIps) { const ips = []; if (ignoreIps) { ips.push(...ignoreIps.split(',').map(n => n.trim())); } - if (ignoreHostnames) { - const resolver = new Resolver(); - const promises = ignoreHostnames - .split(',') - .map(n => resolver.resolve4(n.trim()).catch(() => {})); - - await Promise.all(promises).then(resolvedIps => { - ips.push(...resolvedIps.filter(n => n).flatMap(n => n as string[])); - }); - } - const clientIp = getIpAddress(req); return ips.find(ip => { @@ -177,8 +164,8 @@ async function hasBlockedIp(req: NextApiRequestCollect) { if (addr.kind() === range[0].kind() && addr.match(range)) return true; } - - return false; }); } + + return false; } From c5fc16f2415f2694a39f2b2ee50e09c3794be576 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Tue, 28 Nov 2023 22:44:11 -0800 Subject: [PATCH 26/62] Auto stash before merge of "dev" and "origin/dev" --- src/lib/middleware.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index a6da65c0..38a29876 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -8,6 +8,7 @@ import { findSession } from 'lib/session'; import { badRequest, createMiddleware, + forbidden, parseSecureToken, tooManyRequest, unauthorized, @@ -38,6 +39,9 @@ export const useSession = createMiddleware(async (req, res, next) => { if (e.message === 'Usage Limit.') { return tooManyRequest(res, e.message); } + if (e.message.startsWith('Website not found:')) { + return forbidden(res, e.message); + } return badRequest(res, e.message); } From c24a0c87dcea1443284bd0c261175214b6b2323a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 29 Nov 2023 14:03:10 -0800 Subject: [PATCH 27/62] Updated login check. --- src/app/(main)/App.tsx | 13 ++++++++++-- src/components/hooks/index.js | 2 +- src/components/hooks/useLogin.ts | 22 +++++++++++++++++++ src/components/hooks/useRequireLogin.ts | 28 ------------------------- src/index.ts | 2 +- src/lib/middleware.ts | 2 +- 6 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 src/components/hooks/useLogin.ts delete mode 100644 src/components/hooks/useRequireLogin.ts diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 01da9a6a..4b093165 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,14 +1,23 @@ 'use client'; +import { Loading } from 'react-basics'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; +import { useLogin, useConfig } from 'components/hooks'; import UpdateNotice from './UpdateNotice'; -import { useRequireLogin, useConfig } from 'components/hooks'; export function App({ children }) { - const { user } = useRequireLogin(); + const { user, isLoading, error } = useLogin(); const config = useConfig(); const pathname = usePathname(); + if (isLoading) { + return ; + } + + if (error) { + window.location.href = `${process.env.basePath || ''}/login`; + } + if (!user || !config) { return null; } diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js index 697d54c3..b851eeb7 100644 --- a/src/components/hooks/index.js +++ b/src/components/hooks/index.js @@ -13,7 +13,7 @@ export * from './useMessages'; export * from './useNavigation'; export * from './useReport'; export * from './useReports'; -export * from './useRequireLogin'; +export * from './useLogin'; export * from './useShareToken'; export * from './useSticky'; export * from './useTheme'; diff --git a/src/components/hooks/useLogin.ts b/src/components/hooks/useLogin.ts new file mode 100644 index 00000000..a4ac9d3b --- /dev/null +++ b/src/components/hooks/useLogin.ts @@ -0,0 +1,22 @@ +import useApi from 'components/hooks/useApi'; +import useUser from 'components/hooks/useUser'; + +export function useLogin() { + const { get, useQuery } = useApi(); + const { user, setUser } = useUser(); + + const query = useQuery({ + queryKey: ['login'], + queryFn: async () => { + const data = await get('/auth/verify'); + + setUser(data); + + return data; + }, + }); + + return { user, ...query }; +} + +export default useLogin; diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts deleted file mode 100644 index 68de411b..00000000 --- a/src/components/hooks/useRequireLogin.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from 'react'; -import useApi from 'components/hooks/useApi'; -import useUser from 'components/hooks/useUser'; - -export function useRequireLogin() { - const { get } = useApi(); - const { user, setUser } = useUser(); - - useEffect(() => { - async function loadUser() { - try { - const data = await get('/auth/verify'); - - setUser(data); - } catch { - window.location.href = `${process.env.basePath || ''}/login`; - } - } - - if (!user) { - loadUser(); - } - }, [user]); - - return { user }; -} - -export default useRequireLogin; diff --git a/src/index.ts b/src/index.ts index 01ceb7d1..de555051 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ export * from 'components/hooks/useFormat'; export * from 'components/hooks/useLocale'; export * from 'components/hooks/useMessages'; export * from 'components/hooks/useNavigation'; -export * from 'components/hooks/useRequireLogin'; +export * from 'components/hooks/useLogin'; export * from 'components/hooks/useShareToken'; export * from 'components/hooks/useSticky'; export * from 'components/hooks/useTheme'; diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index a6da65c0..865a6f3d 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -57,7 +57,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { } else if (redis.enabled && authKey) { const key = await redis.client.get(authKey); - user = await getUserById(key.userId); + user = await getUserById(key?.userId); } if (process.env.NODE_ENV === 'development') { From 91d385d8387b0421e478dd65ede9d918c53653a5 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Nov 2023 14:54:45 -0800 Subject: [PATCH 28/62] Added frame-src to CSP. --- next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/next.config.js b/next.config.js index a1e30c36..eaaf8fe7 100644 --- a/next.config.js +++ b/next.config.js @@ -9,6 +9,7 @@ const contentSecurityPolicy = [ `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, `style-src 'self' 'unsafe-inline'`, `connect-src 'self' api.umami.is`, + `frame-src *`, ]; const cspHeader = (values = []) => ({ From a224a3caf36352a60f432d8b1dbe28d8a9a3ca0e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Nov 2023 21:58:11 -0800 Subject: [PATCH 29/62] Upgrade prisma. Lint fixes. --- package.json | 8 +-- .../settings/websites/WebsiteSettings.js | 2 +- .../(main)/websites/[id]/WebsiteChartList.js | 3 +- src/components/common/ErrorMessage.tsx | 2 +- src/components/common/FilterLink.tsx | 6 +-- src/components/common/HamburgerButton.tsx | 3 +- src/components/icons.ts | 2 +- .../analytics/eventData/getEventDataStats.ts | 2 +- src/queries/analytics/getRealtimeData.ts | 12 ++--- src/queries/analytics/reports/getInsights.ts | 13 +++-- yarn.lock | 54 +++++++++---------- 11 files changed, 56 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index cf0398f7..ba85fb3e 100644 --- a/package.json +++ b/package.json @@ -63,11 +63,11 @@ "dependencies": { "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", - "@prisma/client": "5.4.2", + "@prisma/client": "5.6.0", "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.5.0", + "@umami/prisma-client": "^0.7.0", "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", @@ -97,9 +97,9 @@ "next-basics": "^0.37.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", - "prisma": "5.4.2", + "prisma": "5.6.0", "react": "^18.2.0", - "react-basics": "^0.107.0", + "react-basics": "^0.109.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.js index 71d5fe23..82b38048 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.js @@ -34,7 +34,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl const handleReset = async value => { if (value === 'delete') { - await router.push('/settings/websites'); + router.push('/settings/websites'); } else if (value === 'reset') { showSuccess(); } diff --git a/src/app/(main)/websites/[id]/WebsiteChartList.js b/src/app/(main)/websites/[id]/WebsiteChartList.js index 23764dbb..bc2439de 100644 --- a/src/app/(main)/websites/[id]/WebsiteChartList.js +++ b/src/app/(main)/websites/[id]/WebsiteChartList.js @@ -1,4 +1,4 @@ -import { Button, Text, Icon } from 'react-basics'; +import { Button, Text, Icon, Icons } from 'react-basics'; import { useMemo } from 'react'; import { firstBy } from 'thenby'; import Link from 'next/link'; @@ -7,7 +7,6 @@ import useDashboard from 'store/dashboard'; import WebsiteHeader from './WebsiteHeader'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; import { useMessages, useLocale } from 'components/hooks'; -import Icons from 'components/icons'; export default function WebsiteChartList({ websites, showCharts, limit }) { const { formatMessage, labels } = useMessages(); diff --git a/src/components/common/ErrorMessage.tsx b/src/components/common/ErrorMessage.tsx index f8129c6b..0deb6f92 100644 --- a/src/components/common/ErrorMessage.tsx +++ b/src/components/common/ErrorMessage.tsx @@ -7,7 +7,7 @@ export function ErrorMessage() { return (
- + {formatMessage(messages.error)} diff --git a/src/components/common/FilterLink.tsx b/src/components/common/FilterLink.tsx index f91e1459..a9030227 100644 --- a/src/components/common/FilterLink.tsx +++ b/src/components/common/FilterLink.tsx @@ -10,9 +10,9 @@ import styles from './FilterLink.module.css'; export interface FilterLinkProps { id: string; value: string; - label: string; - externalUrl: string; - className: string; + label?: string; + externalUrl?: string; + className?: string; children: ReactNode; } diff --git a/src/components/common/HamburgerButton.tsx b/src/components/common/HamburgerButton.tsx index 380392c8..5a81f3a3 100644 --- a/src/components/common/HamburgerButton.tsx +++ b/src/components/common/HamburgerButton.tsx @@ -1,7 +1,6 @@ -import { Button, Icon } from 'react-basics'; +import { Button, Icon, Icons } from 'react-basics'; import { useState } from 'react'; import MobileMenu from './MobileMenu'; -import Icons from 'components/icons'; export function HamburgerButton({ menuItems }: { menuItems: any[] }) { const [active, setActive] = useState(false); diff --git a/src/components/icons.ts b/src/components/icons.ts index 8eb1f8b0..01d7caf5 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -22,7 +22,7 @@ import User from 'assets/user.svg'; import Users from 'assets/users.svg'; import Visitor from 'assets/visitor.svg'; -const icons: any = { +const icons = { ...Icons, AddUser, Bars, diff --git a/src/queries/analytics/eventData/getEventDataStats.ts b/src/queries/analytics/eventData/getEventDataStats.ts index b940e9c4..39afa1ae 100644 --- a/src/queries/analytics/eventData/getEventDataStats.ts +++ b/src/queries/analytics/eventData/getEventDataStats.ts @@ -45,7 +45,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { async function clickhouseQuery( websiteId: string, filters: QueryFilters, -): Promise<{ events: number; fields: number; records: number }> { +): Promise<{ events: number; fields: number; records: number }[]> { const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, filters); diff --git a/src/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts index 8786ab13..337e7475 100644 --- a/src/queries/analytics/getRealtimeData.ts +++ b/src/queries/analytics/getRealtimeData.ts @@ -2,15 +2,15 @@ import { md5 } from 'next-basics'; import { getSessions, getEvents } from 'queries/index'; import { EVENT_TYPE } from 'lib/constants'; -export async function getRealtimeData(websiteId, time) { +export async function getRealtimeData(websiteId: string, startDate: Date) { const [pageviews, sessions, events] = await Promise.all([ - getEvents(websiteId, time, EVENT_TYPE.pageView), - getSessions(websiteId, time), - getEvents(websiteId, time, EVENT_TYPE.customEvent), + getEvents(websiteId, startDate, EVENT_TYPE.pageView), + getSessions(websiteId, startDate), + getEvents(websiteId, startDate, EVENT_TYPE.customEvent), ]); - const decorate = (id, data) => { - return data.map(props => ({ + const decorate = (id: string, data: any[]) => { + return data.map((props: { [key: string]: any }) => ({ ...props, __id: md5(id, ...Object.values(props)), __type: id, diff --git a/src/queries/analytics/reports/getInsights.ts b/src/queries/analytics/reports/getInsights.ts index c5fac48b..a5b1b773 100644 --- a/src/queries/analytics/reports/getInsights.ts +++ b/src/queries/analytics/reports/getInsights.ts @@ -83,10 +83,17 @@ async function clickhouseQuery( limit 500 `, params, - ); + ).then(a => { + return Object.values(a).map(a => { + return { + x: a.x, + y: Number(a.y), + }; + }); + }); } -function parseFields(fields) { +function parseFields(fields: any[]) { const query = fields.reduce( (arr, field) => { const { name } = field; @@ -99,7 +106,7 @@ function parseFields(fields) { return query.join(',\n'); } -function parseGroupBy(fields) { +function parseGroupBy(fields: { name: any }[]) { if (!fields.length) { return ''; } diff --git a/yarn.lock b/yarn.lock index a259a0b2..1e405135 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,22 +1941,22 @@ "@parcel/watcher-win32-ia32" "2.3.0" "@parcel/watcher-win32-x64" "2.3.0" -"@prisma/client@5.4.2": - version "5.4.2" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.4.2.tgz#786f9c1d8f06d955933004ac638d14da4bf14025" - integrity sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA== +"@prisma/client@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.6.0.tgz#1c15932250d5658fe0127e62faf4ecd96a877259" + integrity sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug== dependencies: - "@prisma/engines-version" "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + "@prisma/engines-version" "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" -"@prisma/engines-version@5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574": - version "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz#ff14f2926890edee47e8f1d08df7b4f392ee34bf" - integrity sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA== +"@prisma/engines-version@5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee": + version "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz#57b003ab5e1ea1523b5cdd7f06b24ebcf5c7fd8c" + integrity sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw== -"@prisma/engines@5.4.2": - version "5.4.2" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f" - integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g== +"@prisma/engines@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.6.0.tgz#82c445aa10633bbc0388aa2d6e411a0bd94c9439" + integrity sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw== "@prisma/extension-read-replicas@^0.3.0": version "0.3.0" @@ -2601,10 +2601,10 @@ "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" -"@umami/prisma-client@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf" - integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg== +"@umami/prisma-client@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.7.0.tgz#f9de0dfc861c9ba6379c0789e012d4effa65f1ef" + integrity sha512-70Azr4aAYMU6c+Lx69bjumNnKWgOFoq2PuYmp+T2kfCDhMyMLXTLDVD5ArDrwJMl1gWsgvpnumxCirYy+6KhGg== dependencies: chalk "^4.1.2" debug "^4.3.4" @@ -6324,7 +6324,7 @@ next-basics@^0.37.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@^13.5.3: +next@13.5.6: version "13.5.6" resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== @@ -7377,12 +7377,12 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -prisma@5.4.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.4.2.tgz#7eac9276439ec7073ec697c6c0dfa259d96e955e" - integrity sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg== +prisma@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.6.0.tgz#ae2c27fdfb4d53be7f7dafb50d6b8b7f55c93aa5" + integrity sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A== dependencies: - "@prisma/engines" "5.4.2" + "@prisma/engines" "5.6.0" promise.series@^0.2.0: version "0.2.0" @@ -7476,10 +7476,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.107.0: - version "0.107.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3" - integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A== +react-basics@^0.109.0: + version "0.109.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.109.0.tgz#9c1f41ebf6abbcf67f7dd11a16a7fb5e3aedd38d" + integrity sha512-n955CwqIeQ/sTMxxvbtYpWtBWS07Rg39zrNKeUYN/JoCIq0YbbZiZDALAhh1Out+qsIe62NoOFA7JtHzk1EkHQ== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" From 3bb82aebd8cb68a6e172d54393bd12012b0532b7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Nov 2023 22:24:43 -0800 Subject: [PATCH 30/62] Fixed redis lookup. --- src/lib/middleware.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index 5796009b..91fb6c7c 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -61,7 +61,9 @@ export const useAuth = createMiddleware(async (req, res, next) => { } else if (redis.enabled && authKey) { const key = await redis.client.get(authKey); - user = await getUserById(key?.userId); + if (key?.userId) { + user = await getUserById(key.userId); + } } if (process.env.NODE_ENV === 'development') { From d0e1912fafdf097f48081e4eca6876031f077834 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Nov 2023 23:40:58 -0800 Subject: [PATCH 31/62] Updated CSP rules. --- next.config.js | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/next.config.js b/next.config.js index eaaf8fe7..c73790b8 100644 --- a/next.config.js +++ b/next.config.js @@ -9,38 +9,21 @@ const contentSecurityPolicy = [ `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, `style-src 'self' 'unsafe-inline'`, `connect-src 'self' api.umami.is`, - `frame-src *`, + `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, ]; -const cspHeader = (values = []) => ({ - key: 'Content-Security-Policy', - value: values - .join(';') - .replace(/\s{2,}/g, ' ') - .trim(), -}); - const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, { - key: 'X-Frame-Options', - value: 'SAMEORIGIN', + key: 'Content-Security-Policy', + value: contentSecurityPolicy + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), }, - cspHeader(contentSecurityPolicy), -]; - -const shareHeaders = [ - { - key: 'X-DNS-Prefetch-Control', - value: 'on', - }, - cspHeader([ - ...contentSecurityPolicy, - `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, - ]), ]; if (process.env.FORCE_SSL) { @@ -142,10 +125,6 @@ const config = { source: '/:path*', headers, }, - { - source: '/share/:path*', - headers: shareHeaders, - }, ]; }, async rewrites() { From b314cc88f567c34f33ce202844384897f727953b Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 1 Dec 2023 12:59:51 -0800 Subject: [PATCH 32/62] update @umami/prisma-client to 0.8.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ba85fb3e..4d244cdc 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.7.0", + "@umami/prisma-client": "^0.8.0", "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", diff --git a/yarn.lock b/yarn.lock index 1e405135..2a8de5f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2601,10 +2601,10 @@ "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" -"@umami/prisma-client@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.7.0.tgz#f9de0dfc861c9ba6379c0789e012d4effa65f1ef" - integrity sha512-70Azr4aAYMU6c+Lx69bjumNnKWgOFoq2PuYmp+T2kfCDhMyMLXTLDVD5ArDrwJMl1gWsgvpnumxCirYy+6KhGg== +"@umami/prisma-client@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.8.0.tgz#9f866c813b15b7ab0e7632506316bf1e5d2e74cc" + integrity sha512-ix3/75CO3eVlf1Rg0cUIjoHDFJV7nxx5sSh1NnvbjyGn8EsTpZ7fVYF874w8+ENQsaKFIMftUSGGiwvgxaZN3g== dependencies: chalk "^4.1.2" debug "^4.3.4" From e068ac0dd98eb5874c6bddf4842c2bcfc1714b5f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 1 Dec 2023 16:02:50 -0800 Subject: [PATCH 33/62] Fixed insights report. --- package.json | 2 +- src/app/(main)/reports/insights/InsightsTable.js | 4 ++-- src/components/hooks/useFormat.ts | 10 +++++----- src/pages/api/reports/insights.ts | 2 +- src/queries/analytics/reports/getInsights.ts | 5 +++-- yarn.lock | 8 ++++---- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index ba85fb3e..addfebdf 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.6.0", "react": "^18.2.0", - "react-basics": "^0.109.0", + "react-basics": "^0.110.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/src/app/(main)/reports/insights/InsightsTable.js b/src/app/(main)/reports/insights/InsightsTable.js index 05d38042..4194fee8 100644 --- a/src/app/(main)/reports/insights/InsightsTable.js +++ b/src/app/(main)/reports/insights/InsightsTable.js @@ -37,10 +37,10 @@ export function InsightsTable() { width="100px" alignment="end" > - {row => row.visitors.toLocaleString()} + {row => row?.visitors?.toLocaleString()} - {row => row.views.toLocaleString()} + {row => row?.views?.toLocaleString()} ); diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index c1160162..f804eb72 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -9,23 +9,23 @@ export function useFormat() { const { locale } = useLocale(); const countryNames = useCountryNames(locale); - const formatBrowser = (value: string) => { + const formatBrowser = (value: string): string => { return BROWSERS[value] || value; }; - const formatCountry = (value: string) => { + const formatCountry = (value: string): string => { return countryNames[value] || value; }; - const formatRegion = (value: string) => { + const formatRegion = (value: string): string => { return regions[value] ? regions[value] : value; }; - const formatDevice = (value: string) => { + const formatDevice = (value: string): string => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value: string, type: string) => { + const formatValue = (value: string, type: string): string => { switch (type) { case 'browser': return formatBrowser(value); diff --git a/src/pages/api/reports/insights.ts b/src/pages/api/reports/insights.ts index 4344422a..c70d218e 100644 --- a/src/pages/api/reports/insights.ts +++ b/src/pages/api/reports/insights.ts @@ -55,7 +55,7 @@ const schema = { }), }; -function convertFilters(filters) { +function convertFilters(filters: any[]) { return filters.reduce((obj, { name, ...value }) => { obj[name] = value; diff --git a/src/queries/analytics/reports/getInsights.ts b/src/queries/analytics/reports/getInsights.ts index a5b1b773..282ed755 100644 --- a/src/queries/analytics/reports/getInsights.ts +++ b/src/queries/analytics/reports/getInsights.ts @@ -86,8 +86,9 @@ async function clickhouseQuery( ).then(a => { return Object.values(a).map(a => { return { - x: a.x, - y: Number(a.y), + ...a, + views: Number(a.views), + visitors: Number(a.visitors), }; }); }); diff --git a/yarn.lock b/yarn.lock index 1e405135..6584c9ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7476,10 +7476,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.109.0: - version "0.109.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.109.0.tgz#9c1f41ebf6abbcf67f7dd11a16a7fb5e3aedd38d" - integrity sha512-n955CwqIeQ/sTMxxvbtYpWtBWS07Rg39zrNKeUYN/JoCIq0YbbZiZDALAhh1Out+qsIe62NoOFA7JtHzk1EkHQ== +react-basics@^0.110.0: + version "0.110.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.110.0.tgz#7b7689edcb96b973528abc91b964345edc6e000a" + integrity sha512-3XQx5hR0zTuEAxDDHyaqS2GfXOowb0Lri8ay65kjGUd7RmvdpnIr62MsrAwo62rtTwtzRzsJXHJCR5CKQ/D3rQ== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" From b578162cb6399b0ed461071246475aec83fed534 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 1 Dec 2023 20:27:59 -0800 Subject: [PATCH 34/62] Refactored useQuery functions. --- src/app/(main)/console/TestConsole.js | 5 +++- src/app/(main)/dashboard/Dashboard.js | 7 ++--- src/app/(main)/dashboard/DashboardEdit.js | 5 +++- src/app/(main)/reports/ReportsDataTable.tsx | 8 +++--- .../(main)/reports/[id]/FilterSelectForm.js | 10 +++---- src/app/(main)/reports/[id]/ReportDetails.js | 5 +++- .../reports/event-data/EventDataParameters.js | 10 +++---- .../(main)/settings/teams/TeamsDataTable.js | 11 +++++--- .../(main)/settings/teams/[id]/TeamMembers.js | 10 +++---- .../settings/teams/[id]/TeamSettings.js | 10 +++---- .../settings/teams/[id]/TeamWebsiteAddForm.js | 5 +++- .../settings/teams/[id]/TeamWebsites.js | 10 +++---- src/app/(main)/settings/users/UserWebsites.js | 8 +++--- .../(main)/settings/users/UsersDataTable.js | 7 +++-- .../settings/users/[id]/UserSettings.js | 10 +++---- .../settings/websites/WebsiteSettings.js | 10 +++++-- .../settings/websites/WebsitesDataTable.tsx | 10 +++---- src/app/(main)/websites/[id]/WebsiteChart.js | 8 +++--- .../(main)/websites/[id]/WebsiteMetricsBar.js | 8 +++--- .../[id]/event-data/EventDataMetricsBar.js | 8 +++--- .../[id]/event-data/WebsiteEventData.js | 10 +++---- .../(main)/websites/[id]/realtime/Realtime.js | 16 +++++------ .../websites/[id]/realtime/RealtimeHome.js | 5 +++- src/components/common/DataTable.tsx | 26 +++++++----------- src/components/common/FilterLink.tsx | 2 +- src/components/hooks/useFilterQuery.ts | 27 ++++++++++--------- src/components/hooks/useReports.ts | 8 +++--- src/components/hooks/useShareToken.ts | 11 +++++--- src/components/hooks/useWebsite.ts | 4 ++- src/components/input/WebsiteSelect.js | 5 +++- src/components/metrics/ActiveUsers.js | 14 +++++----- src/components/metrics/EventsChart.js | 25 +++++++++-------- src/components/metrics/MetricsTable.js | 12 +++++---- 33 files changed, 179 insertions(+), 151 deletions(-) diff --git a/src/app/(main)/console/TestConsole.js b/src/app/(main)/console/TestConsole.js index b88bfd77..7aae09b2 100644 --- a/src/app/(main)/console/TestConsole.js +++ b/src/app/(main)/console/TestConsole.js @@ -14,7 +14,10 @@ import styles from './TestConsole.module.css'; export function TestConsole({ websiteId }) { const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); + const { data, isLoading, error } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites'), + }); const { router } = useNavigation(); function handleChange(value) { diff --git a/src/app/(main)/dashboard/Dashboard.js b/src/app/(main)/dashboard/Dashboard.js index 5fb65f23..6f0659bb 100644 --- a/src/app/(main)/dashboard/Dashboard.js +++ b/src/app/(main)/dashboard/Dashboard.js @@ -20,9 +20,10 @@ export function Dashboard() { const { get, useQuery } = useApi(); const { page, handlePageChange } = useApiFilter(); const pageSize = 10; - const { data: result, isLoading } = useQuery(['websites', page, pageSize], () => - get('/websites', { includeTeams: 1, page, pageSize }), - ); + const { data: result, isLoading } = useQuery({ + queryKey: ['websites', page, pageSize], + queryFn: () => get('/websites', { includeTeams: 1, page, pageSize }), + }); const { data, count } = result || {}; const hasData = data && data?.length !== 0; diff --git a/src/app/(main)/dashboard/DashboardEdit.js b/src/app/(main)/dashboard/DashboardEdit.js index 3af33867..0a8734e8 100644 --- a/src/app/(main)/dashboard/DashboardEdit.js +++ b/src/app/(main)/dashboard/DashboardEdit.js @@ -17,7 +17,10 @@ export function DashboardEdit() { const { formatMessage, labels } = useMessages(); const [order, setOrder] = useState(websiteOrder || []); const { get, useQuery } = useApi(); - const { data: result } = useQuery(['websites'], () => get('/websites', { includeTeams: 1 })); + const { data: result } = useQuery({ + queryKey: ['websites'], + queryFn: () => get('/websites', { includeTeams: 1 }), + }); const { data: websites } = result || {}; const ordered = useMemo(() => { diff --git a/src/app/(main)/reports/ReportsDataTable.tsx b/src/app/(main)/reports/ReportsDataTable.tsx index 0ca853dc..eeef203b 100644 --- a/src/app/(main)/reports/ReportsDataTable.tsx +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -8,9 +8,11 @@ import useCache from 'store/cache'; export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { const { get } = useApi(); const modified = useCache(state => (state as any)?.reports); - const queryResult = useFilterQuery(['reports', { websiteId, modified }], params => - get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), - ); + const queryResult = useFilterQuery({ + queryKey: ['reports', { websiteId, modified }], + queryFn: (params: any) => + get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), + }); return ( diff --git a/src/app/(main)/reports/[id]/FilterSelectForm.js b/src/app/(main)/reports/[id]/FilterSelectForm.js index 9ad4cd93..8457b7ff 100644 --- a/src/app/(main)/reports/[id]/FilterSelectForm.js +++ b/src/app/(main)/reports/[id]/FilterSelectForm.js @@ -8,16 +8,16 @@ import { useApi } from 'components/hooks'; function useValues(websiteId, type) { const now = Date.now(); const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['websites:values', websiteId, type], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['websites:values', websiteId, type], + queryFn: () => get(`/websites/${websiteId}/values`, { type, startAt: +subDays(now, 90), endAt: now, }), - { enabled: !!(websiteId && type) }, - ); + enabled: !!(websiteId && type), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/reports/[id]/ReportDetails.js b/src/app/(main)/reports/[id]/ReportDetails.js index 8605ffb3..df91719a 100644 --- a/src/app/(main)/reports/[id]/ReportDetails.js +++ b/src/app/(main)/reports/[id]/ReportDetails.js @@ -14,7 +14,10 @@ const reports = { export default function ReportDetails({ reportId }) { const { get, useQuery } = useApi(); - const { data: report } = useQuery(['reports', reportId], () => get(`/reports/${reportId}`)); + const { data: report } = useQuery({ + queryKey: ['reports', reportId], + queryFn: () => get(`/reports/${reportId}`), + }); if (!report) { return null; diff --git a/src/app/(main)/reports/event-data/EventDataParameters.js b/src/app/(main)/reports/event-data/EventDataParameters.js index 6b9a0344..ac90fc2a 100644 --- a/src/app/(main)/reports/event-data/EventDataParameters.js +++ b/src/app/(main)/reports/event-data/EventDataParameters.js @@ -12,16 +12,16 @@ import styles from './EventDataParameters.module.css'; function useFields(websiteId, startDate, endDate) { const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['fields', websiteId, startDate, endDate], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['fields', websiteId, startDate, endDate], + queryFn: () => get('/reports/event-data', { websiteId, startAt: +startDate, endAt: +endDate, }), - { enabled: !!(websiteId && startDate && endDate) }, - ); + enabled: !!(websiteId && startDate && endDate), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/settings/teams/TeamsDataTable.js b/src/app/(main)/settings/teams/TeamsDataTable.js index 164838f9..a8c9e21d 100644 --- a/src/app/(main)/settings/teams/TeamsDataTable.js +++ b/src/app/(main)/settings/teams/TeamsDataTable.js @@ -8,10 +8,13 @@ import useCache from 'store/cache'; export function TeamsDataTable() { const { get } = useApi(); const modified = useCache(state => state?.teams); - const queryResult = useFilterQuery(['teams', { modified }], params => { - return get(`/teams`, { - ...params, - }); + const queryResult = useFilterQuery({ + queryKey: ['teams', { modified }], + queryFn: params => { + return get(`/teams`, { + ...params, + }); + }, }); return ( diff --git a/src/app/(main)/settings/teams/[id]/TeamMembers.js b/src/app/(main)/settings/teams/[id]/TeamMembers.js index fb31b6fa..1b0c0d18 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembers.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembers.js @@ -7,15 +7,15 @@ import useCache from 'store/cache'; export function TeamMembers({ teamId, readOnly }) { const { get } = useApi(); const modified = useCache(state => state?.['team:members']); - const queryResult = useFilterQuery( - ['team:members', { teamId, modified }], - params => { + const queryResult = useFilterQuery({ + queryKey: ['team:members', { teamId, modified }], + queryFn: params => { return get(`/teams/${teamId}/users`, { ...params, }); }, - { enabled: !!teamId }, - ); + enabled: !!teamId, + }); return ( <> diff --git a/src/app/(main)/settings/teams/[id]/TeamSettings.js b/src/app/(main)/settings/teams/[id]/TeamSettings.js index 8ec0ad85..d7065f97 100644 --- a/src/app/(main)/settings/teams/[id]/TeamSettings.js +++ b/src/app/(main)/settings/teams/[id]/TeamSettings.js @@ -17,15 +17,15 @@ export function TeamSettings({ teamId }) { const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data, isLoading } = useQuery( - ['team', teamId], - () => { + const { data, isLoading } = useQuery({ + queryKey: ['team', teamId], + queryFn: () => { if (teamId) { return get(`/teams/${teamId}`); } }, - { cacheTime: 0 }, - ); + cacheTime: 0, + }); const canEdit = data?.teamUser?.find( ({ userId, role }) => role === ROLES.teamOwner && userId === user.id, ); diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js index c83ec3d0..004ae54f 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js @@ -12,7 +12,10 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); - const { data: websites, isLoading } = useQuery(['websites'], () => get('/websites')); + const { data: websites, isLoading } = useQuery({ + queryKey: ['websites'], + queryFn: () => get('/websites'), + }); const [selected, setSelected] = useState([]); const hasData = websites && websites.data.length > 0; diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsites.js b/src/app/(main)/settings/teams/[id]/TeamWebsites.js index 9e76ffab..af167b89 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsites.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsites.js @@ -13,15 +13,15 @@ export function TeamWebsites({ teamId }) { const { user } = useUser(); const { get } = useApi(); const modified = useCache(state => state?.['team:websites']); - const queryResult = useFilterQuery( - ['team:websites', { teamId, modified }], - params => { + const queryResult = useFilterQuery({ + queryKey: ['team:websites', { teamId, modified }], + queryFn: params => { return get(`/teams/${teamId}/websites`, { ...params, }); }, - { enabled: !!user }, - ); + enabled: !!user, + }); const handleChange = () => { queryResult.refetch(); diff --git a/src/app/(main)/settings/users/UserWebsites.js b/src/app/(main)/settings/users/UserWebsites.js index 18b5f1a7..cd53c512 100644 --- a/src/app/(main)/settings/users/UserWebsites.js +++ b/src/app/(main)/settings/users/UserWebsites.js @@ -7,15 +7,15 @@ export function UserWebsites({ userId }) { const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = useApiFilter(); const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery( - ['user:websites', userId, filter, page, pageSize], - () => + const { data, isLoading, error } = useQuery({ + queryKey: ['user:websites', userId, filter, page, pageSize], + queryFn: () => get(`/users/${userId}/websites`, { filter, page, pageSize, }), - ); + }); const hasData = data && data.length !== 0; return ( diff --git a/src/app/(main)/settings/users/UsersDataTable.js b/src/app/(main)/settings/users/UsersDataTable.js index 154e37ad..91125309 100644 --- a/src/app/(main)/settings/users/UsersDataTable.js +++ b/src/app/(main)/settings/users/UsersDataTable.js @@ -9,10 +9,9 @@ import useCache from 'store/cache'; export function UsersDataTable() { const { get } = useApi(); const modified = useCache(state => state?.users); - const queryResult = useFilterQuery(['users', { modified }], params => { - return get(`/users`, { - ...params, - }); + const queryResult = useFilterQuery({ + queryKey: ['users', { modified }], + queryFn: params => get(`/users`, params), }); return ( diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.js index ea340ab7..f635bb05 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.js @@ -14,15 +14,15 @@ export function UserSettings({ userId }) { const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data, isLoading } = useQuery( - ['user', userId], - () => { + const { data, isLoading } = useQuery({ + queryKey: ['user', userId], + queryFn: () => { if (userId) { return get(`/users/${userId}`); } }, - { cacheTime: 0 }, - ); + cacheTime: 0, + }); const handleSave = data => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.js index 82b38048..79ba08fc 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.js @@ -1,6 +1,6 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics'; +import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import PageHeader from 'components/layout/PageHeader'; @@ -16,7 +16,9 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data } = useQuery(['website', websiteId], () => get(`/websites/${websiteId}`), { + const { data, isLoading } = useQuery({ + queryKey: ['website', websiteId], + queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, cacheTime: 0, }); @@ -46,6 +48,10 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl } }, [data]); + if (isLoading || !values) { + return ; + } + return ( <> diff --git a/src/app/(main)/settings/websites/WebsitesDataTable.tsx b/src/app/(main)/settings/websites/WebsitesDataTable.tsx index fc6dd0c0..4d55e047 100644 --- a/src/app/(main)/settings/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesDataTable.tsx @@ -21,17 +21,17 @@ function useWebsites(userId: string, { includeTeams, onlyTeams }) { const { get } = useApi(); const modified = useCache((state: any) => state?.websites); - return useFilterQuery( - ['websites', { includeTeams, onlyTeams, modified }], - (params: any) => { + return useFilterQuery({ + queryKey: ['websites', { includeTeams, onlyTeams, modified }], + queryFn: (params: any) => { return get(`/users/${userId}/websites`, { includeTeams, onlyTeams, ...params, }); }, - { enabled: !!userId }, - ); + enabled: !!userId, + }); } export function WebsitesDataTable({ diff --git a/src/app/(main)/websites/[id]/WebsiteChart.js b/src/app/(main)/websites/[id]/WebsiteChart.js index d05ff422..15a7525f 100644 --- a/src/app/(main)/websites/[id]/WebsiteChart.js +++ b/src/app/(main)/websites/[id]/WebsiteChart.js @@ -12,12 +12,12 @@ export function WebsiteChart({ websiteId }) { } = useNavigation(); const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery( - [ + const { data, isLoading } = useQuery({ + queryKey: [ 'websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country, region, city, title }, ], - () => + queryFn: () => get(`/websites/${websiteId}/pageviews`, { startAt: +startDate, endAt: +endDate, @@ -33,7 +33,7 @@ export function WebsiteChart({ websiteId }) { city, title, }), - ); + }); const chartData = useMemo(() => { if (data) { diff --git a/src/app/(main)/websites/[id]/WebsiteMetricsBar.js b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js index 0dd6a4e2..d840c7b6 100644 --- a/src/app/(main)/websites/[id]/WebsiteMetricsBar.js +++ b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js @@ -17,12 +17,12 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { query: { url, referrer, title, os, browser, device, country, region, city }, } = useNavigation(); - const { data, error, isLoading, isFetched } = useQuery( - [ + const { data, error, isLoading, isFetched } = useQuery({ + queryKey: [ 'websites:stats', { websiteId, modified, url, referrer, title, os, browser, device, country, region, city }, ], - () => + queryFn: () => get(`/websites/${websiteId}/stats`, { startAt: +startDate, endAt: +endDate, @@ -36,7 +36,7 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { region, city, }), - ); + }); const { pageviews, uniques, bounces, totaltime } = data || {}; const num = Math.min(data && uniques.value, data && bounces.value); diff --git a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js index 5be19185..b02e166c 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js @@ -11,15 +11,15 @@ export function EventDataMetricsBar({ websiteId }) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate, modified } = dateRange; - const { data, error, isLoading, isFetched } = useQuery( - ['event-data:stats', { websiteId, startDate, endDate, modified }], - () => + const { data, error, isLoading, isFetched } = useQuery({ + queryKey: ['event-data:stats', { websiteId, startDate, endDate, modified }], + queryFn: () => get(`/event-data/stats`, { websiteId, startAt: +startDate, endAt: +endDate, }), - ); + }); return (
diff --git a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js index b5982e32..b67ee95e 100644 --- a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js +++ b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js @@ -10,17 +10,17 @@ function useData(websiteId, event) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['event-data:events', { websiteId, startDate, endDate, event }], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['event-data:events', { websiteId, startDate, endDate, event }], + queryFn: () => get('/event-data/events', { websiteId, startAt: +startDate, endAt: +endDate, event, }), - { enabled: !!(websiteId && startDate && endDate) }, - ); + enabled: !!(websiteId && startDate && endDate), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.js index 737bcd1b..37df458c 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.js @@ -28,15 +28,13 @@ export function Realtime({ websiteId }) { const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); const { data: website } = useWebsite(websiteId); - const { data, isLoading, error } = useQuery( - ['realtime', websiteId], - () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), - { - enabled: !!(websiteId && website), - refetchInterval: REALTIME_INTERVAL, - cache: false, - }, - ); + const { data, isLoading, error } = useQuery({ + queryKey: ['realtime', websiteId], + queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), + enabled: !!(websiteId && website), + refetchInterval: REALTIME_INTERVAL, + cache: false, + }); useEffect(() => { if (data) { diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js b/src/app/(main)/websites/[id]/realtime/RealtimeHome.js index dbaeb541..154ac707 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeHome.js @@ -10,7 +10,10 @@ export function RealtimeHome() { const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const router = useRouter(); - const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); + const { data, isLoading, error } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites'), + }); useEffect(() => { if (data?.length) { diff --git a/src/components/common/DataTable.tsx b/src/components/common/DataTable.tsx index a3c63c0a..621a44fe 100644 --- a/src/components/common/DataTable.tsx +++ b/src/components/common/DataTable.tsx @@ -1,29 +1,16 @@ -import { ReactNode, Dispatch, SetStateAction } from 'react'; +import { ReactNode } from 'react'; import classNames from 'classnames'; import { Banner, Loading, SearchField } from 'react-basics'; import { useMessages } from 'components/hooks'; import Empty from 'components/common/Empty'; import Pager from 'components/common/Pager'; import styles from './DataTable.module.css'; +import { FilterQueryResult } from 'components/hooks/useFilterQuery'; const DEFAULT_SEARCH_DELAY = 600; export interface DataTableProps { - queryResult: { - result: { - page: number; - pageSize: number; - count: number; - data: any[]; - }; - params: { - query: string; - page: number; - }; - setParams: Dispatch>; - isLoading: boolean; - error: unknown; - }; + queryResult: FilterQueryResult; searchDelay?: number; allowSearch?: boolean; allowPaging?: boolean; @@ -38,7 +25,12 @@ export function DataTable({ children, }: DataTableProps) { const { formatMessage, labels, messages } = useMessages(); - const { result, error, isLoading, params, setParams } = queryResult || {}; + const { + result, + params, + setParams, + query: { error, isLoading }, + } = queryResult || {}; const { page, pageSize, count, data } = result || {}; const { query } = params || {}; const hasData = Boolean(!isLoading && data?.length); diff --git a/src/components/common/FilterLink.tsx b/src/components/common/FilterLink.tsx index a9030227..bc0a4d48 100644 --- a/src/components/common/FilterLink.tsx +++ b/src/components/common/FilterLink.tsx @@ -13,7 +13,7 @@ export interface FilterLinkProps { label?: string; externalUrl?: string; className?: string; - children: ReactNode; + children?: ReactNode; } export function FilterLink({ diff --git a/src/components/hooks/useFilterQuery.ts b/src/components/hooks/useFilterQuery.ts index 37c28b7e..2c520741 100644 --- a/src/components/hooks/useFilterQuery.ts +++ b/src/components/hooks/useFilterQuery.ts @@ -1,24 +1,25 @@ -import { useState } from 'react'; +import { useState, Dispatch, SetStateAction } from 'react'; import { useApi } from 'components/hooks/useApi'; -import { UseQueryOptions } from '@tanstack/react-query'; +import { SearchFilter, FilterResult } from 'lib/types'; -export function useFilterQuery(key: any[], fn, options?: UseQueryOptions) { - const [params, setParams] = useState({ +export interface FilterQueryResult { + result: FilterResult; + query: any; + params: SearchFilter; + setParams: Dispatch>; +} + +export function useFilterQuery(props = {}): FilterQueryResult { + const [params, setParams] = useState({ query: '', page: 1, }); const { useQuery } = useApi(); - - const { data, ...other } = useQuery([...key, params], fn.bind(null, params), options); + const { data, ...query } = useQuery>({ ...props }); return { - result: data as { - page: number; - pageSize: number; - count: number; - data: any[]; - }, - ...other, + result: data, + query, params, setParams, }; diff --git a/src/components/hooks/useReports.ts b/src/components/hooks/useReports.ts index d9292aeb..0fad2db1 100644 --- a/src/components/hooks/useReports.ts +++ b/src/components/hooks/useReports.ts @@ -8,10 +8,10 @@ export function useReports() { const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = useApiFilter(); - const { data, error, isLoading } = useQuery( - ['reports', { modified, filter, page, pageSize }], - () => get(`/reports`, { filter, page, pageSize }), - ); + const { data, error, isLoading } = useQuery({ + queryKey: ['reports', { modified, filter, page, pageSize }], + queryFn: () => get(`/reports`, { filter, page, pageSize }), + }); const deleteReport = id => { mutate(id, { diff --git a/src/components/hooks/useShareToken.ts b/src/components/hooks/useShareToken.ts index 088f643e..41ae7faf 100644 --- a/src/components/hooks/useShareToken.ts +++ b/src/components/hooks/useShareToken.ts @@ -6,12 +6,15 @@ const selector = (state: { shareToken: string }) => state.shareToken; export function useShareToken(shareId: string) { const shareToken = useStore(selector); const { get, useQuery } = useApi(); - const { isLoading, error } = useQuery(['share', shareId], async () => { - const data = await get(`/share/${shareId}`); + const { isLoading, error } = useQuery({ + queryKey: ['share', shareId], + queryFn: async () => { + const data = await get(`/share/${shareId}`); - setShareToken(data); + setShareToken(data); - return data; + return data; + }, }); return { shareToken, isLoading, error }; diff --git a/src/components/hooks/useWebsite.ts b/src/components/hooks/useWebsite.ts index 7b68335a..d18e96ba 100644 --- a/src/components/hooks/useWebsite.ts +++ b/src/components/hooks/useWebsite.ts @@ -2,7 +2,9 @@ import useApi from './useApi'; export function useWebsite(websiteId: string) { const { get, useQuery } = useApi(); - return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), { + return useQuery({ + queryKey: ['websites', websiteId], + queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, }); } diff --git a/src/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.js index 078389d3..23334215 100644 --- a/src/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.js @@ -6,7 +6,10 @@ import styles from './WebsiteSelect.module.css'; export function WebsiteSelect({ websiteId, onSelect }) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); - const { data } = useQuery(['websites:me'], () => get('/me/websites', { pageSize: 100 })); + const { data } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites', { pageSize: 100 }), + }); const renderValue = value => { return data?.data?.find(({ id }) => id === value)?.name; diff --git a/src/components/metrics/ActiveUsers.js b/src/components/metrics/ActiveUsers.js index 3074d0df..cd3bfb99 100644 --- a/src/components/metrics/ActiveUsers.js +++ b/src/components/metrics/ActiveUsers.js @@ -7,14 +7,12 @@ import styles from './ActiveUsers.module.css'; export function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) { const { formatMessage, messages } = useMessages(); const { get, useQuery } = useApi(); - const { data } = useQuery( - ['websites:active', websiteId], - () => get(`/websites/${websiteId}/active`), - { - refetchInterval, - enabled: !!websiteId, - }, - ); + const { data } = useQuery({ + queryKey: ['websites:active', websiteId], + queryFn: () => get(`/websites/${websiteId}/active`), + refetchInterval, + enabled: !!websiteId, + }); const count = useMemo(() => { if (websiteId) { diff --git a/src/components/metrics/EventsChart.js b/src/components/metrics/EventsChart.js index f2cf48d1..610a3616 100644 --- a/src/components/metrics/EventsChart.js +++ b/src/components/metrics/EventsChart.js @@ -16,17 +16,20 @@ export function EventsChart({ websiteId, className, token }) { query: { url, event }, } = useNavigation(); - const { data, isLoading } = useQuery(['events', websiteId, modified, event], () => - get(`/websites/${websiteId}/events`, { - startAt: +startDate, - endAt: +endDate, - unit, - timezone, - url, - event, - token, - }), - ); + const { data, isLoading } = useQuery({ + queryKey: ['events', websiteId, modified, event], + queryFn: () => + get(`/websites/${websiteId}/events`, { + startAt: +startDate, + endAt: +endDate, + unit, + timezone, + url, + event, + token, + }), + enabled: !!websiteId, + }); const datasets = useMemo(() => { if (!data) return []; diff --git a/src/components/metrics/MetricsTable.js b/src/components/metrics/MetricsTable.js index 1d64b714..a251756f 100644 --- a/src/components/metrics/MetricsTable.js +++ b/src/components/metrics/MetricsTable.js @@ -35,8 +35,8 @@ export function MetricsTable({ const { get, useQuery } = useApi(); const { dir } = useLocale(); - const { data, isLoading, isFetched, error } = useQuery( - [ + const { data, isLoading, isFetched, error } = useQuery({ + queryKey: [ 'websites:metrics', { websiteId, @@ -53,11 +53,13 @@ export function MetricsTable({ city, }, ], - () => { + queryFn: () => { const filters = { url, title, referrer, os, browser, device, country, region, city }; filters[type] = undefined; + onDataLoad?.(); + return get(`/websites/${websiteId}/metrics`, { type, startAt: +startDate, @@ -65,8 +67,8 @@ export function MetricsTable({ ...filters, }); }, - { onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION }, - ); + retryDelay: delay || DEFAULT_ANIMATION_DURATION, + }); const filteredData = useMemo(() => { if (data) { From 7c42f0da8290fce03d00c7a86660406497c044da Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 03:07:03 -0800 Subject: [PATCH 35/62] Typescript refactor. --- package.json | 10 +-- src/app/(main)/{NavBar.js => NavBar.tsx} | 0 .../{UpdateNotice.js => UpdateNotice.tsx} | 0 .../{TestConsole.js => TestConsole.tsx} | 32 +++++----- .../dashboard/{Dashboard.js => Dashboard.tsx} | 28 ++++++--- .../{DashboardEdit.js => DashboardEdit.tsx} | 6 +- ...sButton.js => DashboardSettingsButton.tsx} | 0 ...DeleteButton.js => ReportDeleteButton.tsx} | 14 ++++- src/app/(main)/reports/ReportsDataTable.tsx | 12 +--- .../{ReportsHeader.js => ReportsHeader.tsx} | 0 .../{ReportsTable.js => ReportsTable.tsx} | 2 +- .../{BaseParameters.js => BaseParameters.tsx} | 13 +++- .../{FieldAddForm.js => FieldAddForm.tsx} | 18 ++++-- ...ggregateForm.js => FieldAggregateForm.tsx} | 12 +++- ...FieldFilterForm.js => FieldFilterForm.tsx} | 19 ++++-- ...FieldSelectForm.js => FieldSelectForm.tsx} | 17 +++++- ...lterSelectForm.js => FilterSelectForm.tsx} | 20 ++++-- .../{ParameterList.js => ParameterList.tsx} | 9 ++- .../[id]/{PopupForm.js => PopupForm.tsx} | 11 +++- .../reports/[id]/{Report.js => Report.tsx} | 16 +++-- .../[id]/{ReportBody.js => ReportBody.tsx} | 0 .../{ReportDetails.js => ReportDetails.tsx} | 2 +- .../{ReportHeader.js => ReportHeader.tsx} | 12 ++-- .../[id]/{ReportMenu.js => ReportMenu.tsx} | 0 ...ReportTemplates.js => ReportTemplates.tsx} | 0 ...aParameters.js => EventDataParameters.tsx} | 17 +++--- ...EventDataReport.js => EventDataReport.tsx} | 2 +- .../{EventDataTable.js => EventDataTable.tsx} | 0 .../{FunnelChart.js => FunnelChart.tsx} | 61 +++++++++++-------- ...nnelParameters.js => FunnelParameters.tsx} | 16 ++--- .../{FunnelReport.js => FunnelReport.tsx} | 0 .../{FunnelTable.js => FunnelTable.tsx} | 0 .../funnel/{UrlAddForm.js => UrlAddForm.tsx} | 7 ++- ...tsParameters.js => InsightsParameters.tsx} | 13 ++-- .../{InsightsReport.js => InsightsReport.tsx} | 2 +- .../{InsightsTable.js => InsightsTable.tsx} | 2 +- ...nParameters.js => RetentionParameters.tsx} | 7 +-- ...RetentionReport.js => RetentionReport.tsx} | 2 +- .../{RetentionTable.js => RetentionTable.tsx} | 2 +- ...teRangeSetting.js => DateRangeSetting.tsx} | 0 ...LanguageSetting.js => LanguageSetting.tsx} | 0 ...angeButton.js => PasswordChangeButton.tsx} | 0 ...sswordEditForm.js => PasswordEditForm.tsx} | 6 +- .../{ProfileHeader.js => ProfileHeader.tsx} | 0 ...ProfileSettings.js => ProfileSettings.tsx} | 0 .../{ThemeSetting.js => ThemeSetting.tsx} | 0 ...TimezoneSetting.js => TimezoneSetting.tsx} | 0 .../teams/{TeamAddForm.js => TeamAddForm.tsx} | 14 ++--- ...amDeleteButton.js => TeamDeleteButton.tsx} | 12 +++- .../{TeamDeleteForm.js => TeamDeleteForm.tsx} | 18 +++++- .../{TeamJoinForm.js => TeamJoinForm.tsx} | 4 +- ...TeamLeaveButton.js => TeamLeaveButton.tsx} | 10 ++- .../{TeamLeaveForm.js => TeamLeaveForm.tsx} | 35 +++++++---- .../{TeamsAddButton.js => TeamsAddButton.tsx} | 4 +- .../{TeamsDataTable.js => TeamsDataTable.tsx} | 4 +- .../teams/{TeamsHeader.js => TeamsHeader.tsx} | 0 ...TeamsJoinButton.js => TeamsJoinButton.tsx} | 0 .../teams/{TeamsTable.js => TeamsTable.tsx} | 2 +- .../teams/{WebsiteTags.js => WebsiteTags.tsx} | 10 ++- .../{TeamEditForm.js => TeamEditForm.tsx} | 0 ...veButton.js => TeamMemberRemoveButton.tsx} | 31 ++++++---- .../[id]/{TeamMembers.js => TeamMembers.tsx} | 2 +- ...amMembersTable.js => TeamMembersTable.tsx} | 10 ++- .../{TeamSettings.js => TeamSettings.tsx} | 6 +- ...bsiteAddForm.js => TeamWebsiteAddForm.tsx} | 16 ++++- ...eButton.js => TeamWebsiteRemoveButton.tsx} | 0 .../{TeamWebsites.js => TeamWebsites.tsx} | 8 ++- ...WebsitesTable.js => TeamWebsitesTable.tsx} | 12 +++- .../settings/teams/[id]/{page.js => page.tsx} | 0 .../{UserAddButton.js => UserAddButton.tsx} | 2 +- .../users/{UserAddForm.js => UserAddForm.tsx} | 6 +- ...erDeleteButton.js => UserDeleteButton.tsx} | 10 ++- .../{UserDeleteForm.js => UserDeleteForm.tsx} | 11 ++-- .../{UserEditForm.js => UserEditForm.tsx} | 26 ++++++-- src/app/(main)/settings/users/UserWebsites.js | 36 ----------- .../(main)/settings/users/UserWebsites.tsx | 26 ++++++++ .../{UsersDataTable.js => UsersDataTable.tsx} | 4 +- .../users/{UsersHeader.js => UsersHeader.tsx} | 2 +- .../users/{UsersTable.js => UsersTable.tsx} | 2 +- .../settings/users/[id]/UserSettings.js | 2 +- .../settings/users/[id]/{page.js => page.tsx} | 0 ...bsiteAddButton.js => WebsiteAddButton.tsx} | 2 +- .../settings/websites/WebsiteAddForm.tsx | 8 ++- .../settings/websites/WebsiteSettings.js | 2 +- .../{WebsitesHeader.js => WebsitesHeader.tsx} | 0 .../{WebsitesTable.js => WebsitesTable.tsx} | 12 +++- .../[id]/{ShareUrl.js => ShareUrl.tsx} | 10 +-- .../{TrackingCode.js => TrackingCode.tsx} | 8 ++- .../[id]/{WebsiteData.js => WebsiteData.tsx} | 8 ++- ...iteDeleteForm.js => WebsiteDeleteForm.tsx} | 14 ++++- ...WebsiteEditForm.js => WebsiteEditForm.tsx} | 14 ++++- ...bsiteResetForm.js => WebsiteResetForm.tsx} | 16 ++++- .../websites/[id]/{page.js => page.tsx} | 0 .../{WebsitesBrowse.js => WebsitesBrowse.tsx} | 2 +- .../{WebsiteChart.js => WebsiteChart.tsx} | 4 +- ...bsiteChartList.js => WebsiteChartList.tsx} | 10 ++- .../{WebsiteDetails.js => WebsiteDetails.tsx} | 7 +-- ...ilterButton.js => WebsiteFilterButton.tsx} | 12 +++- .../{WebsiteHeader.js => WebsiteHeader.tsx} | 11 +++- ...WebsiteMenuView.js => WebsiteMenuView.tsx} | 8 ++- ...iteMetricsBar.js => WebsiteMetricsBar.tsx} | 12 +++- ...bsiteTableView.js => WebsiteTableView.tsx} | 2 +- ...aMetricsBar.js => EventDataMetricsBar.tsx} | 2 +- .../{EventDataTable.js => EventDataTable.tsx} | 0 ...aValueTable.js => EventDataValueTable.tsx} | 2 +- .../[id]/event-data/{page.js => page.tsx} | 0 src/app/login/{LoginForm.js => LoginForm.tsx} | 9 +-- src/app/logout/{Logout.js => Logout.tsx} | 0 .../share/[...id]/{Footer.js => Footer.tsx} | 0 .../share/[...id]/{Header.js => Header.tsx} | 0 src/app/share/[...id]/{Share.js => Share.tsx} | 0 src/components/common/DataTable.tsx | 6 +- src/components/common/EmptyPlaceholder.tsx | 4 +- src/components/common/FilterButtons.tsx | 2 +- src/components/declarations.d.ts | 1 + src/components/hooks/useApiFilter.ts | 28 --------- src/components/hooks/useDateRange.ts | 2 +- src/components/hooks/useFilterQuery.ts | 19 ++++-- src/components/hooks/useNavigation.ts | 7 ++- src/components/hooks/useReports.ts | 30 ++++----- src/components/hooks/useShareToken.ts | 6 +- .../input/{DateFilter.js => DateFilter.tsx} | 25 +++++--- .../{LanguageButton.js => LanguageButton.tsx} | 6 +- .../{LogoutButton.js => LogoutButton.tsx} | 6 +- .../input/{MonthSelect.js => MonthSelect.tsx} | 10 +-- .../{ProfileButton.js => ProfileButton.tsx} | 0 .../{RefreshButton.js => RefreshButton.tsx} | 8 ++- .../{SettingsButton.js => SettingsButton.tsx} | 0 .../input/{ThemeButton.js => ThemeButton.tsx} | 0 ...iteDateFilter.js => WebsiteDateFilter.tsx} | 2 +- .../{WebsiteSelect.js => WebsiteSelect.tsx} | 8 ++- src/components/layout/Grid.js | 18 ------ src/components/layout/Grid.tsx | 34 +++++++++++ .../layout/{NavGroup.js => NavGroup.tsx} | 10 ++- .../layout/{SideNav.js => SideNav.tsx} | 11 +++- src/components/{messages.js => messages.ts} | 0 .../{ActiveUsers.js => ActiveUsers.tsx} | 12 +++- .../metrics/{BarChart.js => BarChart.tsx} | 31 +++++++--- .../{BrowsersTable.js => BrowsersTable.tsx} | 5 +- .../{CitiesTable.js => CitiesTable.tsx} | 7 +-- .../{CountriesTable.js => CountriesTable.tsx} | 19 ++++-- .../{DatePickerForm.js => DatePickerForm.tsx} | 2 +- .../{DevicesTable.js => DevicesTable.tsx} | 5 +- .../{EventsChart.js => EventsChart.tsx} | 9 ++- .../{EventsTable.js => EventsTable.tsx} | 7 +-- .../metrics/{FilterTags.js => FilterTags.tsx} | 4 +- .../{LanguagesTable.js => LanguagesTable.tsx} | 8 ++- .../metrics/{Legend.js => Legend.tsx} | 0 .../metrics/{ListTable.js => ListTable.tsx} | 17 +++++- .../metrics/{MetricCard.js => MetricCard.tsx} | 16 ++++- .../metrics/{MetricsBar.js => MetricsBar.tsx} | 10 ++- .../{MetricsTable.js => MetricsTable.tsx} | 39 +++++++----- .../metrics/{OSTable.js => OSTable.tsx} | 6 +- .../metrics/{PagesTable.js => PagesTable.tsx} | 9 ++- .../{PageviewsChart.js => PageviewsChart.tsx} | 16 +++-- ...etersTable.js => QueryParametersTable.tsx} | 8 ++- .../{RealtimeChart.js => RealtimeChart.tsx} | 20 +++--- .../{ReferrersTable.js => ReferrersTable.tsx} | 5 +- .../{RegionsTable.js => RegionsTable.tsx} | 7 +-- .../{ScreenTable.js => ScreenTable.tsx} | 5 +- .../metrics/{WorldMap.js => WorldMap.tsx} | 8 ++- src/lib/{charts.js => charts.tsx} | 12 ++-- src/lib/{crypto.js => crypto.ts} | 4 +- src/lib/{db.js => db.ts} | 4 +- src/lib/{filters.js => filters.ts} | 10 +-- src/lib/{format.js => format.ts} | 20 +++--- src/lib/{lang.js => lang.ts} | 4 +- src/lib/types.ts | 2 +- src/store/{app.js => app.ts} | 0 src/store/{cache.js => cache.ts} | 0 src/store/{dashboard.js => dashboard.ts} | 0 src/store/{version.js => version.ts} | 0 yarn.lock | 53 ++++++++-------- 173 files changed, 968 insertions(+), 549 deletions(-) rename src/app/(main)/{NavBar.js => NavBar.tsx} (100%) rename src/app/(main)/{UpdateNotice.js => UpdateNotice.tsx} (100%) rename src/app/(main)/console/{TestConsole.js => TestConsole.tsx} (85%) rename src/app/(main)/dashboard/{Dashboard.js => Dashboard.tsx} (80%) rename src/app/(main)/dashboard/{DashboardEdit.js => DashboardEdit.tsx} (95%) rename src/app/(main)/dashboard/{DashboardSettingsButton.js => DashboardSettingsButton.tsx} (100%) rename src/app/(main)/reports/{ReportDeleteButton.js => ReportDeleteButton.tsx} (81%) rename src/app/(main)/reports/{ReportsHeader.js => ReportsHeader.tsx} (100%) rename src/app/(main)/reports/{ReportsTable.js => ReportsTable.tsx} (94%) rename src/app/(main)/reports/[id]/{BaseParameters.js => BaseParameters.tsx} (83%) rename src/app/(main)/reports/[id]/{FieldAddForm.js => FieldAddForm.tsx} (76%) rename src/app/(main)/reports/[id]/{FieldAggregateForm.js => FieldAggregateForm.tsx} (86%) rename src/app/(main)/reports/[id]/{FieldFilterForm.js => FieldFilterForm.tsx} (86%) rename src/app/(main)/reports/[id]/{FieldSelectForm.js => FieldSelectForm.tsx} (65%) rename src/app/(main)/reports/[id]/{FilterSelectForm.js => FilterSelectForm.tsx} (67%) rename src/app/(main)/reports/[id]/{ParameterList.js => ParameterList.tsx} (82%) rename src/app/(main)/reports/[id]/{PopupForm.js => PopupForm.tsx} (59%) rename src/app/(main)/reports/[id]/{Report.js => Report.tsx} (56%) rename src/app/(main)/reports/[id]/{ReportBody.js => ReportBody.tsx} (100%) rename src/app/(main)/reports/[id]/{ReportDetails.js => ReportDetails.tsx} (90%) rename src/app/(main)/reports/[id]/{ReportHeader.js => ReportHeader.tsx} (92%) rename src/app/(main)/reports/[id]/{ReportMenu.js => ReportMenu.tsx} (100%) rename src/app/(main)/reports/create/{ReportTemplates.js => ReportTemplates.tsx} (100%) rename src/app/(main)/reports/event-data/{EventDataParameters.js => EventDataParameters.tsx} (91%) rename src/app/(main)/reports/event-data/{EventDataReport.js => EventDataReport.tsx} (89%) rename src/app/(main)/reports/event-data/{EventDataTable.js => EventDataTable.tsx} (100%) rename src/app/(main)/reports/funnel/{FunnelChart.js => FunnelChart.tsx} (52%) rename src/app/(main)/reports/funnel/{FunnelParameters.js => FunnelParameters.tsx} (84%) rename src/app/(main)/reports/funnel/{FunnelReport.js => FunnelReport.tsx} (100%) rename src/app/(main)/reports/funnel/{FunnelTable.js => FunnelTable.tsx} (100%) rename src/app/(main)/reports/funnel/{UrlAddForm.js => UrlAddForm.tsx} (86%) rename src/app/(main)/reports/insights/{InsightsParameters.js => InsightsParameters.tsx} (93%) rename src/app/(main)/reports/insights/{InsightsReport.js => InsightsReport.tsx} (90%) rename src/app/(main)/reports/insights/{InsightsTable.js => InsightsTable.tsx} (96%) rename src/app/(main)/reports/retention/{RetentionParameters.js => RetentionParameters.tsx} (87%) rename src/app/(main)/reports/retention/{RetentionReport.js => RetentionReport.tsx} (92%) rename src/app/(main)/reports/retention/{RetentionTable.js => RetentionTable.tsx} (96%) rename src/app/(main)/settings/profile/{DateRangeSetting.js => DateRangeSetting.tsx} (100%) rename src/app/(main)/settings/profile/{LanguageSetting.js => LanguageSetting.tsx} (100%) rename src/app/(main)/settings/profile/{PasswordChangeButton.js => PasswordChangeButton.tsx} (100%) rename src/app/(main)/settings/profile/{PasswordEditForm.js => PasswordEditForm.tsx} (91%) rename src/app/(main)/settings/profile/{ProfileHeader.js => ProfileHeader.tsx} (100%) rename src/app/(main)/settings/profile/{ProfileSettings.js => ProfileSettings.tsx} (100%) rename src/app/(main)/settings/profile/{ThemeSetting.js => ThemeSetting.tsx} (100%) rename src/app/(main)/settings/profile/{TimezoneSetting.js => TimezoneSetting.tsx} (100%) rename src/app/(main)/settings/teams/{TeamAddForm.js => TeamAddForm.tsx} (72%) rename src/app/(main)/settings/teams/{TeamDeleteButton.js => TeamDeleteButton.tsx} (79%) rename src/app/(main)/settings/teams/{TeamDeleteForm.js => TeamDeleteForm.tsx} (72%) rename src/app/(main)/settings/teams/{TeamJoinForm.js => TeamJoinForm.tsx} (85%) rename src/app/(main)/settings/teams/{TeamLeaveButton.js => TeamLeaveButton.tsx} (87%) rename src/app/(main)/settings/teams/{TeamLeaveForm.js => TeamLeaveForm.tsx} (60%) rename src/app/(main)/settings/teams/{TeamsAddButton.js => TeamsAddButton.tsx} (79%) rename src/app/(main)/settings/teams/{TeamsDataTable.js => TeamsDataTable.tsx} (88%) rename src/app/(main)/settings/teams/{TeamsHeader.js => TeamsHeader.tsx} (100%) rename src/app/(main)/settings/teams/{TeamsJoinButton.js => TeamsJoinButton.tsx} (100%) rename src/app/(main)/settings/teams/{TeamsTable.js => TeamsTable.tsx} (96%) rename src/app/(main)/settings/teams/{WebsiteTags.js => WebsiteTags.tsx} (83%) rename src/app/(main)/settings/teams/[id]/{TeamEditForm.js => TeamEditForm.tsx} (100%) rename src/app/(main)/settings/teams/[id]/{TeamMemberRemoveButton.js => TeamMemberRemoveButton.tsx} (59%) rename src/app/(main)/settings/teams/[id]/{TeamMembers.js => TeamMembers.tsx} (89%) rename src/app/(main)/settings/teams/[id]/{TeamMembersTable.js => TeamMembersTable.tsx} (90%) rename src/app/(main)/settings/teams/[id]/{TeamSettings.js => TeamSettings.tsx} (91%) rename src/app/(main)/settings/teams/[id]/{TeamWebsiteAddForm.js => TeamWebsiteAddForm.tsx} (86%) rename src/app/(main)/settings/teams/[id]/{TeamWebsiteRemoveButton.js => TeamWebsiteRemoveButton.tsx} (100%) rename src/app/(main)/settings/teams/[id]/{TeamWebsites.js => TeamWebsites.tsx} (86%) rename src/app/(main)/settings/teams/[id]/{TeamWebsitesTable.js => TeamWebsitesTable.tsx} (85%) rename src/app/(main)/settings/teams/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/settings/users/{UserAddButton.js => UserAddButton.tsx} (92%) rename src/app/(main)/settings/users/{UserAddForm.js => UserAddForm.tsx} (92%) rename src/app/(main)/settings/users/{UserDeleteButton.js => UserDeleteButton.tsx} (85%) rename src/app/(main)/settings/users/{UserDeleteForm.js => UserDeleteForm.tsx} (72%) rename src/app/(main)/settings/users/{UserEditForm.js => UserEditForm.tsx} (82%) delete mode 100644 src/app/(main)/settings/users/UserWebsites.js create mode 100644 src/app/(main)/settings/users/UserWebsites.tsx rename src/app/(main)/settings/users/{UsersDataTable.js => UsersDataTable.tsx} (82%) rename src/app/(main)/settings/users/{UsersHeader.js => UsersHeader.tsx} (85%) rename src/app/(main)/settings/users/{UsersTable.js => UsersTable.tsx} (96%) rename src/app/(main)/settings/users/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/settings/websites/{WebsiteAddButton.js => WebsiteAddButton.tsx} (92%) rename src/app/(main)/settings/websites/{WebsitesHeader.js => WebsitesHeader.tsx} (100%) rename src/app/(main)/settings/websites/{WebsitesTable.js => WebsitesTable.tsx} (90%) rename src/app/(main)/settings/websites/[id]/{ShareUrl.js => ShareUrl.tsx} (91%) rename src/app/(main)/settings/websites/[id]/{TrackingCode.js => TrackingCode.tsx} (87%) rename src/app/(main)/settings/websites/[id]/{WebsiteData.js => WebsiteData.tsx} (92%) rename src/app/(main)/settings/websites/[id]/{WebsiteDeleteForm.js => WebsiteDeleteForm.tsx} (82%) rename src/app/(main)/settings/websites/[id]/{WebsiteEditForm.js => WebsiteEditForm.tsx} (85%) rename src/app/(main)/settings/websites/[id]/{WebsiteResetForm.js => WebsiteResetForm.tsx} (78%) rename src/app/(main)/settings/websites/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/websites/{WebsitesBrowse.js => WebsitesBrowse.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteChart.js => WebsiteChart.tsx} (90%) rename src/app/(main)/websites/[id]/{WebsiteChartList.js => WebsiteChartList.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteDetails.js => WebsiteDetails.tsx} (87%) rename src/app/(main)/websites/[id]/{WebsiteFilterButton.js => WebsiteFilterButton.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteHeader.js => WebsiteHeader.tsx} (92%) rename src/app/(main)/websites/[id]/{WebsiteMenuView.js => WebsiteMenuView.tsx} (97%) rename src/app/(main)/websites/[id]/{WebsiteMetricsBar.js => WebsiteMetricsBar.tsx} (93%) rename src/app/(main)/websites/[id]/{WebsiteTableView.js => WebsiteTableView.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{EventDataMetricsBar.js => EventDataMetricsBar.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{EventDataTable.js => EventDataTable.tsx} (100%) rename src/app/(main)/websites/[id]/event-data/{EventDataValueTable.js => EventDataValueTable.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{page.js => page.tsx} (100%) rename src/app/login/{LoginForm.js => LoginForm.tsx} (90%) rename src/app/logout/{Logout.js => Logout.tsx} (100%) rename src/app/share/[...id]/{Footer.js => Footer.tsx} (100%) rename src/app/share/[...id]/{Header.js => Header.tsx} (100%) rename src/app/share/[...id]/{Share.js => Share.tsx} (100%) delete mode 100644 src/components/hooks/useApiFilter.ts rename src/components/input/{DateFilter.js => DateFilter.tsx} (89%) rename src/components/input/{LanguageButton.js => LanguageButton.tsx} (88%) rename src/components/input/{LogoutButton.js => LogoutButton.tsx} (80%) rename src/components/input/{MonthSelect.js => MonthSelect.tsx} (87%) rename src/components/input/{ProfileButton.js => ProfileButton.tsx} (100%) rename src/components/input/{RefreshButton.js => RefreshButton.tsx} (87%) rename src/components/input/{SettingsButton.js => SettingsButton.tsx} (100%) rename src/components/input/{ThemeButton.js => ThemeButton.tsx} (100%) rename src/components/input/{WebsiteDateFilter.js => WebsiteDateFilter.tsx} (95%) rename src/components/input/{WebsiteSelect.js => WebsiteSelect.tsx} (88%) delete mode 100644 src/components/layout/Grid.js create mode 100644 src/components/layout/Grid.tsx rename src/components/layout/{NavGroup.js => NavGroup.tsx} (90%) rename src/components/layout/{SideNav.js => SideNav.tsx} (82%) rename src/components/{messages.js => messages.ts} (100%) rename src/components/metrics/{ActiveUsers.js => ActiveUsers.tsx} (85%) rename src/components/metrics/{BarChart.js => BarChart.tsx} (83%) rename src/components/metrics/{BrowsersTable.js => BrowsersTable.tsx} (85%) rename src/components/metrics/{CitiesTable.js => CitiesTable.tsx} (85%) rename src/components/metrics/{CountriesTable.js => CountriesTable.tsx} (73%) rename src/components/metrics/{DatePickerForm.js => DatePickerForm.tsx} (96%) rename src/components/metrics/{DevicesTable.js => DevicesTable.tsx} (86%) rename src/components/metrics/{EventsChart.js => EventsChart.tsx} (92%) rename src/components/metrics/{EventsTable.js => EventsTable.tsx} (69%) rename src/components/metrics/{FilterTags.js => FilterTags.tsx} (92%) rename src/components/metrics/{LanguagesTable.js => LanguagesTable.tsx} (80%) rename src/components/metrics/{Legend.js => Legend.tsx} (100%) rename src/components/metrics/{ListTable.js => ListTable.tsx} (87%) rename src/components/metrics/{MetricCard.js => MetricCard.tsx} (77%) rename src/components/metrics/{MetricsBar.js => MetricsBar.tsx} (79%) rename src/components/metrics/{MetricsTable.js => MetricsTable.tsx} (80%) rename src/components/metrics/{OSTable.js => OSTable.tsx} (80%) rename src/components/metrics/{PagesTable.js => PagesTable.tsx} (83%) rename src/components/metrics/{PageviewsChart.js => PageviewsChart.tsx} (74%) rename src/components/metrics/{QueryParametersTable.js => QueryParametersTable.tsx} (88%) rename src/components/metrics/{RealtimeChart.js => RealtimeChart.tsx} (81%) rename src/components/metrics/{ReferrersTable.js => ReferrersTable.tsx} (83%) rename src/components/metrics/{RegionsTable.js => RegionsTable.tsx} (86%) rename src/components/metrics/{ScreenTable.js => ScreenTable.tsx} (70%) rename src/components/metrics/{WorldMap.js => WorldMap.tsx} (91%) rename src/lib/{charts.js => charts.tsx} (77%) rename src/lib/{crypto.js => crypto.ts} (85%) rename src/lib/{db.js => db.ts} (91%) rename src/lib/{filters.js => filters.ts} (86%) rename src/lib/{format.js => format.ts} (74%) rename src/lib/{lang.js => lang.ts} (96%) rename src/store/{app.js => app.ts} (100%) rename src/store/{cache.js => cache.ts} (100%) rename src/store/{dashboard.js => dashboard.ts} (100%) rename src/store/{version.js => version.ts} (100%) diff --git a/package.json b/package.json index feec3db8..a158696c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@prisma/client": "5.6.0", "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", - "@tanstack/react-query": "^4.33.0", + "@tanstack/react-query": "^5.12.2", "@umami/prisma-client": "^0.8.0", "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", @@ -94,12 +94,12 @@ "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", "next": "13.5.6", - "next-basics": "^0.37.0", + "next-basics": "^0.39.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "prisma": "5.6.0", "react": "^18.2.0", - "react-basics": "^0.110.0", + "react-basics": "^0.114.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", @@ -127,8 +127,8 @@ "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^8.1.0", "@types/node": "^20.9.0", - "@types/react": "^18.2.37", - "@types/react-dom": "^18.2.15", + "@types/react": "^18.2.41", + "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/NavBar.js b/src/app/(main)/NavBar.tsx similarity index 100% rename from src/app/(main)/NavBar.js rename to src/app/(main)/NavBar.tsx diff --git a/src/app/(main)/UpdateNotice.js b/src/app/(main)/UpdateNotice.tsx similarity index 100% rename from src/app/(main)/UpdateNotice.js rename to src/app/(main)/UpdateNotice.tsx diff --git a/src/app/(main)/console/TestConsole.js b/src/app/(main)/console/TestConsole.tsx similarity index 85% rename from src/app/(main)/console/TestConsole.js rename to src/app/(main)/console/TestConsole.tsx index 7aae09b2..0bb807ff 100644 --- a/src/app/(main)/console/TestConsole.js +++ b/src/app/(main)/console/TestConsole.tsx @@ -1,18 +1,18 @@ 'use client'; +import { Button } from 'react-basics'; +import Head from 'next/head'; +import Link from 'next/link'; +import Script from 'next/script'; import WebsiteSelect from 'components/input/WebsiteSelect'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EventsChart from 'components/metrics/EventsChart'; -import WebsiteChart from '../../(main)/websites/[id]/WebsiteChart'; +import WebsiteChart from 'app/(main)/websites/[id]/WebsiteChart'; import useApi from 'components/hooks/useApi'; -import Head from 'next/head'; -import Link from 'next/link'; import useNavigation from 'components/hooks/useNavigation'; -import Script from 'next/script'; -import { Button } from 'react-basics'; import styles from './TestConsole.module.css'; -export function TestConsole({ websiteId }) { +export function TestConsole({ websiteId }: { websiteId: string }) { const { get, useQuery } = useApi(); const { data, isLoading, error } = useQuery({ queryKey: ['websites:me'], @@ -20,14 +20,14 @@ export function TestConsole({ websiteId }) { }); const { router } = useNavigation(); - function handleChange(value) { + function handleChange(value: string) { router.push(`/console/${value}`); } function handleClick() { - window.umami.track({ url: '/page-view', referrer: 'https://www.google.com' }); - window.umami.track('track-event-no-data'); - window.umami.track('track-event-with-data', { + window['umami'].track({ url: '/page-view', referrer: 'https://www.google.com' }); + window['umami'].track('track-event-no-data'); + window['umami'].track('track-event-with-data', { test: 'test-data', boolean: true, booleanError: 'true', @@ -47,7 +47,7 @@ export function TestConsole({ websiteId }) { } function handleIdentifyClick() { - window.umami.identify({ + window['umami'].identify({ userId: 123, name: 'brian', number: Math.random() * 100, @@ -74,7 +74,7 @@ export function TestConsole({ websiteId }) { const website = data?.data.find(({ id }) => websiteId === id); return ( - + {website ? `${website.name} | Umami Console` : 'Umami Console'} @@ -116,7 +116,7 @@ export function TestConsole({ websiteId }) {
Click events
-

@@ -125,18 +125,18 @@ export function TestConsole({ websiteId }) { data-umami-event="button-click" data-umami-event-name="bob" data-umami-event-id="123" - variant="action" + variant="primary" > Send event with data

Javascript events
-

-

diff --git a/src/app/(main)/dashboard/Dashboard.js b/src/app/(main)/dashboard/Dashboard.tsx similarity index 80% rename from src/app/(main)/dashboard/Dashboard.js rename to src/app/(main)/dashboard/Dashboard.tsx index 6f0659bb..1afc0da2 100644 --- a/src/app/(main)/dashboard/Dashboard.js +++ b/src/app/(main)/dashboard/Dashboard.tsx @@ -11,23 +11,31 @@ import useApi from 'components/hooks/useApi'; import useDashboard from 'store/dashboard'; import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; -import useApiFilter from 'components/hooks/useApiFilter'; +import useFilterQuery from 'components/hooks/useFilterQuery'; export function Dashboard() { const { formatMessage, labels, messages } = useMessages(); const { showCharts, editing } = useDashboard(); const { dir } = useLocale(); - const { get, useQuery } = useApi(); - const { page, handlePageChange } = useApiFilter(); + const { get } = useApi(); const pageSize = 10; - const { data: result, isLoading } = useQuery({ - queryKey: ['websites', page, pageSize], - queryFn: () => get('/websites', { includeTeams: 1, page, pageSize }), - }); - const { data, count } = result || {}; - const hasData = data && data?.length !== 0; - if (isLoading) { + const { query, params, setParams, result } = useFilterQuery({ + queryKey: ['dashboard:websites'], + queryFn: (params: any) => { + return get(`/websites`, { ...params, includeTeams: true, pageSize }); + }, + }); + + const handlePageChange = (page: number) => { + setParams({ ...params, page }); + }; + + const { data, count } = result || {}; + const hasData = !!(data as any)?.length; + const { page } = params; + + if (query.isLoading) { return ; } diff --git a/src/app/(main)/dashboard/DashboardEdit.js b/src/app/(main)/dashboard/DashboardEdit.tsx similarity index 95% rename from src/app/(main)/dashboard/DashboardEdit.js rename to src/app/(main)/dashboard/DashboardEdit.tsx index 0a8734e8..35638038 100644 --- a/src/app/(main)/dashboard/DashboardEdit.js +++ b/src/app/(main)/dashboard/DashboardEdit.tsx @@ -60,13 +60,13 @@ export function DashboardEdit() { return ( <>
- - -
diff --git a/src/app/(main)/dashboard/DashboardSettingsButton.js b/src/app/(main)/dashboard/DashboardSettingsButton.tsx similarity index 100% rename from src/app/(main)/dashboard/DashboardSettingsButton.js rename to src/app/(main)/dashboard/DashboardSettingsButton.tsx diff --git a/src/app/(main)/reports/ReportDeleteButton.js b/src/app/(main)/reports/ReportDeleteButton.tsx similarity index 81% rename from src/app/(main)/reports/ReportDeleteButton.js rename to src/app/(main)/reports/ReportDeleteButton.tsx index 35809a98..d3f1bb4f 100644 --- a/src/app/(main)/reports/ReportDeleteButton.js +++ b/src/app/(main)/reports/ReportDeleteButton.tsx @@ -3,13 +3,21 @@ import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm'; import { useApi, useMessages } from 'components/hooks'; import { setValue } from 'store/cache'; -export function ReportDeleteButton({ reportId, reportName, onDelete }) { +export function ReportDeleteButton({ + reportId, + reportName, + onDelete, +}: { + reportId: string; + reportName: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); - const handleConfirm = close => { - mutate(reportId, { + const handleConfirm = (close: () => void) => { + mutate(reportId as any, { onSuccess: () => { setValue('reports', Date.now()); onDelete?.(); diff --git a/src/app/(main)/reports/ReportsDataTable.tsx b/src/app/(main)/reports/ReportsDataTable.tsx index eeef203b..3ede4783 100644 --- a/src/app/(main)/reports/ReportsDataTable.tsx +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -1,18 +1,10 @@ 'use client'; -import { useApi } from 'components/hooks'; +import { useReports } from 'components/hooks'; import ReportsTable from './ReportsTable'; -import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; -import useCache from 'store/cache'; export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { - const { get } = useApi(); - const modified = useCache(state => (state as any)?.reports); - const queryResult = useFilterQuery({ - queryKey: ['reports', { websiteId, modified }], - queryFn: (params: any) => - get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), - }); + const queryResult = useReports(websiteId); return ( diff --git a/src/app/(main)/reports/ReportsHeader.js b/src/app/(main)/reports/ReportsHeader.tsx similarity index 100% rename from src/app/(main)/reports/ReportsHeader.js rename to src/app/(main)/reports/ReportsHeader.tsx diff --git a/src/app/(main)/reports/ReportsTable.js b/src/app/(main)/reports/ReportsTable.tsx similarity index 94% rename from src/app/(main)/reports/ReportsTable.js rename to src/app/(main)/reports/ReportsTable.tsx index 6b2a7932..882110ee 100644 --- a/src/app/(main)/reports/ReportsTable.js +++ b/src/app/(main)/reports/ReportsTable.tsx @@ -5,7 +5,7 @@ import useUser from 'components/hooks/useUser'; import { REPORT_TYPES } from 'lib/constants'; import ReportDeleteButton from './ReportDeleteButton'; -export function ReportsTable({ data = [], showDomain }) { +export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomain?: boolean }) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/reports/[id]/BaseParameters.js b/src/app/(main)/reports/[id]/BaseParameters.tsx similarity index 83% rename from src/app/(main)/reports/[id]/BaseParameters.js rename to src/app/(main)/reports/[id]/BaseParameters.tsx index a54ef4f3..9b6be5d8 100644 --- a/src/app/(main)/reports/[id]/BaseParameters.js +++ b/src/app/(main)/reports/[id]/BaseParameters.tsx @@ -6,12 +6,19 @@ import WebsiteSelect from 'components/input/WebsiteSelect'; import { useMessages } from 'components/hooks'; import { ReportContext } from './Report'; +export interface BaseParametersProps { + showWebsiteSelect?: boolean; + allowWebsiteSelect?: boolean; + showDateSelect?: boolean; + allowDateSelect?: boolean; +} + export function BaseParameters({ showWebsiteSelect = true, allowWebsiteSelect = true, showDateSelect = true, allowDateSelect = true, -}) { +}: BaseParametersProps) { const { report, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); @@ -19,11 +26,11 @@ export function BaseParameters({ const { websiteId, dateRange } = parameters || {}; const { value, startDate, endDate } = dateRange || {}; - const handleWebsiteSelect = websiteId => { + const handleWebsiteSelect = (websiteId: string) => { updateReport({ websiteId, parameters: { websiteId } }); }; - const handleDateChange = value => { + const handleDateChange = (value: string) => { updateReport({ parameters: { dateRange: { ...parseDateRange(value) } } }); }; diff --git a/src/app/(main)/reports/[id]/FieldAddForm.js b/src/app/(main)/reports/[id]/FieldAddForm.tsx similarity index 76% rename from src/app/(main)/reports/[id]/FieldAddForm.js rename to src/app/(main)/reports/[id]/FieldAddForm.tsx index 6923bceb..9db472d8 100644 --- a/src/app/(main)/reports/[id]/FieldAddForm.js +++ b/src/app/(main)/reports/[id]/FieldAddForm.tsx @@ -7,10 +7,20 @@ import FieldAggregateForm from './FieldAggregateForm'; import FieldFilterForm from './FieldFilterForm'; import styles from './FieldAddForm.module.css'; -export function FieldAddForm({ fields = [], group, onAdd, onClose }) { - const [selected, setSelected] = useState(); +export function FieldAddForm({ + fields = [], + group, + onAdd, + onClose, +}: { + fields?: any[]; + group: string; + onAdd: (group: string, value: string) => void; + onClose: () => void; +}) { + const [selected, setSelected] = useState<{ name: string; type: string; value: string }>(); - const handleSelect = value => { + const handleSelect = (value: any) => { const { type } = value; if (group === REPORT_PARAMETERS.groups || type === 'array' || type === 'boolean') { @@ -22,7 +32,7 @@ export function FieldAddForm({ fields = [], group, onAdd, onClose }) { setSelected(value); }; - const handleSave = value => { + const handleSave = (value: any) => { onAdd(group, value); onClose(); }; diff --git a/src/app/(main)/reports/[id]/FieldAggregateForm.js b/src/app/(main)/reports/[id]/FieldAggregateForm.tsx similarity index 86% rename from src/app/(main)/reports/[id]/FieldAggregateForm.js rename to src/app/(main)/reports/[id]/FieldAggregateForm.tsx index 34b67980..6b5bf636 100644 --- a/src/app/(main)/reports/[id]/FieldAggregateForm.js +++ b/src/app/(main)/reports/[id]/FieldAggregateForm.tsx @@ -1,7 +1,15 @@ import { Form, FormRow, Menu, Item } from 'react-basics'; import { useMessages } from 'components/hooks'; -export default function FieldAggregateForm({ name, type, onSelect }) { +export default function FieldAggregateForm({ + name, + type, + onSelect, +}: { + name: string; + type: string; + onSelect: (key: any) => void; +}) { const { formatMessage, labels } = useMessages(); const options = { @@ -27,7 +35,7 @@ export default function FieldAggregateForm({ name, type, onSelect }) { const items = options[type]; - const handleSelect = value => { + const handleSelect = (value: any) => { onSelect({ name, type, value }); }; diff --git a/src/app/(main)/reports/[id]/FieldFilterForm.js b/src/app/(main)/reports/[id]/FieldFilterForm.tsx similarity index 86% rename from src/app/(main)/reports/[id]/FieldFilterForm.js rename to src/app/(main)/reports/[id]/FieldFilterForm.tsx index ea80f82a..4af7febf 100644 --- a/src/app/(main)/reports/[id]/FieldFilterForm.js +++ b/src/app/(main)/reports/[id]/FieldFilterForm.tsx @@ -3,6 +3,15 @@ import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics'; import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks'; import styles from './FieldFilterForm.module.css'; +export interface FieldFilterFormProps { + name: string; + label?: string; + type: string; + values?: any[]; + onSelect?: (key: any) => void; + allowFilterSelect?: boolean; +} + export default function FieldFilterForm({ name, label, @@ -10,7 +19,7 @@ export default function FieldFilterForm({ values, onSelect, allowFilterSelect = true, -}) { +}: FieldFilterFormProps) { const { formatMessage, labels } = useMessages(); const [filter, setFilter] = useState('eq'); const [value, setValue] = useState(); @@ -21,7 +30,7 @@ export default function FieldFilterForm({ const formattedValues = useMemo(() => { const formatted = {}; - const format = val => { + const format = (val: string) => { formatted[val] = formatValue(val, name); return formatted[val]; }; @@ -56,7 +65,7 @@ export default function FieldFilterForm({ items={filters} value={filter} renderValue={renderFilterValue} - onChange={setFilter} + onChange={(key: any) => setFilter(key)} > {({ value, label }) => { return {label}; @@ -69,12 +78,12 @@ export default function FieldFilterForm({ items={values} value={value} renderValue={renderValue} - onChange={setValue} + onChange={(key: any) => setValue(key)} style={{ minWidth: '250px', }} > - {value => { + {(value: string) => { return {formattedValues[value]}; }} diff --git a/src/app/(main)/reports/[id]/FieldSelectForm.js b/src/app/(main)/reports/[id]/FieldSelectForm.tsx similarity index 65% rename from src/app/(main)/reports/[id]/FieldSelectForm.js rename to src/app/(main)/reports/[id]/FieldSelectForm.tsx index e7661511..dfd402cf 100644 --- a/src/app/(main)/reports/[id]/FieldSelectForm.js +++ b/src/app/(main)/reports/[id]/FieldSelectForm.tsx @@ -1,15 +1,26 @@ import { Menu, Item, Form, FormRow } from 'react-basics'; import { useMessages } from 'components/hooks'; import styles from './FieldSelectForm.module.css'; +import { Key } from 'react'; -export default function FieldSelectForm({ items, onSelect, showType = true }) { +export interface FieldSelectFormProps { + fields?: any[]; + onSelect?: (key: any) => void; + showType?: boolean; +} + +export default function FieldSelectForm({ + fields = [], + onSelect, + showType = true, +}: FieldSelectFormProps) { const { formatMessage, labels } = useMessages(); return ( - onSelect(items[key])}> - {items.map(({ name, label, type }, index) => { + onSelect(fields[key as any])}> + {fields.map(({ name, label, type }: any, index: Key) => { return (
{label || name}
diff --git a/src/app/(main)/reports/[id]/FilterSelectForm.js b/src/app/(main)/reports/[id]/FilterSelectForm.tsx similarity index 67% rename from src/app/(main)/reports/[id]/FilterSelectForm.js rename to src/app/(main)/reports/[id]/FilterSelectForm.tsx index 8457b7ff..4f9b9264 100644 --- a/src/app/(main)/reports/[id]/FilterSelectForm.js +++ b/src/app/(main)/reports/[id]/FilterSelectForm.tsx @@ -5,7 +5,7 @@ import FieldSelectForm from './FieldSelectForm'; import FieldFilterForm from './FieldFilterForm'; import { useApi } from 'components/hooks'; -function useValues(websiteId, type) { +function useValues(websiteId: string, type: string) { const now = Date.now(); const { get, useQuery } = useApi(); const { data, error, isLoading } = useQuery({ @@ -22,12 +22,24 @@ function useValues(websiteId, type) { return { data, error, isLoading }; } -export default function FilterSelectForm({ websiteId, items, onSelect, allowFilterSelect }) { - const [field, setField] = useState(); +export interface FilterSelectFormProps { + websiteId: string; + items: any[]; + onSelect?: (key: any) => void; + allowFilterSelect?: boolean; +} + +export default function FilterSelectForm({ + websiteId, + items, + onSelect, + allowFilterSelect, +}: FilterSelectFormProps) { + const [field, setField] = useState<{ name: string; label: string; type: string }>(); const { data, isLoading } = useValues(websiteId, field?.name); if (!field) { - return ; + return ; } if (isLoading) { diff --git a/src/app/(main)/reports/[id]/ParameterList.js b/src/app/(main)/reports/[id]/ParameterList.tsx similarity index 82% rename from src/app/(main)/reports/[id]/ParameterList.js rename to src/app/(main)/reports/[id]/ParameterList.tsx index bf77dd9d..eb1a646a 100644 --- a/src/app/(main)/reports/[id]/ParameterList.js +++ b/src/app/(main)/reports/[id]/ParameterList.tsx @@ -1,10 +1,17 @@ +import { ReactNode } from 'react'; import { Icon, TooltipPopup } from 'react-basics'; import Icons from 'components/icons'; import Empty from 'components/common/Empty'; import { useMessages } from 'components/hooks'; import styles from './ParameterList.module.css'; -export function ParameterList({ items = [], children, onRemove }) { +export interface ParameterListProps { + items: any[]; + children?: ReactNode | ((item: any) => ReactNode); + onRemove: (index: number, e: any) => void; +} + +export function ParameterList({ items = [], children, onRemove }: ParameterListProps) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/app/(main)/reports/[id]/PopupForm.js b/src/app/(main)/reports/[id]/PopupForm.tsx similarity index 59% rename from src/app/(main)/reports/[id]/PopupForm.js rename to src/app/(main)/reports/[id]/PopupForm.tsx index 6b99b00a..f2666199 100644 --- a/src/app/(main)/reports/[id]/PopupForm.js +++ b/src/app/(main)/reports/[id]/PopupForm.tsx @@ -1,7 +1,16 @@ +import { CSSProperties, ReactNode } from 'react'; import classNames from 'classnames'; import styles from './PopupForm.module.css'; -export function PopupForm({ className, style, children }) { +export function PopupForm({ + className, + style, + children, +}: { + className?: string; + style?: CSSProperties; + children: ReactNode; +}) { return (
-
- {children} -
+
{children}
); } diff --git a/src/app/(main)/reports/[id]/ReportBody.js b/src/app/(main)/reports/[id]/ReportBody.tsx similarity index 100% rename from src/app/(main)/reports/[id]/ReportBody.js rename to src/app/(main)/reports/[id]/ReportBody.tsx diff --git a/src/app/(main)/reports/[id]/ReportDetails.js b/src/app/(main)/reports/[id]/ReportDetails.tsx similarity index 90% rename from src/app/(main)/reports/[id]/ReportDetails.js rename to src/app/(main)/reports/[id]/ReportDetails.tsx index df91719a..e4d4688a 100644 --- a/src/app/(main)/reports/[id]/ReportDetails.js +++ b/src/app/(main)/reports/[id]/ReportDetails.tsx @@ -12,7 +12,7 @@ const reports = { retention: RetentionReport, }; -export default function ReportDetails({ reportId }) { +export default function ReportDetails({ reportId }: { reportId: string }) { const { get, useQuery } = useApi(); const { data: report } = useQuery({ queryKey: ['reports', reportId], diff --git a/src/app/(main)/reports/[id]/ReportHeader.js b/src/app/(main)/reports/[id]/ReportHeader.tsx similarity index 92% rename from src/app/(main)/reports/[id]/ReportHeader.js rename to src/app/(main)/reports/[id]/ReportHeader.tsx index ed3b9736..6d226344 100644 --- a/src/app/(main)/reports/[id]/ReportHeader.js +++ b/src/app/(main)/reports/[id]/ReportHeader.tsx @@ -12,8 +12,10 @@ export function ReportHeader({ icon }) { const { showToast } = useToasts(); const { post, useMutation } = useApi(); const router = useRouter(); - const { mutate: create, isLoading: isCreating } = useMutation(data => post(`/reports`, data)); - const { mutate: update, isLoading: isUpdating } = useMutation(data => + const { mutate: create, isLoading: isCreating } = useMutation((data: any) => + post(`/reports`, data), + ); + const { mutate: update, isLoading: isUpdating } = useMutation((data: any) => post(`/reports/${data.id}`, data), ); @@ -26,7 +28,7 @@ export function ReportHeader({ icon }) { create(report, { onSuccess: async ({ id }) => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); - router.push(`/reports/${id}`, null, { shallow: true }); + router.push(`/reports/${id}`); }, }); } else { @@ -38,11 +40,11 @@ export function ReportHeader({ icon }) { } }; - const handleNameChange = name => { + const handleNameChange = (name: string) => { updateReport({ name: name || defaultName }); }; - const handleDescriptionChange = description => { + const handleDescriptionChange = (description: string) => { updateReport({ description }); }; diff --git a/src/app/(main)/reports/[id]/ReportMenu.js b/src/app/(main)/reports/[id]/ReportMenu.tsx similarity index 100% rename from src/app/(main)/reports/[id]/ReportMenu.js rename to src/app/(main)/reports/[id]/ReportMenu.tsx diff --git a/src/app/(main)/reports/create/ReportTemplates.js b/src/app/(main)/reports/create/ReportTemplates.tsx similarity index 100% rename from src/app/(main)/reports/create/ReportTemplates.js rename to src/app/(main)/reports/create/ReportTemplates.tsx diff --git a/src/app/(main)/reports/event-data/EventDataParameters.js b/src/app/(main)/reports/event-data/EventDataParameters.tsx similarity index 91% rename from src/app/(main)/reports/event-data/EventDataParameters.js rename to src/app/(main)/reports/event-data/EventDataParameters.tsx index ac90fc2a..7a39131b 100644 --- a/src/app/(main)/reports/event-data/EventDataParameters.js +++ b/src/app/(main)/reports/event-data/EventDataParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics'; import Empty from 'components/common/Empty'; import Icons from 'components/icons'; @@ -29,7 +29,6 @@ function useFields(websiteId, startDate, endDate) { export function EventDataParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels, messages } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, fields, filters, groups } = parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -53,28 +52,28 @@ export function EventDataParameters() { runReport(values); }; - const handleAdd = (group, value) => { + const handleAdd = (group: string, value: any) => { const data = parameterData[group]; - if (!data.find(({ name }) => name === value.name)) { + if (!data.find(({ name }) => name === value?.name)) { updateReport({ parameters: { [group]: data.concat(value) } }); } }; - const handleRemove = (group, index) => { + const handleRemove = (group: string, index: number) => { const data = [...parameterData[group]]; data.splice(index, 1); updateReport({ parameters: { [group]: data } }); }; - const AddButton = ({ group }) => { + const AddButton = ({ group, onAdd }) => { return ( - {close => { + {(close: () => void) => { return ( ({ @@ -82,7 +81,7 @@ export function EventDataParameters() { type: DATA_TYPES[eventDataType], }))} group={group} - onAdd={handleAdd} + onAdd={onAdd} onClose={close} /> ); @@ -93,7 +92,7 @@ export function EventDataParameters() { }; return ( - + {!hasData && } {parametersSelected && diff --git a/src/app/(main)/reports/event-data/EventDataReport.js b/src/app/(main)/reports/event-data/EventDataReport.tsx similarity index 89% rename from src/app/(main)/reports/event-data/EventDataReport.js rename to src/app/(main)/reports/event-data/EventDataReport.tsx index e91cb4a2..8b4dc99c 100644 --- a/src/app/(main)/reports/event-data/EventDataReport.js +++ b/src/app/(main)/reports/event-data/EventDataReport.tsx @@ -11,7 +11,7 @@ const defaultParameters = { parameters: { fields: [], filters: [] }, }; -export default function EventDataReport({ reportId }) { +export default function EventDataReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/event-data/EventDataTable.js b/src/app/(main)/reports/event-data/EventDataTable.tsx similarity index 100% rename from src/app/(main)/reports/event-data/EventDataTable.js rename to src/app/(main)/reports/event-data/EventDataTable.tsx diff --git a/src/app/(main)/reports/funnel/FunnelChart.js b/src/app/(main)/reports/funnel/FunnelChart.tsx similarity index 52% rename from src/app/(main)/reports/funnel/FunnelChart.js rename to src/app/(main)/reports/funnel/FunnelChart.tsx index 7461afbc..923e8d56 100644 --- a/src/app/(main)/reports/funnel/FunnelChart.js +++ b/src/app/(main)/reports/funnel/FunnelChart.tsx @@ -1,13 +1,18 @@ -import { useCallback, useContext, useMemo } from 'react'; +import { JSX, useCallback, useContext, useMemo } from 'react'; import { Loading, StatusLight } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useTheme from 'components/hooks/useTheme'; import BarChart from 'components/metrics/BarChart'; import { formatLongNumber } from 'lib/format'; -import styles from './FunnelChart.module.css'; import { ReportContext } from '../[id]/Report'; +import styles from './FunnelChart.module.css'; -export function FunnelChart({ className, loading }) { +export interface FunnelChartProps { + className?: string; + isLoading?: boolean; +} + +export function FunnelChart({ className, isLoading }: FunnelChartProps) { const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { colors } = useTheme(); @@ -15,33 +20,39 @@ export function FunnelChart({ className, loading }) { const { parameters, data } = report || {}; const renderXLabel = useCallback( - (label, index) => { + (label: string, index: number) => { return parameters.urls[index]; }, [parameters], ); - const renderTooltipPopup = useCallback((setTooltipPopup, model) => { - const { opacity, labelColors, dataPoints } = model.tooltip; + const renderTooltipPopup = useCallback( + ( + setTooltipPopup: (arg0: JSX.Element) => void, + model: { tooltip: { opacity: any; labelColors: any; dataPoints: any } }, + ) => { + const { opacity, labelColors, dataPoints } = model.tooltip; - if (!dataPoints?.length || !opacity) { - setTooltipPopup(null); - return; - } + if (!dataPoints?.length || !opacity) { + setTooltipPopup(null); + return; + } - setTooltipPopup( - <> -
- {formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)} -
-
- - {formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)} - -
- , - ); - }, []); + setTooltipPopup( + <> +
+ {formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)} +
+
+ + {formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)} + +
+ , + ); + }, + [], + ); const datasets = useMemo(() => { return [ @@ -54,7 +65,7 @@ export function FunnelChart({ className, loading }) { ]; }, [data, colors, formatMessage, labels]); - if (loading) { + if (isLoading) { return ; } @@ -63,7 +74,7 @@ export function FunnelChart({ className, loading }) { className={className} datasets={datasets} unit="day" - loading={loading} + isLoading={isLoading} renderXLabel={renderXLabel} renderTooltipPopup={renderTooltipPopup} XAxisType="category" diff --git a/src/app/(main)/reports/funnel/FunnelParameters.js b/src/app/(main)/reports/funnel/FunnelParameters.tsx similarity index 84% rename from src/app/(main)/reports/funnel/FunnelParameters.js rename to src/app/(main)/reports/funnel/FunnelParameters.tsx index 135b5db8..0bb45415 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.js +++ b/src/app/(main)/reports/funnel/FunnelParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useMessages } from 'components/hooks'; import { Icon, @@ -21,13 +21,12 @@ import PopupForm from '../[id]/PopupForm'; export function FunnelParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, urls } = parameters || {}; const queryDisabled = !websiteId || !dateRange || urls?.length < 2; - const handleSubmit = (data, e) => { + const handleSubmit = (data: any, e: any) => { e.stopPropagation(); e.preventDefault(); if (!queryDisabled) { @@ -35,11 +34,11 @@ export function FunnelParameters() { } }; - const handleAddUrl = url => { + const handleAddUrl = (url: string) => { updateReport({ parameters: { urls: parameters.urls.concat(url) } }); }; - const handleRemoveUrl = (index, e) => { + const handleRemoveUrl = (index: number, e: any) => { e.stopPropagation(); const urls = [...parameters.urls]; urls.splice(index, 1); @@ -62,7 +61,7 @@ export function FunnelParameters() { }; return ( - + }> - + handleRemoveUrl(index, e)} + /> diff --git a/src/app/(main)/reports/funnel/FunnelReport.js b/src/app/(main)/reports/funnel/FunnelReport.tsx similarity index 100% rename from src/app/(main)/reports/funnel/FunnelReport.js rename to src/app/(main)/reports/funnel/FunnelReport.tsx diff --git a/src/app/(main)/reports/funnel/FunnelTable.js b/src/app/(main)/reports/funnel/FunnelTable.tsx similarity index 100% rename from src/app/(main)/reports/funnel/FunnelTable.js rename to src/app/(main)/reports/funnel/FunnelTable.tsx diff --git a/src/app/(main)/reports/funnel/UrlAddForm.js b/src/app/(main)/reports/funnel/UrlAddForm.tsx similarity index 86% rename from src/app/(main)/reports/funnel/UrlAddForm.js rename to src/app/(main)/reports/funnel/UrlAddForm.tsx index 3e77ce15..88c27ae9 100644 --- a/src/app/(main)/reports/funnel/UrlAddForm.js +++ b/src/app/(main)/reports/funnel/UrlAddForm.tsx @@ -3,7 +3,12 @@ import { useMessages } from 'components/hooks'; import { Button, Form, FormRow, TextField, Flexbox } from 'react-basics'; import styles from './UrlAddForm.module.css'; -export function UrlAddForm({ defaultValue = '', onAdd }) { +export interface UrlAddFormProps { + defaultValue?: string; + onAdd?: (url: string) => void; +} + +export function UrlAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) { const [url, setUrl] = useState(defaultValue); const { formatMessage, labels } = useMessages(); diff --git a/src/app/(main)/reports/insights/InsightsParameters.js b/src/app/(main)/reports/insights/InsightsParameters.tsx similarity index 93% rename from src/app/(main)/reports/insights/InsightsParameters.js rename to src/app/(main)/reports/insights/InsightsParameters.tsx index 91dd09f8..cd643eed 100644 --- a/src/app/(main)/reports/insights/InsightsParameters.js +++ b/src/app/(main)/reports/insights/InsightsParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useFormat, useMessages, useFilters } from 'components/hooks'; import { Form, @@ -24,7 +24,6 @@ export function InsightsParameters() { const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); const { filterLabels } = useFilters(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, fields, filters } = parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -72,7 +71,7 @@ export function InsightsParameters() { updateReport({ parameters: { [id]: data } }); }; - const AddButton = ({ id }) => { + const AddButton = ({ id, onAdd }) => { return ( @@ -84,8 +83,8 @@ export function InsightsParameters() { {id === 'fields' && ( )} @@ -93,7 +92,7 @@ export function InsightsParameters() { )} @@ -103,7 +102,7 @@ export function InsightsParameters() { }; return ( - + {parametersSelected && parameterGroups.map(({ id, label }) => { diff --git a/src/app/(main)/reports/insights/InsightsReport.js b/src/app/(main)/reports/insights/InsightsReport.tsx similarity index 90% rename from src/app/(main)/reports/insights/InsightsReport.js rename to src/app/(main)/reports/insights/InsightsReport.tsx index f99e187b..b90ff396 100644 --- a/src/app/(main)/reports/insights/InsightsReport.js +++ b/src/app/(main)/reports/insights/InsightsReport.tsx @@ -13,7 +13,7 @@ const defaultParameters = { parameters: { fields: [], filters: [] }, }; -export default function InsightsReport({ reportId }) { +export default function InsightsReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/insights/InsightsTable.js b/src/app/(main)/reports/insights/InsightsTable.tsx similarity index 96% rename from src/app/(main)/reports/insights/InsightsTable.js rename to src/app/(main)/reports/insights/InsightsTable.tsx index 4194fee8..a4517698 100644 --- a/src/app/(main)/reports/insights/InsightsTable.js +++ b/src/app/(main)/reports/insights/InsightsTable.tsx @@ -5,7 +5,7 @@ import { ReportContext } from '../[id]/Report'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; export function InsightsTable() { - const [fields, setFields] = useState(); + const [fields, setFields] = useState([]); const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); diff --git a/src/app/(main)/reports/retention/RetentionParameters.js b/src/app/(main)/reports/retention/RetentionParameters.tsx similarity index 87% rename from src/app/(main)/reports/retention/RetentionParameters.js rename to src/app/(main)/reports/retention/RetentionParameters.tsx index 762a313d..fc168695 100644 --- a/src/app/(main)/reports/retention/RetentionParameters.js +++ b/src/app/(main)/reports/retention/RetentionParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useMessages } from 'components/hooks'; import { Form, FormButtons, FormRow, SubmitButton } from 'react-basics'; import { ReportContext } from '../[id]/Report'; @@ -9,14 +9,13 @@ import { parseDateRange } from 'lib/date'; export function RetentionParameters() { const { report, runReport, isRunning, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange } = parameters || {}; const { startDate } = dateRange || {}; const queryDisabled = !websiteId || !dateRange; - const handleSubmit = (data, e) => { + const handleSubmit = (data: any, e: any) => { e.stopPropagation(); e.preventDefault(); @@ -30,7 +29,7 @@ export function RetentionParameters() { }; return ( - + diff --git a/src/app/(main)/reports/retention/RetentionReport.js b/src/app/(main)/reports/retention/RetentionReport.tsx similarity index 92% rename from src/app/(main)/reports/retention/RetentionReport.js rename to src/app/(main)/reports/retention/RetentionReport.tsx index ae42e76b..35f0fcb1 100644 --- a/src/app/(main)/reports/retention/RetentionReport.js +++ b/src/app/(main)/reports/retention/RetentionReport.tsx @@ -19,7 +19,7 @@ const defaultParameters = { }, }; -export default function RetentionReport({ reportId }) { +export default function RetentionReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/retention/RetentionTable.js b/src/app/(main)/reports/retention/RetentionTable.tsx similarity index 96% rename from src/app/(main)/reports/retention/RetentionTable.js rename to src/app/(main)/reports/retention/RetentionTable.tsx index a71fae6f..d2e7f129 100644 --- a/src/app/(main)/reports/retention/RetentionTable.js +++ b/src/app/(main)/reports/retention/RetentionTable.tsx @@ -18,7 +18,7 @@ export function RetentionTable({ days = DAYS }) { return ; } - const rows = data.reduce((arr, row) => { + const rows = data.reduce((arr: any[], row: { date: any; visitors: any; day: any }) => { const { date, visitors, day } = row; if (day === 0) { return arr.concat({ diff --git a/src/app/(main)/settings/profile/DateRangeSetting.js b/src/app/(main)/settings/profile/DateRangeSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/DateRangeSetting.js rename to src/app/(main)/settings/profile/DateRangeSetting.tsx diff --git a/src/app/(main)/settings/profile/LanguageSetting.js b/src/app/(main)/settings/profile/LanguageSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/LanguageSetting.js rename to src/app/(main)/settings/profile/LanguageSetting.tsx diff --git a/src/app/(main)/settings/profile/PasswordChangeButton.js b/src/app/(main)/settings/profile/PasswordChangeButton.tsx similarity index 100% rename from src/app/(main)/settings/profile/PasswordChangeButton.js rename to src/app/(main)/settings/profile/PasswordChangeButton.tsx diff --git a/src/app/(main)/settings/profile/PasswordEditForm.js b/src/app/(main)/settings/profile/PasswordEditForm.tsx similarity index 91% rename from src/app/(main)/settings/profile/PasswordEditForm.js rename to src/app/(main)/settings/profile/PasswordEditForm.tsx index 39ecfb77..7062ea59 100644 --- a/src/app/(main)/settings/profile/PasswordEditForm.js +++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx @@ -6,10 +6,10 @@ import useMessages from 'components/hooks/useMessages'; export function PasswordEditForm({ onSave, onClose }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/me/password', data)); + const { mutate, error, isLoading } = useMutation((data: any) => post('/me/password', data)); const ref = useRef(null); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); @@ -18,7 +18,7 @@ export function PasswordEditForm({ onSave, onClose }) { }); }; - const samePassword = value => { + const samePassword = (value: string) => { if (value !== ref?.current?.getValues('newPassword')) { return formatMessage(messages.noMatchPassword); } diff --git a/src/app/(main)/settings/profile/ProfileHeader.js b/src/app/(main)/settings/profile/ProfileHeader.tsx similarity index 100% rename from src/app/(main)/settings/profile/ProfileHeader.js rename to src/app/(main)/settings/profile/ProfileHeader.tsx diff --git a/src/app/(main)/settings/profile/ProfileSettings.js b/src/app/(main)/settings/profile/ProfileSettings.tsx similarity index 100% rename from src/app/(main)/settings/profile/ProfileSettings.js rename to src/app/(main)/settings/profile/ProfileSettings.tsx diff --git a/src/app/(main)/settings/profile/ThemeSetting.js b/src/app/(main)/settings/profile/ThemeSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/ThemeSetting.js rename to src/app/(main)/settings/profile/ThemeSetting.tsx diff --git a/src/app/(main)/settings/profile/TimezoneSetting.js b/src/app/(main)/settings/profile/TimezoneSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/TimezoneSetting.js rename to src/app/(main)/settings/profile/TimezoneSetting.tsx diff --git a/src/app/(main)/settings/teams/TeamAddForm.js b/src/app/(main)/settings/teams/TeamAddForm.tsx similarity index 72% rename from src/app/(main)/settings/teams/TeamAddForm.js rename to src/app/(main)/settings/teams/TeamAddForm.tsx index b8bb8c3a..5a242cb9 100644 --- a/src/app/(main)/settings/teams/TeamAddForm.js +++ b/src/app/(main)/settings/teams/TeamAddForm.tsx @@ -1,4 +1,3 @@ -import { useRef } from 'react'; import { Form, FormRow, @@ -12,11 +11,12 @@ import { setValue } from 'store/cache'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; -export function TeamAddForm({ onSave, onClose }) { +export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/teams', data)); - const ref = useRef(null); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/teams', data), + }); const handleSubmit = async data => { mutate(data, { @@ -29,17 +29,17 @@ export function TeamAddForm({ onSave, onClose }) { }; return ( - + - + {formatMessage(labels.save)} - diff --git a/src/app/(main)/settings/teams/TeamDeleteButton.js b/src/app/(main)/settings/teams/TeamDeleteButton.tsx similarity index 79% rename from src/app/(main)/settings/teams/TeamDeleteButton.js rename to src/app/(main)/settings/teams/TeamDeleteButton.tsx index 5e4a41ea..36a1a1f8 100644 --- a/src/app/(main)/settings/teams/TeamDeleteButton.js +++ b/src/app/(main)/settings/teams/TeamDeleteButton.tsx @@ -2,7 +2,15 @@ import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import TeamDeleteForm from './TeamDeleteForm'; -export function TeamDeleteButton({ teamId, teamName, onDelete }) { +export function TeamDeleteButton({ + teamId, + teamName, + onDelete, +}: { + teamId: string; + teamName: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); return ( @@ -14,7 +22,7 @@ export function TeamDeleteButton({ teamId, teamName, onDelete }) { {formatMessage(labels.delete)} - {close => ( + {(close: any) => ( )} diff --git a/src/app/(main)/settings/teams/TeamDeleteForm.js b/src/app/(main)/settings/teams/TeamDeleteForm.tsx similarity index 72% rename from src/app/(main)/settings/teams/TeamDeleteForm.js rename to src/app/(main)/settings/teams/TeamDeleteForm.tsx index 9b80668a..00eedb17 100644 --- a/src/app/(main)/settings/teams/TeamDeleteForm.js +++ b/src/app/(main)/settings/teams/TeamDeleteForm.tsx @@ -3,10 +3,22 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamDeleteForm({ teamId, teamName, onSave, onClose }) { +export function TeamDeleteForm({ + teamId, + teamName, + onSave, + onClose, +}: { + teamId: string; + teamName: string; + onSave: () => void; + onClose: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => del(`/teams/${teamId}`, data), + }); const handleSubmit = async data => { mutate(data, { @@ -24,7 +36,7 @@ export function TeamDeleteForm({ teamId, teamName, onSave, onClose }) { {teamName} }} />

- + {formatMessage(labels.delete)} diff --git a/src/app/(main)/settings/teams/TeamJoinForm.js b/src/app/(main)/settings/teams/TeamJoinForm.tsx similarity index 85% rename from src/app/(main)/settings/teams/TeamJoinForm.js rename to src/app/(main)/settings/teams/TeamJoinForm.tsx index 528e1d75..5cd38225 100644 --- a/src/app/(main)/settings/teams/TeamJoinForm.js +++ b/src/app/(main)/settings/teams/TeamJoinForm.tsx @@ -12,10 +12,10 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamJoinForm({ onSave, onClose }) { +export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { const { formatMessage, labels, getMessage } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post('/teams/join', data)); + const { mutate, error } = useMutation({ mutationFn: (data: any) => post('/teams/join', data) }); const ref = useRef(null); const handleSubmit = async data => { diff --git a/src/app/(main)/settings/teams/TeamLeaveButton.js b/src/app/(main)/settings/teams/TeamLeaveButton.tsx similarity index 87% rename from src/app/(main)/settings/teams/TeamLeaveButton.js rename to src/app/(main)/settings/teams/TeamLeaveButton.tsx index 7b98f082..97676e17 100644 --- a/src/app/(main)/settings/teams/TeamLeaveButton.js +++ b/src/app/(main)/settings/teams/TeamLeaveButton.tsx @@ -4,7 +4,15 @@ import useLocale from 'components/hooks/useLocale'; import useUser from 'components/hooks/useUser'; import TeamDeleteForm from './TeamLeaveForm'; -export function TeamLeaveButton({ teamId, teamName, onLeave }) { +export function TeamLeaveButton({ + teamId, + teamName, + onLeave, +}: { + teamId: string; + teamName: string; + onLeave?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { dir } = useLocale(); const { user } = useUser(); diff --git a/src/app/(main)/settings/teams/TeamLeaveForm.js b/src/app/(main)/settings/teams/TeamLeaveForm.tsx similarity index 60% rename from src/app/(main)/settings/teams/TeamLeaveForm.js rename to src/app/(main)/settings/teams/TeamLeaveForm.tsx index a9b6922a..3b4d4703 100644 --- a/src/app/(main)/settings/teams/TeamLeaveForm.js +++ b/src/app/(main)/settings/teams/TeamLeaveForm.tsx @@ -3,22 +3,33 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) { +export function TeamLeaveForm({ + teamId, + userId, + teamName, + onSave, + onClose, +}: { + teamId: string; + userId: string; + teamName: string; + onSave: () => void; + onClose: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(() => del(`/teams/${teamId}/users/${userId}`)); + const { mutate, error, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/users/${userId}`), + }); const handleSubmit = async () => { - mutate( - {}, - { - onSuccess: async () => { - setValue('team:members', Date.now()); - onSave(); - onClose(); - }, + mutate(null, { + onSuccess: async () => { + setValue('team:members', Date.now()); + onSave(); + onClose(); }, - ); + }); }; return ( @@ -27,7 +38,7 @@ export function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) { {teamName} }} />

- + {formatMessage(labels.leave)} diff --git a/src/app/(main)/settings/teams/TeamsAddButton.js b/src/app/(main)/settings/teams/TeamsAddButton.tsx similarity index 79% rename from src/app/(main)/settings/teams/TeamsAddButton.js rename to src/app/(main)/settings/teams/TeamsAddButton.tsx index b7850812..09f9ecbb 100644 --- a/src/app/(main)/settings/teams/TeamsAddButton.js +++ b/src/app/(main)/settings/teams/TeamsAddButton.tsx @@ -3,7 +3,7 @@ import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; import TeamAddForm from './TeamAddForm'; -export function TeamsAddButton({ onAdd }) { +export function TeamsAddButton({ onAdd }: { onAdd?: () => void }) { const { formatMessage, labels } = useMessages(); return ( @@ -15,7 +15,7 @@ export function TeamsAddButton({ onAdd }) { {formatMessage(labels.createTeam)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/teams/TeamsDataTable.js b/src/app/(main)/settings/teams/TeamsDataTable.tsx similarity index 88% rename from src/app/(main)/settings/teams/TeamsDataTable.js rename to src/app/(main)/settings/teams/TeamsDataTable.tsx index a8c9e21d..2424e464 100644 --- a/src/app/(main)/settings/teams/TeamsDataTable.js +++ b/src/app/(main)/settings/teams/TeamsDataTable.tsx @@ -7,10 +7,10 @@ import useCache from 'store/cache'; export function TeamsDataTable() { const { get } = useApi(); - const modified = useCache(state => state?.teams); + const modified = useCache((state: any) => state?.teams); const queryResult = useFilterQuery({ queryKey: ['teams', { modified }], - queryFn: params => { + queryFn: (params: any) => { return get(`/teams`, { ...params, }); diff --git a/src/app/(main)/settings/teams/TeamsHeader.js b/src/app/(main)/settings/teams/TeamsHeader.tsx similarity index 100% rename from src/app/(main)/settings/teams/TeamsHeader.js rename to src/app/(main)/settings/teams/TeamsHeader.tsx diff --git a/src/app/(main)/settings/teams/TeamsJoinButton.js b/src/app/(main)/settings/teams/TeamsJoinButton.tsx similarity index 100% rename from src/app/(main)/settings/teams/TeamsJoinButton.js rename to src/app/(main)/settings/teams/TeamsJoinButton.tsx diff --git a/src/app/(main)/settings/teams/TeamsTable.js b/src/app/(main)/settings/teams/TeamsTable.tsx similarity index 96% rename from src/app/(main)/settings/teams/TeamsTable.js rename to src/app/(main)/settings/teams/TeamsTable.tsx index 1f7f1da4..70a7bebb 100644 --- a/src/app/(main)/settings/teams/TeamsTable.js +++ b/src/app/(main)/settings/teams/TeamsTable.tsx @@ -7,7 +7,7 @@ import { Button, GridColumn, GridTable, Icon, Icons, Text, useBreakpoint } from import TeamDeleteButton from './TeamDeleteButton'; import TeamLeaveButton from './TeamLeaveButton'; -export function TeamsTable({ data = [] }) { +export function TeamsTable({ data = [] }: { data: any[] }) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/teams/WebsiteTags.js b/src/app/(main)/settings/teams/WebsiteTags.tsx similarity index 83% rename from src/app/(main)/settings/teams/WebsiteTags.js rename to src/app/(main)/settings/teams/WebsiteTags.tsx index c17d5763..4a0f109d 100644 --- a/src/app/(main)/settings/teams/WebsiteTags.js +++ b/src/app/(main)/settings/teams/WebsiteTags.tsx @@ -1,7 +1,15 @@ import { Button, Icon, Icons, Text } from 'react-basics'; import styles from './WebsiteTags.module.css'; -export function WebsiteTags({ items = [], websites = [], onClick }) { +export function WebsiteTags({ + items = [], + websites = [], + onClick, +}: { + items: any[]; + websites: any[]; + onClick: (e: Event) => void; +}) { if (websites.length === 0) { return null; } diff --git a/src/app/(main)/settings/teams/[id]/TeamEditForm.js b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx similarity index 100% rename from src/app/(main)/settings/teams/[id]/TeamEditForm.js rename to src/app/(main)/settings/teams/[id]/TeamEditForm.tsx diff --git a/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js b/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx similarity index 59% rename from src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js rename to src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx index 603adae3..cef2977e 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js +++ b/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx @@ -3,28 +3,37 @@ import useMessages from 'components/hooks/useMessages'; import { Icon, Icons, LoadingButton, Text } from 'react-basics'; import { setValue } from 'store/cache'; -export function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) { +export function TeamMemberRemoveButton({ + teamId, + userId, + disabled, + onSave, +}: { + teamId: string; + userId: string; + disabled?: boolean; + onSave?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/users/${userId}`)); + const { mutate, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/users/${userId}`), + }); const handleRemoveTeamMember = () => { - mutate( - {}, - { - onSuccess: () => { - setValue('team:members', Date.now()); - onSave?.(); - }, + mutate(null, { + onSuccess: () => { + setValue('team:members', Date.now()); + onSave?.(); }, - ); + }); }; return ( handleRemoveTeamMember()} disabled={disabled} - isLoading={isLoading} + isLoading={isPending} > diff --git a/src/app/(main)/settings/teams/[id]/TeamMembers.js b/src/app/(main)/settings/teams/[id]/TeamMembers.tsx similarity index 89% rename from src/app/(main)/settings/teams/[id]/TeamMembers.js rename to src/app/(main)/settings/teams/[id]/TeamMembers.tsx index 1b0c0d18..588a5a52 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembers.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembers.tsx @@ -4,7 +4,7 @@ import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; import useCache from 'store/cache'; -export function TeamMembers({ teamId, readOnly }) { +export function TeamMembers({ teamId, readOnly }: { teamId: string; readOnly: boolean }) { const { get } = useApi(); const modified = useCache(state => state?.['team:members']); const queryResult = useFilterQuery({ diff --git a/src/app/(main)/settings/teams/[id]/TeamMembersTable.js b/src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx similarity index 90% rename from src/app/(main)/settings/teams/[id]/TeamMembersTable.js rename to src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx index 9a402d44..a08c746b 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembersTable.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx @@ -4,7 +4,15 @@ import useUser from 'components/hooks/useUser'; import { ROLES } from 'lib/constants'; import TeamMemberRemoveButton from './TeamMemberRemoveButton'; -export function TeamMembersTable({ data = [], teamId, readOnly }) { +export function TeamMembersTable({ + data = [], + teamId, + readOnly, +}: { + data: any[]; + teamId: string; + readOnly: boolean; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/teams/[id]/TeamSettings.js b/src/app/(main)/settings/teams/[id]/TeamSettings.tsx similarity index 91% rename from src/app/(main)/settings/teams/[id]/TeamSettings.js rename to src/app/(main)/settings/teams/[id]/TeamSettings.tsx index d7065f97..cf5ae35c 100644 --- a/src/app/(main)/settings/teams/[id]/TeamSettings.js +++ b/src/app/(main)/settings/teams/[id]/TeamSettings.tsx @@ -10,7 +10,7 @@ import TeamEditForm from './TeamEditForm'; import TeamMembers from './TeamMembers'; import TeamWebsites from './TeamWebsites'; -export function TeamSettings({ teamId }) { +export function TeamSettings({ teamId }: { teamId: string }) { const { formatMessage, labels, messages } = useMessages(); const { user } = useUser(); const [values, setValues] = useState(null); @@ -24,7 +24,7 @@ export function TeamSettings({ teamId }) { return get(`/teams/${teamId}`); } }, - cacheTime: 0, + gcTime: 0, }); const canEdit = data?.teamUser?.find( ({ userId, role }) => role === ROLES.teamOwner && userId === user.id, @@ -48,7 +48,7 @@ export function TeamSettings({ teamId }) { return ( - + setTab(value)} style={{ marginBottom: 30 }}> {formatMessage(labels.details)} {formatMessage(labels.members)} {formatMessage(labels.websites)} diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx similarity index 86% rename from src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js rename to src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx index 004ae54f..64a0c58e 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx @@ -7,11 +7,21 @@ import Empty from 'components/common/Empty'; import { setValue } from 'store/cache'; import { useUser } from 'components/hooks'; -export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { +export function TeamWebsiteAddForm({ + teamId, + onSave, + onClose, +}: { + teamId: string; + onSave: () => void; + onClose: () => void; +}) { const { user } = useUser(); const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/teams/${teamId}/websites`, data), + }); const { data: websites, isLoading } = useQuery({ queryKey: ['websites'], queryFn: () => get('/websites'), @@ -42,7 +52,7 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { {!isLoading && !hasData && } {hasData && ( - + {row => ( { - queryResult.refetch(); + queryResult.query.refetch(); }; return ( @@ -43,7 +43,9 @@ export function TeamWebsites({ teamId }) { - {({ data }) => } + {({ data }) => ( + + )} ); diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js b/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx similarity index 85% rename from src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js rename to src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx index 0f802212..29b95816 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx @@ -4,7 +4,15 @@ import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; import TeamWebsiteRemoveButton from './TeamWebsiteRemoveButton'; -export function TeamWebsitesTable({ data = [], onRemove }) { +export function TeamWebsitesTable({ + data = [], + readOnly, + onRemove, +}: { + data: any[]; + readOnly: boolean; + onRemove: () => void; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); @@ -17,7 +25,7 @@ export function TeamWebsitesTable({ data = [], onRemove }) { const { id: teamId, teamUser } = row.teamWebsite[0].team; const { id: websiteId, userId } = row; const owner = teamUser[0]; - const canRemove = user.id === userId || user.id === owner.userId; + const canRemove = !readOnly && (user.id === userId || user.id === owner.userId); return ( <> {canRemove && ( diff --git a/src/app/(main)/settings/teams/[id]/page.js b/src/app/(main)/settings/teams/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/teams/[id]/page.js rename to src/app/(main)/settings/teams/[id]/page.tsx diff --git a/src/app/(main)/settings/users/UserAddButton.js b/src/app/(main)/settings/users/UserAddButton.tsx similarity index 92% rename from src/app/(main)/settings/users/UserAddButton.js rename to src/app/(main)/settings/users/UserAddButton.tsx index 0f4bf734..7f08107c 100644 --- a/src/app/(main)/settings/users/UserAddButton.js +++ b/src/app/(main)/settings/users/UserAddButton.tsx @@ -3,7 +3,7 @@ import UserAddForm from './UserAddForm'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function UserAddButton({ onSave }) { +export function UserAddButton({ onSave }: { onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { showToast } = useToasts(); diff --git a/src/app/(main)/settings/users/UserAddForm.js b/src/app/(main)/settings/users/UserAddForm.tsx similarity index 92% rename from src/app/(main)/settings/users/UserAddForm.js rename to src/app/(main)/settings/users/UserAddForm.tsx index 38c1bedd..11066a24 100644 --- a/src/app/(main)/settings/users/UserAddForm.js +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -16,7 +16,9 @@ import useMessages from 'components/hooks/useMessages'; export function UserAddForm({ onSave, onClose }) { const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post(`/users`, data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post(`/users`, data), + }); const { formatMessage, labels } = useMessages(); const handleSubmit = async data => { @@ -65,7 +67,7 @@ export function UserAddForm({ onSave, onClose }) { {formatMessage(labels.save)} - diff --git a/src/app/(main)/settings/users/UserDeleteButton.js b/src/app/(main)/settings/users/UserDeleteButton.tsx similarity index 85% rename from src/app/(main)/settings/users/UserDeleteButton.js rename to src/app/(main)/settings/users/UserDeleteButton.tsx index 22d93171..2b93c138 100644 --- a/src/app/(main)/settings/users/UserDeleteButton.js +++ b/src/app/(main)/settings/users/UserDeleteButton.tsx @@ -3,7 +3,15 @@ import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; import UserDeleteForm from './UserDeleteForm'; -export function UserDeleteButton({ userId, username, onDelete }) { +export function UserDeleteButton({ + userId, + username, + onDelete, +}: { + userId: string; + username: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); diff --git a/src/app/(main)/settings/users/UserDeleteForm.js b/src/app/(main)/settings/users/UserDeleteForm.tsx similarity index 72% rename from src/app/(main)/settings/users/UserDeleteForm.js rename to src/app/(main)/settings/users/UserDeleteForm.tsx index 5a47fdc1..eaa8e481 100644 --- a/src/app/(main)/settings/users/UserDeleteForm.js +++ b/src/app/(main)/settings/users/UserDeleteForm.tsx @@ -1,14 +1,13 @@ -import { useMutation } from '@tanstack/react-query'; import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; export function UserDeleteForm({ userId, username, onSave, onClose }) { const { formatMessage, FormattedMessage, labels, messages } = useMessages(); - const { del } = useApi(); - const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`)); + const { del, useMutation } = useApi(); + const { mutate, error, isPending } = useMutation({ mutationFn: () => del(`/users/${userId}`) }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); @@ -23,10 +22,10 @@ export function UserDeleteForm({ userId, username, onSave, onClose }) { {username} }} />

- + {formatMessage(labels.delete)} - diff --git a/src/app/(main)/settings/users/UserEditForm.js b/src/app/(main)/settings/users/UserEditForm.tsx similarity index 82% rename from src/app/(main)/settings/users/UserEditForm.js rename to src/app/(main)/settings/users/UserEditForm.tsx index 157c9ad6..0b823a94 100644 --- a/src/app/(main)/settings/users/UserEditForm.js +++ b/src/app/(main)/settings/users/UserEditForm.tsx @@ -13,14 +13,30 @@ import useApi from 'components/hooks/useApi'; import { ROLES } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function UserEditForm({ userId, data, onSave }) { +export function UserEditForm({ + userId, + data, + onSave, +}: { + userId: string; + data: any[]; + onSave: (data: any) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(({ username, password, role }) => - post(`/users/${userId}`, { username, password, role }), - ); + const { mutate, error } = useMutation({ + mutationFn: ({ + username, + password, + role, + }: { + username: string; + password: string; + role: string; + }) => post(`/users/${userId}`, { username, password, role }), + }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); diff --git a/src/app/(main)/settings/users/UserWebsites.js b/src/app/(main)/settings/users/UserWebsites.js deleted file mode 100644 index cd53c512..00000000 --- a/src/app/(main)/settings/users/UserWebsites.js +++ /dev/null @@ -1,36 +0,0 @@ -import Page from 'components/layout/Page'; -import useApi from 'components/hooks/useApi'; -import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; -import useApiFilter from 'components/hooks/useApiFilter'; - -export function UserWebsites({ userId }) { - const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = - useApiFilter(); - const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery({ - queryKey: ['user:websites', userId, filter, page, pageSize], - queryFn: () => - get(`/users/${userId}/websites`, { - filter, - page, - pageSize, - }), - }); - const hasData = data && data.length !== 0; - - return ( - - {hasData && ( - - )} - - ); -} - -export default UserWebsites; diff --git a/src/app/(main)/settings/users/UserWebsites.tsx b/src/app/(main)/settings/users/UserWebsites.tsx new file mode 100644 index 00000000..2d06e82a --- /dev/null +++ b/src/app/(main)/settings/users/UserWebsites.tsx @@ -0,0 +1,26 @@ +import Page from 'components/layout/Page'; +import useApi from 'components/hooks/useApi'; +import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; +import useFilterQuery from 'components/hooks/useFilterQuery'; +import DataTable from 'components/common/DataTable'; + +export function UserWebsites({ userId }) { + const { get } = useApi(); + const queryResult = useFilterQuery({ + queryKey: ['user:websites', userId], + queryFn: (params: any) => get(`/users/${userId}/websites`, params), + }); + const hasData = queryResult.result && queryResult.result.data.length !== 0; + + return ( + + {hasData && ( + + {({ data }) => } + + )} + + ); +} + +export default UserWebsites; diff --git a/src/app/(main)/settings/users/UsersDataTable.js b/src/app/(main)/settings/users/UsersDataTable.tsx similarity index 82% rename from src/app/(main)/settings/users/UsersDataTable.js rename to src/app/(main)/settings/users/UsersDataTable.tsx index 91125309..b7716451 100644 --- a/src/app/(main)/settings/users/UsersDataTable.js +++ b/src/app/(main)/settings/users/UsersDataTable.tsx @@ -8,10 +8,10 @@ import useCache from 'store/cache'; export function UsersDataTable() { const { get } = useApi(); - const modified = useCache(state => state?.users); + const modified = useCache((state: any) => state?.users); const queryResult = useFilterQuery({ queryKey: ['users', { modified }], - queryFn: params => get(`/users`, params), + queryFn: (params: { [key: string]: any }) => get(`/users`, params), }); return ( diff --git a/src/app/(main)/settings/users/UsersHeader.js b/src/app/(main)/settings/users/UsersHeader.tsx similarity index 85% rename from src/app/(main)/settings/users/UsersHeader.js rename to src/app/(main)/settings/users/UsersHeader.tsx index caf1f913..0901a6fb 100644 --- a/src/app/(main)/settings/users/UsersHeader.js +++ b/src/app/(main)/settings/users/UsersHeader.tsx @@ -3,7 +3,7 @@ import PageHeader from 'components/layout/PageHeader'; import useMessages from 'components/hooks/useMessages'; import UserAddButton from './UserAddButton'; -export function UsersHeader({ onAdd }) { +export function UsersHeader({ onAdd }: { onAdd?: () => void }) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/app/(main)/settings/users/UsersTable.js b/src/app/(main)/settings/users/UsersTable.tsx similarity index 96% rename from src/app/(main)/settings/users/UsersTable.js rename to src/app/(main)/settings/users/UsersTable.tsx index a0b5aba1..2b840b64 100644 --- a/src/app/(main)/settings/users/UsersTable.js +++ b/src/app/(main)/settings/users/UsersTable.tsx @@ -6,7 +6,7 @@ import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; import UserDeleteButton from './UserDeleteButton'; -export function UsersTable({ data = [] }) { +export function UsersTable({ data = [] }: { data: any[] }) { const { formatMessage, labels } = useMessages(); const { dateLocale } = useLocale(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.js index f635bb05..3d8a92ea 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.js @@ -21,7 +21,7 @@ export function UserSettings({ userId }) { return get(`/users/${userId}`); } }, - cacheTime: 0, + gcTime: 0, }); const handleSave = data => { diff --git a/src/app/(main)/settings/users/[id]/page.js b/src/app/(main)/settings/users/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/users/[id]/page.js rename to src/app/(main)/settings/users/[id]/page.tsx diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.js b/src/app/(main)/settings/websites/WebsiteAddButton.tsx similarity index 92% rename from src/app/(main)/settings/websites/WebsiteAddButton.js rename to src/app/(main)/settings/websites/WebsiteAddButton.tsx index b1a69429..16681b0e 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.js +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -3,7 +3,7 @@ import WebsiteAddForm from './WebsiteAddForm'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function WebsiteAddButton({ onSave }) { +export function WebsiteAddButton({ onSave }: { onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { showToast } = useToasts(); diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.tsx b/src/app/(main)/settings/websites/WebsiteAddForm.tsx index 99624103..9f3ba178 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -14,7 +14,9 @@ import useMessages from 'components/hooks/useMessages'; export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/websites', data), + }); const handleSubmit = async (data: any) => { mutate(data, { @@ -26,7 +28,7 @@ export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClo }; return ( - + @@ -48,7 +50,7 @@ export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClo {formatMessage(labels.save)}
{onClose && ( - )} diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.js index 79ba08fc..ffda838c 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.js @@ -20,7 +20,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl queryKey: ['website', websiteId], queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, - cacheTime: 0, + gcTime: 0, }); const [values, setValues] = useState(null); const [tab, setTab] = useState('details'); diff --git a/src/app/(main)/settings/websites/WebsitesHeader.js b/src/app/(main)/settings/websites/WebsitesHeader.tsx similarity index 100% rename from src/app/(main)/settings/websites/WebsitesHeader.js rename to src/app/(main)/settings/websites/WebsitesHeader.tsx diff --git a/src/app/(main)/settings/websites/WebsitesTable.js b/src/app/(main)/settings/websites/WebsitesTable.tsx similarity index 90% rename from src/app/(main)/settings/websites/WebsitesTable.js rename to src/app/(main)/settings/websites/WebsitesTable.tsx index eef3f7d4..02434448 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.js +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -1,8 +1,18 @@ +import { ReactNode } from 'react'; import Link from 'next/link'; import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; +export interface WebsitesTableProps { + data: any[]; + showTeam?: boolean; + showActions?: boolean; + allowEdit?: boolean; + allowView?: boolean; + children?: ReactNode; +} + export function WebsitesTable({ data = [], showTeam, @@ -10,7 +20,7 @@ export function WebsitesTable({ allowEdit, allowView, children, -}) { +}: WebsitesTableProps) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/websites/[id]/ShareUrl.js b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx similarity index 91% rename from src/app/(main)/settings/websites/[id]/ShareUrl.js rename to src/app/(main)/settings/websites/[id]/ShareUrl.tsx index 72ba217c..19149035 100644 --- a/src/app/(main)/settings/websites/[id]/ShareUrl.js +++ b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx @@ -20,9 +20,9 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(({ shareId }) => - post(`/websites/${websiteId}`, { shareId }), - ); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + }); const ref = useRef(null); const url = useMemo( () => @@ -32,7 +32,7 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { [id, name], ); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); @@ -50,7 +50,7 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { setId(id); }; - const handleCheck = checked => { + const handleCheck = (checked: boolean) => { const data = { shareId: checked ? generateId() : null }; mutate(data, { onSuccess: async () => { diff --git a/src/app/(main)/settings/websites/[id]/TrackingCode.js b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx similarity index 87% rename from src/app/(main)/settings/websites/[id]/TrackingCode.js rename to src/app/(main)/settings/websites/[id]/TrackingCode.tsx index 368368d7..b6bbdf89 100644 --- a/src/app/(main)/settings/websites/[id]/TrackingCode.js +++ b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx @@ -2,7 +2,13 @@ import { TextArea } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useConfig from 'components/hooks/useConfig'; -export function TrackingCode({ websiteId, analyticsUrl }) { +export function TrackingCode({ + websiteId, + analyticsUrl, +}: { + websiteId: string; + analyticsUrl: string; +}) { const { formatMessage, messages } = useMessages(); const config = useConfig(); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteData.js b/src/app/(main)/settings/websites/[id]/WebsiteData.tsx similarity index 92% rename from src/app/(main)/settings/websites/[id]/WebsiteData.js rename to src/app/(main)/settings/websites/[id]/WebsiteData.tsx index 07dc9257..b4bfe609 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteData.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteData.tsx @@ -3,7 +3,13 @@ import WebsiteDeleteForm from './WebsiteDeleteForm'; import WebsiteResetForm from './WebsiteResetForm'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteData({ websiteId, onSave }) { +export function WebsiteData({ + websiteId, + onSave, +}: { + websiteId: string; + onSave?: (value: string) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const handleReset = async () => { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx similarity index 82% rename from src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx index 1548bddb..c3b5d74a 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx @@ -12,10 +12,20 @@ import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'DELETE'; -export function WebsiteDeleteForm({ websiteId, onSave, onClose }) { +export function WebsiteDeleteForm({ + websiteId, + onSave, + onClose, +}: { + websiteId: string; + onSave?: () => void; + onClose?: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => del(`/websites/${websiteId}`, data), + }); const handleSubmit = async data => { mutate(data, { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.js b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx similarity index 85% rename from src/app/(main)/settings/websites/[id]/WebsiteEditForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx index 18ad0ac9..9c05905c 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx @@ -4,10 +4,20 @@ import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteEditForm({ websiteId, data, onSave }) { +export function WebsiteEditForm({ + websiteId, + data, + onSave, +}: { + websiteId: string; + data: any[]; + onSave?: (data: any) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/websites/${websiteId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + }); const ref = useRef(null); const handleSubmit = async data => { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.js b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx similarity index 78% rename from src/app/(main)/settings/websites/[id]/WebsiteResetForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx index 9886429b..76c2bc47 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx @@ -12,12 +12,22 @@ import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'RESET'; -export function WebsiteResetForm({ websiteId, onSave, onClose }) { +export function WebsiteResetForm({ + websiteId, + onSave, + onClose, +}: { + websiteId: string; + onSave?: () => void; + onClose?: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/websites/${websiteId}/reset`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data), + }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); diff --git a/src/app/(main)/settings/websites/[id]/page.js b/src/app/(main)/settings/websites/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/websites/[id]/page.js rename to src/app/(main)/settings/websites/[id]/page.tsx diff --git a/src/app/(main)/websites/WebsitesBrowse.js b/src/app/(main)/websites/WebsitesBrowse.tsx similarity index 91% rename from src/app/(main)/websites/WebsitesBrowse.js rename to src/app/(main)/websites/WebsitesBrowse.tsx index 3e8df2b2..c426cc06 100644 --- a/src/app/(main)/websites/WebsitesBrowse.js +++ b/src/app/(main)/websites/WebsitesBrowse.tsx @@ -17,7 +17,7 @@ export function WebsitesBrowse() { return ( <> - + setTab(tab)} style={{ marginBottom: 30 }}> {formatMessage(labels.myWebsites)} {formatMessage(labels.teamWebsites)} diff --git a/src/app/(main)/websites/[id]/WebsiteChart.js b/src/app/(main)/websites/[id]/WebsiteChart.tsx similarity index 90% rename from src/app/(main)/websites/[id]/WebsiteChart.js rename to src/app/(main)/websites/[id]/WebsiteChart.tsx index 15a7525f..eba155c1 100644 --- a/src/app/(main)/websites/[id]/WebsiteChart.js +++ b/src/app/(main)/websites/[id]/WebsiteChart.tsx @@ -3,7 +3,7 @@ import PageviewsChart from 'components/metrics/PageviewsChart'; import { useApi, useDateRange, useTimezone, useNavigation } from 'components/hooks'; import { getDateArray } from 'lib/date'; -export function WebsiteChart({ websiteId }) { +export function WebsiteChart({ websiteId }: { websiteId: string }) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate, unit, modified } = dateRange; const [timezone] = useTimezone(); @@ -45,7 +45,7 @@ export function WebsiteChart({ websiteId }) { return { pageviews: [], sessions: [] }; }, [data, startDate, endDate, unit]); - return ; + return ; } export default WebsiteChart; diff --git a/src/app/(main)/websites/[id]/WebsiteChartList.js b/src/app/(main)/websites/[id]/WebsiteChartList.tsx similarity index 91% rename from src/app/(main)/websites/[id]/WebsiteChartList.js rename to src/app/(main)/websites/[id]/WebsiteChartList.tsx index bc2439de..b35b6f1f 100644 --- a/src/app/(main)/websites/[id]/WebsiteChartList.js +++ b/src/app/(main)/websites/[id]/WebsiteChartList.tsx @@ -8,7 +8,15 @@ import WebsiteHeader from './WebsiteHeader'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; import { useMessages, useLocale } from 'components/hooks'; -export default function WebsiteChartList({ websites, showCharts, limit }) { +export default function WebsiteChartList({ + websites, + showCharts, + limit, +}: { + websites: any[]; + showCharts?: boolean; + limit?: number; +}) { const { formatMessage, labels } = useMessages(); const { websiteOrder } = useDashboard(); const { dir } = useLocale(); diff --git a/src/app/(main)/websites/[id]/WebsiteDetails.js b/src/app/(main)/websites/[id]/WebsiteDetails.tsx similarity index 87% rename from src/app/(main)/websites/[id]/WebsiteDetails.js rename to src/app/(main)/websites/[id]/WebsiteDetails.tsx index c6ad1acc..4d3a18e7 100644 --- a/src/app/(main)/websites/[id]/WebsiteDetails.js +++ b/src/app/(main)/websites/[id]/WebsiteDetails.tsx @@ -11,7 +11,7 @@ import WebsiteHeader from './WebsiteHeader'; import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteTableView from './WebsiteTableView'; -export default function WebsiteDetails({ websiteId }) { +export default function WebsiteDetails({ websiteId }: { websiteId: string }) { const { data: website, isLoading, error } = useWebsite(websiteId); const pathname = usePathname(); const showLinks = !pathname.includes('/share/'); @@ -27,10 +27,7 @@ export default function WebsiteDetails({ websiteId }) { return ( <> - + {!website && } diff --git a/src/app/(main)/websites/[id]/WebsiteFilterButton.js b/src/app/(main)/websites/[id]/WebsiteFilterButton.tsx similarity index 91% rename from src/app/(main)/websites/[id]/WebsiteFilterButton.js rename to src/app/(main)/websites/[id]/WebsiteFilterButton.tsx index e96856f6..6a02cd47 100644 --- a/src/app/(main)/websites/[id]/WebsiteFilterButton.js +++ b/src/app/(main)/websites/[id]/WebsiteFilterButton.tsx @@ -3,7 +3,13 @@ import PopupForm from 'app/(main)/reports/[id]/PopupForm'; import FilterSelectForm from 'app/(main)/reports/[id]/FilterSelectForm'; import { useMessages, useNavigation } from 'components/hooks'; -export function WebsiteFilterButton({ websiteId, className }) { +export function WebsiteFilterButton({ + websiteId, + className, +}: { + websiteId: string; + className?: string; +}) { const { formatMessage, labels } = useMessages(); const { makeUrl, router } = useNavigation(); @@ -31,9 +37,9 @@ export function WebsiteFilterButton({ websiteId, className }) { {formatMessage(labels.filter)} - {close => { + {(close: () => void) => { return ( - + `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} + format={n => `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} /> )} diff --git a/src/app/(main)/websites/[id]/WebsiteTableView.js b/src/app/(main)/websites/[id]/WebsiteTableView.tsx similarity index 94% rename from src/app/(main)/websites/[id]/WebsiteTableView.js rename to src/app/(main)/websites/[id]/WebsiteTableView.tsx index 28a8fad6..e530f2ba 100644 --- a/src/app/(main)/websites/[id]/WebsiteTableView.js +++ b/src/app/(main)/websites/[id]/WebsiteTableView.tsx @@ -10,7 +10,7 @@ import CountriesTable from 'components/metrics/CountriesTable'; import EventsTable from 'components/metrics/EventsTable'; import EventsChart from 'components/metrics/EventsChart'; -export default function WebsiteTableView({ websiteId }) { +export default function WebsiteTableView({ websiteId }: { websiteId: string }) { const [countryData, setCountryData] = useState(); const tableProps = { websiteId, diff --git a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx similarity index 94% rename from src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js rename to src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx index b02e166c..419472d5 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx @@ -5,7 +5,7 @@ import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; import MetricsBar from 'components/metrics/MetricsBar'; import styles from './EventDataMetricsBar.module.css'; -export function EventDataMetricsBar({ websiteId }) { +export function EventDataMetricsBar({ websiteId }: { websiteId: string }) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const [dateRange] = useDateRange(websiteId); diff --git a/src/app/(main)/websites/[id]/event-data/EventDataTable.js b/src/app/(main)/websites/[id]/event-data/EventDataTable.tsx similarity index 100% rename from src/app/(main)/websites/[id]/event-data/EventDataTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataTable.tsx diff --git a/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx similarity index 94% rename from src/app/(main)/websites/[id]/event-data/EventDataValueTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx index 4e50f5b9..7976ce36 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx @@ -6,7 +6,7 @@ import PageHeader from 'components/layout/PageHeader'; import Empty from 'components/common/Empty'; import { DATA_TYPES } from 'lib/constants'; -export function EventDataValueTable({ data = [], event }) { +export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) { const { formatMessage, labels } = useMessages(); const { makeUrl } = useNavigation(); diff --git a/src/app/(main)/websites/[id]/event-data/page.js b/src/app/(main)/websites/[id]/event-data/page.tsx similarity index 100% rename from src/app/(main)/websites/[id]/event-data/page.js rename to src/app/(main)/websites/[id]/event-data/page.tsx diff --git a/src/app/login/LoginForm.js b/src/app/login/LoginForm.tsx similarity index 90% rename from src/app/login/LoginForm.js rename to src/app/login/LoginForm.tsx index 59d145bf..78cf3dd3 100644 --- a/src/app/login/LoginForm.js +++ b/src/app/login/LoginForm.tsx @@ -1,5 +1,4 @@ 'use client'; -import { useMutation } from '@tanstack/react-query'; import { Form, FormRow, @@ -21,8 +20,10 @@ import styles from './LoginForm.module.css'; export function LoginForm() { const { formatMessage, labels, getMessage } = useMessages(); const router = useRouter(); - const { post } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/auth/login', data)); + const { post, useMutation } = useApi(); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/auth/login', data), + }); const handleSubmit = async data => { mutate(data, { @@ -53,7 +54,7 @@ export function LoginForm() {
- + {formatMessage(labels.login)} diff --git a/src/app/logout/Logout.js b/src/app/logout/Logout.tsx similarity index 100% rename from src/app/logout/Logout.js rename to src/app/logout/Logout.tsx diff --git a/src/app/share/[...id]/Footer.js b/src/app/share/[...id]/Footer.tsx similarity index 100% rename from src/app/share/[...id]/Footer.js rename to src/app/share/[...id]/Footer.tsx diff --git a/src/app/share/[...id]/Header.js b/src/app/share/[...id]/Header.tsx similarity index 100% rename from src/app/share/[...id]/Header.js rename to src/app/share/[...id]/Header.tsx diff --git a/src/app/share/[...id]/Share.js b/src/app/share/[...id]/Share.tsx similarity index 100% rename from src/app/share/[...id]/Share.js rename to src/app/share/[...id]/Share.tsx diff --git a/src/components/common/DataTable.tsx b/src/components/common/DataTable.tsx index 621a44fe..00aba09c 100644 --- a/src/components/common/DataTable.tsx +++ b/src/components/common/DataTable.tsx @@ -36,11 +36,11 @@ export function DataTable({ const hasData = Boolean(!isLoading && data?.length); const noResults = Boolean(!isLoading && query && !hasData); - const handleSearch = query => { + const handleSearch = (query: string) => { setParams({ ...params, query, page: params.page ? page : 1 }); }; - const handlePageChange = page => { + const handlePageChange = (page: number) => { setParams({ ...params, query, page }); }; @@ -54,7 +54,7 @@ export function DataTable({ diff --git a/src/components/common/FilterButtons.tsx b/src/components/common/FilterButtons.tsx index e1860c78..a64a6482 100644 --- a/src/components/common/FilterButtons.tsx +++ b/src/components/common/FilterButtons.tsx @@ -4,7 +4,7 @@ import { ButtonGroup, Button, Flexbox } from 'react-basics'; export interface FilterButtonsProps { items: any[]; selectedKey?: Key; - onSelect: () => void; + onSelect: (key: any) => void; } export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) { diff --git a/src/components/declarations.d.ts b/src/components/declarations.d.ts index 81533301..ca55157b 100644 --- a/src/components/declarations.d.ts +++ b/src/components/declarations.d.ts @@ -1,3 +1,4 @@ declare module '*.css'; declare module '*.svg'; declare module '*.json'; +declare module 'uuid'; diff --git a/src/components/hooks/useApiFilter.ts b/src/components/hooks/useApiFilter.ts deleted file mode 100644 index d411fd43..00000000 --- a/src/components/hooks/useApiFilter.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useState } from 'react'; - -export function useApiFilter() { - const [filter, setFilter] = useState(); - const [filterType, setFilterType] = useState('All'); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - - const handleFilterChange = value => setFilter(value); - const handlePageChange = value => setPage(value); - const handlePageSizeChange = value => setPageSize(value); - - return { - filter, - setFilter, - filterType, - setFilterType, - page, - setPage, - pageSize, - setPageSize, - handleFilterChange, - handlePageChange, - handlePageSizeChange, - }; -} - -export default useApiFilter; diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 6e70a368..71361b69 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; import useApi from './useApi'; -export function useDateRange(websiteId: string) { +export function useDateRange(websiteId?: string) { const { get } = useApi(); const { locale } = useLocale(); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); diff --git a/src/components/hooks/useFilterQuery.ts b/src/components/hooks/useFilterQuery.ts index 2c520741..6dc3abe7 100644 --- a/src/components/hooks/useFilterQuery.ts +++ b/src/components/hooks/useFilterQuery.ts @@ -1,24 +1,33 @@ +import { UseQueryOptions } from '@tanstack/react-query'; import { useState, Dispatch, SetStateAction } from 'react'; import { useApi } from 'components/hooks/useApi'; -import { SearchFilter, FilterResult } from 'lib/types'; +import { FilterResult, SearchFilter } from 'lib/types'; export interface FilterQueryResult { - result: FilterResult; + result: FilterResult; query: any; params: SearchFilter; setParams: Dispatch>; } -export function useFilterQuery(props = {}): FilterQueryResult { +export function useFilterQuery({ + queryKey, + queryFn, + ...options +}: UseQueryOptions): FilterQueryResult { const [params, setParams] = useState({ query: '', page: 1, }); const { useQuery } = useApi(); - const { data, ...query } = useQuery>({ ...props }); + const { data, ...query } = useQuery({ + queryKey: [...queryKey, params], + queryFn: (data: any) => queryFn({ ...data, ...params }), + ...options, + }); return { - result: data, + result: data as FilterResult, query, params, setParams, diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 9f01cd80..fb9bffc5 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -2,7 +2,12 @@ import { useMemo } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { buildUrl } from 'next-basics'; -export function useNavigation() { +export function useNavigation(): { + pathname: string; + query: { [key: string]: string }; + router: any; + makeUrl: (params: any, reset?: boolean) => string; +} { const router = useRouter(); const pathname = usePathname(); const params = useSearchParams(); diff --git a/src/components/hooks/useReports.ts b/src/components/hooks/useReports.ts index 0fad2db1..d2473002 100644 --- a/src/components/hooks/useReports.ts +++ b/src/components/hooks/useReports.ts @@ -1,19 +1,19 @@ import { useState } from 'react'; import useApi from './useApi'; -import useApiFilter from 'components/hooks/useApiFilter'; +import useFilterQuery from 'components/hooks/useFilterQuery'; -export function useReports() { +export function useReports(websiteId?: string) { const [modified, setModified] = useState(Date.now()); - const { get, useQuery, del, useMutation } = useApi(); - const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); - const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = - useApiFilter(); - const { data, error, isLoading } = useQuery({ - queryKey: ['reports', { modified, filter, page, pageSize }], - queryFn: () => get(`/reports`, { filter, page, pageSize }), + const { get, del, useMutation } = useApi(); + const { mutate } = useMutation({ mutationFn: (reportId: string) => del(`/reports/${reportId}`) }); + const queryResult = useFilterQuery({ + queryKey: ['reports', { websiteId, modified }], + queryFn: (params: any) => { + return get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params); + }, }); - const deleteReport = id => { + const deleteReport = (id: any) => { mutate(id, { onSuccess: () => { setModified(Date.now()); @@ -22,16 +22,8 @@ export function useReports() { }; return { - reports: data, - error, - isLoading, + ...queryResult, deleteReport, - filter, - page, - pageSize, - handleFilterChange, - handlePageChange, - handlePageSizeChange, }; } diff --git a/src/components/hooks/useShareToken.ts b/src/components/hooks/useShareToken.ts index 41ae7faf..189657be 100644 --- a/src/components/hooks/useShareToken.ts +++ b/src/components/hooks/useShareToken.ts @@ -3,7 +3,11 @@ import useApi from './useApi'; const selector = (state: { shareToken: string }) => state.shareToken; -export function useShareToken(shareId: string) { +export function useShareToken(shareId: string): { + shareToken: any; + isLoading?: boolean; + error?: Error; +} { const shareToken = useStore(selector); const { get, useQuery } = useApi(); const { isLoading, error } = useQuery({ diff --git a/src/components/input/DateFilter.js b/src/components/input/DateFilter.tsx similarity index 89% rename from src/components/input/DateFilter.js rename to src/components/input/DateFilter.tsx index 9fde27ca..f7739f17 100644 --- a/src/components/input/DateFilter.js +++ b/src/components/input/DateFilter.tsx @@ -3,9 +3,20 @@ import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; import useLocale from 'components/hooks/useLocale'; -import { formatDate } from 'lib/date'; -import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; +import Icons from 'components/icons'; +import { formatDate } from 'lib/date'; + +export interface DateFilterProps { + value: string; + startDate: Date; + endDate: Date; + className?: string; + onChange?: (value: string) => void; + selectedUnit?: string; + showAllTime?: boolean; + alignment?: 'start' | 'center' | 'end'; +} export function DateFilter({ value, @@ -16,7 +27,7 @@ export function DateFilter({ selectedUnit, showAllTime = false, alignment = 'end', -}) { +}: DateFilterProps) { const { formatMessage, labels } = useMessages(); const [showPicker, setShowPicker] = useState(false); @@ -65,7 +76,7 @@ export function DateFilter({ }, ].filter(n => n); - const renderValue = value => { + const renderValue = (value: string) => { return value.startsWith('range') ? ( { + const handleChange = (value: string) => { if (value === 'custom') { setShowPicker(true); return; @@ -86,7 +97,7 @@ export function DateFilter({ onChange(value); }; - const handlePickerChange = value => { + const handlePickerChange = (value: string) => { setShowPicker(false); onChange(value); }; @@ -102,7 +113,7 @@ export function DateFilter({ value={value} alignment={alignment} placeholder={formatMessage(labels.selectDate)} - onChange={handleChange} + onChange={key => handleChange(key as any)} > {({ label, value, divider }) => ( diff --git a/src/components/input/LanguageButton.js b/src/components/input/LanguageButton.tsx similarity index 88% rename from src/components/input/LanguageButton.js rename to src/components/input/LanguageButton.tsx index 3c0d0cd6..1151da0b 100644 --- a/src/components/input/LanguageButton.js +++ b/src/components/input/LanguageButton.tsx @@ -9,7 +9,7 @@ export function LanguageButton() { const { locale, saveLocale, dir } = useLocale(); const items = Object.keys(languages).map(key => ({ ...languages[key], value: key })); - function handleSelect(value, close, e) { + function handleSelect(value: string, close: () => void, e: MouseEvent) { e.stopPropagation(); saveLocale(value); close(); @@ -23,7 +23,7 @@ export function LanguageButton() { - {close => { + {(close: () => void) => { return (
{items.map(({ value, label }) => { @@ -31,7 +31,7 @@ export function LanguageButton() {
handleSelect(value, close, e)} > {label} {value === locale && ( diff --git a/src/components/input/LogoutButton.js b/src/components/input/LogoutButton.tsx similarity index 80% rename from src/components/input/LogoutButton.js rename to src/components/input/LogoutButton.tsx index 6ca358a1..c787f229 100644 --- a/src/components/input/LogoutButton.js +++ b/src/components/input/LogoutButton.tsx @@ -2,7 +2,11 @@ import { Button, Icon, Icons, TooltipPopup } from 'react-basics'; import Link from 'next/link'; import useMessages from 'components/hooks/useMessages'; -export function LogoutButton({ tooltipPosition = 'top' }) { +export function LogoutButton({ + tooltipPosition = 'top', +}: { + tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'; +}) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/components/input/MonthSelect.js b/src/components/input/MonthSelect.tsx similarity index 87% rename from src/components/input/MonthSelect.js rename to src/components/input/MonthSelect.tsx index 312c6854..acb17dfe 100644 --- a/src/components/input/MonthSelect.js +++ b/src/components/input/MonthSelect.tsx @@ -20,7 +20,7 @@ export function MonthSelect({ date = new Date(), onChange }) { const year = date.getFullYear(); const ref = useRef(); - const handleChange = (close, date) => { + const handleChange = (close: () => void, date: Date) => { onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`); close(); }; @@ -53,12 +53,8 @@ export function MonthSelect({ date = new Date(), onChange }) { - {close => ( - + {(close: any) => ( + )} diff --git a/src/components/input/ProfileButton.js b/src/components/input/ProfileButton.tsx similarity index 100% rename from src/components/input/ProfileButton.js rename to src/components/input/ProfileButton.tsx diff --git a/src/components/input/RefreshButton.js b/src/components/input/RefreshButton.tsx similarity index 87% rename from src/components/input/RefreshButton.js rename to src/components/input/RefreshButton.tsx index 8b40cafa..01e80378 100644 --- a/src/components/input/RefreshButton.js +++ b/src/components/input/RefreshButton.tsx @@ -4,7 +4,13 @@ import useDateRange from 'components/hooks/useDateRange'; import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; -export function RefreshButton({ websiteId, isLoading }) { +export function RefreshButton({ + websiteId, + isLoading, +}: { + websiteId: string; + isLoading?: boolean; +}) { const { formatMessage, labels } = useMessages(); const [dateRange] = useDateRange(websiteId); diff --git a/src/components/input/SettingsButton.js b/src/components/input/SettingsButton.tsx similarity index 100% rename from src/components/input/SettingsButton.js rename to src/components/input/SettingsButton.tsx diff --git a/src/components/input/ThemeButton.js b/src/components/input/ThemeButton.tsx similarity index 100% rename from src/components/input/ThemeButton.js rename to src/components/input/ThemeButton.tsx diff --git a/src/components/input/WebsiteDateFilter.js b/src/components/input/WebsiteDateFilter.tsx similarity index 95% rename from src/components/input/WebsiteDateFilter.js rename to src/components/input/WebsiteDateFilter.tsx index 1725ca3b..cf1beaa1 100644 --- a/src/components/input/WebsiteDateFilter.js +++ b/src/components/input/WebsiteDateFilter.tsx @@ -5,7 +5,7 @@ import { Button, Icon, Icons } from 'react-basics'; import DateFilter from './DateFilter'; import styles from './WebsiteDateFilter.module.css'; -export function WebsiteDateFilter({ websiteId }) { +export function WebsiteDateFilter({ websiteId }: { websiteId: string }) { const [dateRange, setDateRange] = useDateRange(websiteId); const { value, startDate, endDate, selectedUnit } = dateRange; const isFutureDate = diff --git a/src/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.tsx similarity index 88% rename from src/components/input/WebsiteSelect.js rename to src/components/input/WebsiteSelect.tsx index 23334215..e125e258 100644 --- a/src/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.tsx @@ -3,7 +3,13 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import styles from './WebsiteSelect.module.css'; -export function WebsiteSelect({ websiteId, onSelect }) { +export function WebsiteSelect({ + websiteId, + onSelect, +}: { + websiteId: string; + onSelect?: (key: any) => void; +}) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { data } = useQuery({ diff --git a/src/components/layout/Grid.js b/src/components/layout/Grid.js deleted file mode 100644 index 86b08887..00000000 --- a/src/components/layout/Grid.js +++ /dev/null @@ -1,18 +0,0 @@ -import classNames from 'classnames'; -import { mapChildren } from 'react-basics'; -import styles from './Grid.module.css'; - -export function Grid({ className, ...otherProps }) { - return
; -} - -export function GridRow(props) { - const { columns = 'two', className, children, ...otherProps } = props; - return ( -
- {mapChildren(children, child => { - return
{child}
; - })} -
- ); -} diff --git a/src/components/layout/Grid.tsx b/src/components/layout/Grid.tsx new file mode 100644 index 00000000..2a34fdc4 --- /dev/null +++ b/src/components/layout/Grid.tsx @@ -0,0 +1,34 @@ +import { CSSProperties } from 'react'; +import classNames from 'classnames'; +import { mapChildren } from 'react-basics'; +import styles from './Grid.module.css'; + +export interface GridProps { + className?: string; + style?: CSSProperties; + children?: any; +} + +export function Grid({ className, style, children }: GridProps) { + return ( +
+ {children} +
+ ); +} + +export function GridRow(props: { + [x: string]: any; + columns?: 'one' | 'two' | 'three' | 'one-two' | 'two-one'; + className?: string; + children?: any; +}) { + const { columns = 'two', className, children, ...otherProps } = props; + return ( +
+ {mapChildren(children, child => { + return
{child}
; + })} +
+ ); +} diff --git a/src/components/layout/NavGroup.js b/src/components/layout/NavGroup.tsx similarity index 90% rename from src/components/layout/NavGroup.js rename to src/components/layout/NavGroup.tsx index 361dffb5..e95b61fa 100644 --- a/src/components/layout/NavGroup.js +++ b/src/components/layout/NavGroup.tsx @@ -6,13 +6,21 @@ import Link from 'next/link'; import Icons from 'components/icons'; import styles from './NavGroup.module.css'; +export interface NavGroupProps { + title: string; + items: any[]; + defaultExpanded?: boolean; + allowExpand?: boolean; + minimized?: boolean; +} + export function NavGroup({ title, items, defaultExpanded = true, allowExpand = true, minimized = false, -}) { +}: NavGroupProps) { const pathname = usePathname(); const [expanded, setExpanded] = useState(defaultExpanded); diff --git a/src/components/layout/SideNav.js b/src/components/layout/SideNav.tsx similarity index 82% rename from src/components/layout/SideNav.js rename to src/components/layout/SideNav.tsx index c93881e4..f38bdba0 100644 --- a/src/components/layout/SideNav.js +++ b/src/components/layout/SideNav.tsx @@ -4,6 +4,15 @@ import { usePathname } from 'next/navigation'; import Link from 'next/link'; import styles from './SideNav.module.css'; +export interface SideNavProps { + selectedKey: string; + items: any[]; + shallow?: boolean; + scroll?: boolean; + className?: boolean; + onSelect?: () => void; +} + export function SideNav({ selectedKey, items, @@ -11,7 +20,7 @@ export function SideNav({ scroll = false, className, onSelect = () => {}, -}) { +}: SideNavProps) { const pathname = usePathname(); return ( get(`/websites/${websiteId}/active`), - refetchInterval, enabled: !!websiteId, + refetchInterval, }); const count = useMemo(() => { diff --git a/src/components/metrics/BarChart.js b/src/components/metrics/BarChart.tsx similarity index 83% rename from src/components/metrics/BarChart.js rename to src/components/metrics/BarChart.tsx index 8341fbc7..8bb3a7a7 100644 --- a/src/components/metrics/BarChart.js +++ b/src/components/metrics/BarChart.tsx @@ -10,12 +10,28 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import { renderNumberLabels } from 'lib/charts'; import styles from './BarChart.module.css'; +export interface BarChartProps { + datasets?: any[]; + unit?: string; + animationDuration?: number; + stacked?: boolean; + isLoading?: boolean; + renderXLabel?: (label: string, index: number, values: any[]) => string; + renderYLabel?: (label: string, index: number, values: any[]) => string; + XAxisType?: string; + YAxisType?: string; + renderTooltipPopup?: (setTooltipPopup: (data: any) => void, model: any) => void; + onCreate?: (chart: any) => void; + onUpdate?: (chart: any) => void; + className?: string; +} + export function BarChart({ - datasets, + datasets = [], unit, animationDuration = DEFAULT_ANIMATION_DURATION, stacked = false, - loading = false, + isLoading = false, renderXLabel, renderYLabel, XAxisType = 'time', @@ -24,7 +40,7 @@ export function BarChart({ onCreate, onUpdate, className, -}) { +}: BarChartProps) { const canvas = useRef(); const chart = useRef(null); const [tooltip, setTooltipPopup] = useState(null); @@ -85,7 +101,7 @@ export function BarChart({ color: colors.chart.line, }, ticks: { - color: colors.text, + color: colors.chart.text, callback: renderYLabel || renderNumberLabels, }, }, @@ -106,16 +122,15 @@ export function BarChart({ const createChart = () => { Chart.defaults.font.family = 'Inter'; - const options = getOptions(); - chart.current = new Chart(canvas.current, { type: 'bar', data: { datasets, }, - options, }); + chart.current.options = getOptions(); + onCreate?.(chart.current); }; @@ -147,7 +162,7 @@ export function BarChart({ return (
- {loading && } + {isLoading && }
diff --git a/src/components/metrics/BrowsersTable.js b/src/components/metrics/BrowsersTable.tsx similarity index 85% rename from src/components/metrics/BrowsersTable.js rename to src/components/metrics/BrowsersTable.tsx index afc53663..e1c05435 100644 --- a/src/components/metrics/BrowsersTable.js +++ b/src/components/metrics/BrowsersTable.tsx @@ -1,9 +1,9 @@ import FilterLink from 'components/common/FilterLink'; -import MetricsTable from 'components/metrics/MetricsTable'; +import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable'; import useMessages from 'components/hooks/useMessages'; import useFormat from 'components/hooks/useFormat'; -export function BrowsersTable({ websiteId, ...props }) { +export function BrowsersTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); const { formatBrowser } = useFormat(); @@ -26,7 +26,6 @@ export function BrowsersTable({ websiteId, ...props }) { title={formatMessage(labels.browsers)} type="browser" metric={formatMessage(labels.visitors)} - websiteId={websiteId} renderLabel={renderLink} /> ); diff --git a/src/components/metrics/CitiesTable.js b/src/components/metrics/CitiesTable.tsx similarity index 85% rename from src/components/metrics/CitiesTable.js rename to src/components/metrics/CitiesTable.tsx index c5f1b1d7..69b89962 100644 --- a/src/components/metrics/CitiesTable.js +++ b/src/components/metrics/CitiesTable.tsx @@ -1,16 +1,16 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import { emptyFilter } from 'lib/filters'; import FilterLink from 'components/common/FilterLink'; import useLocale from 'components/hooks/useLocale'; import useMessages from 'components/hooks/useMessages'; import useCountryNames from 'components/hooks/useCountryNames'; -export function CitiesTable({ websiteId, ...props }) { +export function CitiesTable(props: MetricsTableProps) { const { locale } = useLocale(); const { formatMessage, labels } = useMessages(); const countryNames = useCountryNames(locale); - const renderLabel = (city, country) => { + const renderLabel = (city: string, country: string) => { const name = countryNames[country]; return name ? `${city}, ${name}` : city; }; @@ -34,7 +34,6 @@ export function CitiesTable({ websiteId, ...props }) { title={formatMessage(labels.cities)} type="city" metric={formatMessage(labels.visitors)} - websiteId={websiteId} dataFilter={emptyFilter} renderLabel={renderLink} /> diff --git a/src/components/metrics/CountriesTable.js b/src/components/metrics/CountriesTable.tsx similarity index 73% rename from src/components/metrics/CountriesTable.js rename to src/components/metrics/CountriesTable.tsx index 6f3b75b0..99f9ca2f 100644 --- a/src/components/metrics/CountriesTable.js +++ b/src/components/metrics/CountriesTable.tsx @@ -1,15 +1,24 @@ import FilterLink from 'components/common/FilterLink'; import useCountryNames from 'components/hooks/useCountryNames'; import { useLocale, useMessages, useFormat } from 'components/hooks'; -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; -export function CountriesTable({ websiteId, ...props }) { +export function CountriesTable({ + onDataLoad, + ...props +}: { + onDataLoad: (data: any) => void; +} & MetricsTableProps) { const { locale } = useLocale(); const countryNames = useCountryNames(locale); const { formatMessage, labels } = useMessages(); const { formatCountry } = useFormat(); - function renderLink({ x: code }) { + const handleDataLoad = (data: any) => { + onDataLoad?.(data); + }; + + const renderLink = ({ x: code }) => { return ( ); - } + }; return ( ); } diff --git a/src/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.tsx similarity index 96% rename from src/components/metrics/DatePickerForm.js rename to src/components/metrics/DatePickerForm.tsx index 5e1906c3..b4f0781e 100644 --- a/src/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.tsx @@ -39,7 +39,7 @@ export function DatePickerForm({ return (
- + setSelected(key as any)}> diff --git a/src/components/metrics/DevicesTable.js b/src/components/metrics/DevicesTable.tsx similarity index 86% rename from src/components/metrics/DevicesTable.js rename to src/components/metrics/DevicesTable.tsx index 606b020a..4ebdac1b 100644 --- a/src/components/metrics/DevicesTable.js +++ b/src/components/metrics/DevicesTable.tsx @@ -1,9 +1,9 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; import { useFormat } from 'components/hooks'; -export function DevicesTable({ websiteId, ...props }) { +export function DevicesTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); const { formatDevice } = useFormat(); @@ -26,7 +26,6 @@ export function DevicesTable({ websiteId, ...props }) { title={formatMessage(labels.devices)} type="device" metric={formatMessage(labels.visitors)} - websiteId={websiteId} renderLabel={renderLink} /> ); diff --git a/src/components/metrics/EventsChart.js b/src/components/metrics/EventsChart.tsx similarity index 92% rename from src/components/metrics/EventsChart.js rename to src/components/metrics/EventsChart.tsx index 610a3616..be6d4a0c 100644 --- a/src/components/metrics/EventsChart.js +++ b/src/components/metrics/EventsChart.tsx @@ -7,7 +7,13 @@ import { useApi, useLocale, useDateRange, useTimezone, useNavigation } from 'com import { EVENT_COLORS } from 'lib/constants'; import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts'; -export function EventsChart({ websiteId, className, token }) { +export interface EventsChartProps { + websiteId: string; + className?: string; + token?: string; +} + +export function EventsChart({ websiteId, className, token }: EventsChartProps) { const { get, useQuery } = useApi(); const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId); const { locale } = useLocale(); @@ -71,7 +77,6 @@ export function EventsChart({ websiteId, className, token }) { className={className} datasets={datasets} unit={unit} - height={300} loading={isLoading} stacked renderXLabel={renderDateLabels(unit, locale)} diff --git a/src/components/metrics/EventsTable.js b/src/components/metrics/EventsTable.tsx similarity index 69% rename from src/components/metrics/EventsTable.js rename to src/components/metrics/EventsTable.tsx index a8ae82aa..26d9529b 100644 --- a/src/components/metrics/EventsTable.js +++ b/src/components/metrics/EventsTable.tsx @@ -1,10 +1,10 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import useMessages from 'components/hooks/useMessages'; -export function EventsTable({ websiteId, ...props }) { +export function EventsTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); - function handleDataLoad(data) { + function handleDataLoad(data: any) { props.onDataLoad?.(data); } @@ -14,7 +14,6 @@ export function EventsTable({ websiteId, ...props }) { title={formatMessage(labels.events)} type="event" metric={formatMessage(labels.actions)} - websiteId={websiteId} onDataLoad={handleDataLoad} /> ); diff --git a/src/components/metrics/FilterTags.js b/src/components/metrics/FilterTags.tsx similarity index 92% rename from src/components/metrics/FilterTags.js rename to src/components/metrics/FilterTags.tsx index db8fdcbd..140385f5 100644 --- a/src/components/metrics/FilterTags.js +++ b/src/components/metrics/FilterTags.tsx @@ -18,7 +18,7 @@ export function FilterTags({ params }) { return null; } - function handleCloseFilter(param) { + function handleCloseFilter(param?: string) { if (!param) { router.push(makeUrl({ view }, true)); } else { @@ -44,7 +44,7 @@ export function FilterTags({ params }) {
); })} - diff --git a/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx index b1dc5854..420afe9b 100644 --- a/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx +++ b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx @@ -18,15 +18,17 @@ const generateId = () => getRandomChars(16); export function TeamEditForm({ teamId, data, onSave, readOnly }) { const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/teams/${teamId}`, data), + }); const ref = useRef(null); const [accessCode, setAccessCode] = useState(data.accessCode); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { ref.current.reset(data); - onSave(data); + onSave?.(data); }, }); }; diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx b/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx index 59e393e1..0c039d4e 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx @@ -5,10 +5,12 @@ import { Icon, Icons, LoadingButton, Text } from 'react-basics'; export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/websites/${websiteId}`)); + const { mutate, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/websites/${websiteId}`), + }); const handleRemoveTeamMember = async () => { - await mutate(null, { + mutate(null, { onSuccess: () => { onSave(); }, @@ -16,7 +18,7 @@ export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { }; return ( - handleRemoveTeamMember()} isLoading={isLoading}> + handleRemoveTeamMember()} isLoading={isPending}> From 905b480c131a1e570317571d331a74b8b031bc7b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 22:20:36 -0800 Subject: [PATCH 41/62] Fixed mutate. --- src/app/(main)/reports/ReportDeleteButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/reports/ReportDeleteButton.tsx b/src/app/(main)/reports/ReportDeleteButton.tsx index d3f1bb4f..32ec819e 100644 --- a/src/app/(main)/reports/ReportDeleteButton.tsx +++ b/src/app/(main)/reports/ReportDeleteButton.tsx @@ -14,7 +14,7 @@ export function ReportDeleteButton({ }) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); + const { mutate } = useMutation({ mutationFn: reportId => del(`/reports/${reportId}`) }); const handleConfirm = (close: () => void) => { mutate(reportId as any, { From 66d7a815fcabcf68fa61e54dd83d10b257196f58 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 22:49:30 -0800 Subject: [PATCH 42/62] Fixed websites query. --- src/lib/auth.ts | 2 +- src/lib/crypto.ts | 2 +- src/pages/api/websites/index.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 97e20d98..cb3d3cc7 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -59,7 +59,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri return !!(await findTeamWebsiteByUserId(websiteId, user.id)); } -export async function canViewAllWebsite({ user }: Auth) { +export async function canViewAllWebsites({ user }: Auth) { return user.isAdmin; } diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index b0ce20de..a2763352 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -12,7 +12,7 @@ export function salt() { return hash(secret(), ROTATING_SALT); } -export function uuid(...args: [any]) { +export function uuid(...args: any) { if (!args.length) return v4(); return v5(hash(...args, salt()), v5.DNS); diff --git a/src/pages/api/websites/index.ts b/src/pages/api/websites/index.ts index 099649fa..02ec713b 100644 --- a/src/pages/api/websites/index.ts +++ b/src/pages/api/websites/index.ts @@ -1,4 +1,4 @@ -import { canCreateWebsite, canViewAllWebsite } from 'lib/auth'; +import { canCreateWebsite, canViewAllWebsites } from 'lib/auth'; import { uuid } from 'lib/crypto'; import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; @@ -41,7 +41,7 @@ export default async ( } = req.auth; if (req.method === 'GET') { - if (canViewAllWebsite(req.auth)) { + if (await canViewAllWebsites(req.auth)) { const websites = getWebsites(req.query, { include: { teamWebsite: { From e718b22599cf67ff413671d4af6c712ad17aacdd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 23:02:28 -0800 Subject: [PATCH 43/62] Fixed types. --- src/pages/api/websites/index.ts | 2 +- src/queries/admin/website.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/api/websites/index.ts b/src/pages/api/websites/index.ts index 02ec713b..89162067 100644 --- a/src/pages/api/websites/index.ts +++ b/src/pages/api/websites/index.ts @@ -42,7 +42,7 @@ export default async ( if (req.method === 'GET') { if (await canViewAllWebsites(req.auth)) { - const websites = getWebsites(req.query, { + const websites = await getWebsites(req.query, { include: { teamWebsite: { include: { diff --git a/src/queries/admin/website.ts b/src/queries/admin/website.ts index 0e7f5124..524e2e14 100644 --- a/src/queries/admin/website.ts +++ b/src/queries/admin/website.ts @@ -21,7 +21,7 @@ export async function getWebsiteByShareId(shareId: string) { export async function getWebsites( filters: WebsiteSearchFilter, options?: { include?: Prisma.WebsiteInclude }, -): Promise> { +): Promise> { const { userId, teamId, includeTeams, onlyTeams, query } = filters; const mode = prisma.getSearchMode(); @@ -105,7 +105,7 @@ export async function getWebsites( export async function getWebsitesByUserId( userId: string, filters?: WebsiteSearchFilter, -): Promise> { +): Promise> { return getWebsites( { userId, ...filters }, { @@ -133,7 +133,7 @@ export async function getWebsitesByUserId( export async function getWebsitesByTeamId( teamId: string, filters?: WebsiteSearchFilter, -): Promise> { +): Promise> { return getWebsites( { teamId, From 3f657d97b26d7763d1d2a59cf4166efea1c3f5c7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 23:16:50 -0800 Subject: [PATCH 44/62] Fixed bar chart rendering issue. --- src/components/metrics/BarChart.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/metrics/BarChart.tsx b/src/components/metrics/BarChart.tsx index 8bb3a7a7..6d28dc20 100644 --- a/src/components/metrics/BarChart.tsx +++ b/src/components/metrics/BarChart.tsx @@ -127,10 +127,9 @@ export function BarChart({ data: { datasets, }, + options: getOptions() as any, }); - chart.current.options = getOptions(); - onCreate?.(chart.current); }; @@ -160,7 +159,7 @@ export function BarChart({ }, [datasets, unit, theme, animationDuration, locale]); return ( -
+ <>
{isLoading && } @@ -171,7 +170,7 @@ export function BarChart({
{tooltip}
)} -
+ ); } From 4fca98d25d91066e8e107d0a137de52a118001a9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 5 Dec 2023 19:22:14 -0800 Subject: [PATCH 45/62] Added SettingsContext. --- next.config.js | 1 + src/app/(main)/dashboard/Dashboard.tsx | 2 +- src/app/(main)/settings/SettingsContext.tsx | 5 +++ src/app/(main)/settings/layout.tsx | 29 ++++++++++----- .../(main)/settings/users/UserAddButton.tsx | 2 +- src/app/(main)/settings/users/UserAddForm.tsx | 2 +- .../settings/users/UserDeleteButton.tsx | 2 +- .../{UserSettings.js => UserSettings.tsx} | 10 +++--- .../settings/websites/WebsiteAddButton.tsx | 2 +- .../settings/websites/WebsiteAddForm.tsx | 5 ++- ...WebsiteSettings.js => WebsiteSettings.tsx} | 31 +++++++--------- .../settings/websites/WebsitesDataTable.tsx | 36 +++++++++---------- .../settings/websites/WebsitesTable.tsx | 8 +++-- .../settings/websites/[id]/ShareUrl.tsx | 11 +++--- .../settings/websites/[id]/TrackingCode.tsx | 13 +++---- .../settings/websites/[id]/WebsiteData.tsx | 4 +-- .../websites/[id]/WebsiteDeleteForm.tsx | 7 ++-- .../websites/[id]/WebsiteEditForm.tsx | 8 +++-- .../websites/[id]/WebsiteResetForm.tsx | 5 ++- src/app/sso/page.tsx | 2 +- src/components/layout/Page.tsx | 2 +- src/index.ts | 2 ++ 22 files changed, 106 insertions(+), 83 deletions(-) create mode 100644 src/app/(main)/settings/SettingsContext.tsx rename src/app/(main)/settings/users/[id]/{UserSettings.js => UserSettings.tsx} (90%) rename src/app/(main)/settings/websites/{WebsiteSettings.js => WebsiteSettings.tsx} (77%) diff --git a/next.config.js b/next.config.js index c73790b8..a155ece7 100644 --- a/next.config.js +++ b/next.config.js @@ -87,6 +87,7 @@ const config = { defaultLocale: process.env.DEFAULT_LOCALE || '', disableLogin: process.env.DISABLE_LOGIN || '', disableUI: process.env.DISABLE_UI || '', + hostUrl: process.env.HOST_URL || '', }, basePath, output: 'standalone', diff --git a/src/app/(main)/dashboard/Dashboard.tsx b/src/app/(main)/dashboard/Dashboard.tsx index 0f281aa7..ec1d793c 100644 --- a/src/app/(main)/dashboard/Dashboard.tsx +++ b/src/app/(main)/dashboard/Dashboard.tsx @@ -38,7 +38,7 @@ export function Dashboard() { const { page } = params; if (query.isLoading) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/SettingsContext.tsx b/src/app/(main)/settings/SettingsContext.tsx new file mode 100644 index 00000000..898a40cc --- /dev/null +++ b/src/app/(main)/settings/SettingsContext.tsx @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const SettingsContext = createContext(null); + +export default SettingsContext; diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index f738f883..f9612361 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -4,6 +4,7 @@ import useUser from 'components/hooks/useUser'; import useMessages from 'components/hooks/useMessages'; import SideNav from 'components/layout/SideNav'; import styles from './layout.module.css'; +import SettingsContext from './SettingsContext'; export default function SettingsLayout({ children }) { const { user } = useUser(); @@ -24,14 +25,26 @@ export default function SettingsLayout({ children }) { return null; } + const hostUrl = process.env.hostUrl || location.origin; + + const config = { + settingsUrl: '/settings/websites', + hostUrl, + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }; + return ( -
- {!cloudMode && ( -
- -
- )} -
{children}
-
+ +
+ {!cloudMode && ( +
+ +
+ )} +
{children}
+
+
); } diff --git a/src/app/(main)/settings/users/UserAddButton.tsx b/src/app/(main)/settings/users/UserAddButton.tsx index 7f08107c..1158ecec 100644 --- a/src/app/(main)/settings/users/UserAddButton.tsx +++ b/src/app/(main)/settings/users/UserAddButton.tsx @@ -22,7 +22,7 @@ export function UserAddButton({ onSave }: { onSave?: () => void }) { {formatMessage(labels.createUser)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/users/UserAddForm.tsx b/src/app/(main)/settings/users/UserAddForm.tsx index 11066a24..8c153775 100644 --- a/src/app/(main)/settings/users/UserAddForm.tsx +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -21,7 +21,7 @@ export function UserAddForm({ onSave, onClose }) { }); const { formatMessage, labels } = useMessages(); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); diff --git a/src/app/(main)/settings/users/UserDeleteButton.tsx b/src/app/(main)/settings/users/UserDeleteButton.tsx index 2b93c138..775004a8 100644 --- a/src/app/(main)/settings/users/UserDeleteButton.tsx +++ b/src/app/(main)/settings/users/UserDeleteButton.tsx @@ -24,7 +24,7 @@ export function UserDeleteButton({ {formatMessage(labels.delete)} - {close => ( + {(close: () => void) => ( )} diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.tsx similarity index 90% rename from src/app/(main)/settings/users/[id]/UserSettings.js rename to src/app/(main)/settings/users/[id]/UserSettings.tsx index 3d8a92ea..d9b149c3 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.tsx @@ -1,17 +1,17 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { Key, useEffect, useState } from 'react'; import { Item, Loading, Tabs, useToasts } from 'react-basics'; import UserEditForm from '../UserEditForm'; import PageHeader from 'components/layout/PageHeader'; import useApi from 'components/hooks/useApi'; -import UserWebsites from '../UserWebsites'; import useMessages from 'components/hooks/useMessages'; +import UserWebsites from '../UserWebsites'; export function UserSettings({ userId }) { const { formatMessage, labels, messages } = useMessages(); const [edit, setEdit] = useState(false); const [values, setValues] = useState(null); - const [tab, setTab] = useState('details'); + const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); const { data, isLoading } = useQuery({ @@ -24,7 +24,7 @@ export function UserSettings({ userId }) { gcTime: 0, }); - const handleSave = data => { + const handleSave = (data: any) => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); if (data) { setValues(state => ({ ...state, ...data })); @@ -42,7 +42,7 @@ export function UserSettings({ userId }) { }, [data]); if (isLoading || !values) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.tsx b/src/app/(main)/settings/websites/WebsiteAddButton.tsx index 16681b0e..7b4c92de 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -22,7 +22,7 @@ export function WebsiteAddButton({ onSave }: { onSave?: () => void }) { {formatMessage(labels.addWebsite)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.tsx b/src/app/(main)/settings/websites/WebsiteAddForm.tsx index 9f3ba178..6d8fb8c3 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -10,12 +10,15 @@ import { import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../SettingsContext'; export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error, isPending } = useMutation({ - mutationFn: (data: any) => post('/websites', data), + mutationFn: (data: any) => post(websitesUrl, data), }); const handleSubmit = async (data: any) => { diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.tsx similarity index 77% rename from src/app/(main)/settings/websites/WebsiteSettings.js rename to src/app/(main)/settings/websites/WebsiteSettings.tsx index ffda838c..e925d1cf 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState, Key } from 'react'; import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; @@ -10,33 +10,35 @@ import TrackingCode from './[id]/TrackingCode'; import ShareUrl from './[id]/ShareUrl'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import SettingsContext from '../SettingsContext'; -export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl }) { +export function WebsiteSettings({ websiteId, openExternal = false }) { const router = useRouter(); const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); + const { websitesUrl, settingsUrl } = useContext(SettingsContext); const { data, isLoading } = useQuery({ queryKey: ['website', websiteId], - queryFn: () => get(`/websites/${websiteId}`), + queryFn: () => get(`${websitesUrl}/${websiteId}`), enabled: !!websiteId, gcTime: 0, }); const [values, setValues] = useState(null); - const [tab, setTab] = useState('details'); + const [tab, setTab] = useState('details'); const showSuccess = () => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); }; - const handleSave = data => { + const handleSave = (data: any) => { showSuccess(); - setValues(state => ({ ...state, ...data })); + setValues((state: any) => ({ ...state, ...data })); }; - const handleReset = async value => { + const handleReset = async (value: string) => { if (value === 'delete') { - router.push('/settings/websites'); + router.push(settingsUrl); } else if (value === 'reset') { showSuccess(); } @@ -55,7 +57,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl return ( <> - + - {close => ( + {(close: () => void) => ( )} @@ -42,7 +42,7 @@ export function WebsiteData({ - {close => ( + {(close: () => void) => ( )} diff --git a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx index c3b5d74a..e0f71041 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx @@ -9,6 +9,8 @@ import { } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../../SettingsContext'; const CONFIRM_VALUE = 'DELETE'; @@ -22,12 +24,13 @@ export function WebsiteDeleteForm({ onClose?: () => void; }) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { del, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => del(`/websites/${websiteId}`, data), + mutationFn: (data: any) => del(`${websitesUrl}/${websiteId}`, data), }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx index 9c05905c..80b36cae 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx @@ -1,8 +1,9 @@ import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics'; -import { useRef } from 'react'; +import { useContext, useRef } from 'react'; import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import SettingsContext from '../../SettingsContext'; export function WebsiteEditForm({ websiteId, @@ -14,13 +15,14 @@ export function WebsiteEditForm({ onSave?: (data: any) => void; }) { const { formatMessage, labels, messages } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data), }); const ref = useRef(null); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { ref.current.reset(data); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx index 76c2bc47..0c02c77b 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx @@ -9,6 +9,8 @@ import { } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../../SettingsContext'; const CONFIRM_VALUE = 'RESET'; @@ -22,9 +24,10 @@ export function WebsiteResetForm({ onClose?: () => void; }) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}/reset`, data), }); const handleSubmit = async (data: any) => { diff --git a/src/app/sso/page.tsx b/src/app/sso/page.tsx index 75ea945d..e577767a 100644 --- a/src/app/sso/page.tsx +++ b/src/app/sso/page.tsx @@ -18,5 +18,5 @@ export default function SSOPage() { } }, [router, url, token]); - return ; + return ; } diff --git a/src/components/layout/Page.tsx b/src/components/layout/Page.tsx index 2f702012..e32a09a3 100644 --- a/src/components/layout/Page.tsx +++ b/src/components/layout/Page.tsx @@ -23,7 +23,7 @@ export function Page({ } if (isLoading) { - return ; + return ; } return
{children}
; diff --git a/src/index.ts b/src/index.ts index de555051..7b192054 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,8 @@ export * from 'app/(main)/settings/websites/WebsiteSettings'; export * from 'app/(main)/settings/websites/WebsitesDataTable'; export * from 'app/(main)/settings/websites/WebsitesTable'; +export * from 'app/(main)/settings/SettingsContext'; + export * from 'components/common/ConfirmDeleteForm'; export * from 'components/common/DataTable'; export * from 'components/common/Empty'; From c8eb76c7af8c6aee1a084c805314ff17eb4301c9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 6 Dec 2023 01:26:58 -0800 Subject: [PATCH 46/62] Fixed share url. --- src/app/(main)/settings/layout.tsx | 3 +-- src/app/(main)/settings/websites/WebsiteSettings.tsx | 2 +- src/app/(main)/settings/websites/[id]/ShareUrl.tsx | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index f9612361..1c30d2db 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -21,7 +21,7 @@ export default function SettingsLayout({ children }) { const getKey = () => items.find(({ url }) => pathname === url)?.key; - if (cloudMode && pathname != '/settings/profile') { + if (cloudMode && pathname !== '/settings/profile') { return null; } @@ -29,7 +29,6 @@ export default function SettingsLayout({ children }) { const config = { settingsUrl: '/settings/websites', - hostUrl, shareUrl: hostUrl, trackingCodeUrl: hostUrl, websitesUrl: `/websites`, diff --git a/src/app/(main)/settings/websites/WebsiteSettings.tsx b/src/app/(main)/settings/websites/WebsiteSettings.tsx index e925d1cf..4607b423 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.tsx +++ b/src/app/(main)/settings/websites/WebsiteSettings.tsx @@ -51,7 +51,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) { }, [data]); if (isLoading || !values) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx index 07a590fa..90ce6ea6 100644 --- a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx +++ b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx @@ -17,15 +17,15 @@ import SettingsContext from '../../SettingsContext'; const generateId = () => getRandomChars(16); export function ShareUrl({ websiteId, data, onSave }) { + const ref = useRef(null); + const { shareUrl, websitesUrl } = useContext(SettingsContext); const { formatMessage, labels, messages } = useMessages(); const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data), }); - const ref = useRef(null); - const { shareUrl } = useContext(SettingsContext); const url = useMemo( () => `${shareUrl}${process.env.basePath}/share/${id}/${encodeURIComponent(name)}`, [id, name], From e67282d7d895a7c430174a6b343d05df8fe54c88 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 8 Dec 2023 22:14:55 -0800 Subject: [PATCH 47/62] Render correct OS names. --- src/components/input/SettingsButton.tsx | 7 +------ src/components/metrics/OSTable.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/input/SettingsButton.tsx b/src/components/input/SettingsButton.tsx index 46c72597..2a076d42 100644 --- a/src/components/input/SettingsButton.tsx +++ b/src/components/input/SettingsButton.tsx @@ -15,12 +15,7 @@ export function SettingsButton() { - e.stopPropagation()} - > + diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index e8b8e81f..c39cba22 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -2,14 +2,20 @@ import MetricsTable from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; +const names = { + 'Mac OS': 'macOS', + 'Chrome OS': 'ChromeOS', + 'Sun OS': 'SunOS', +}; + export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) { const { formatMessage, labels } = useMessages(); function renderLink({ x: os }) { return ( - + {os} Date: Sat, 9 Dec 2023 00:35:54 -0800 Subject: [PATCH 48/62] Convert realtime components to TS. --- ...bsiteEventData.js => WebsiteEventData.tsx} | 2 +- .../realtime/{Realtime.js => Realtime.tsx} | 22 +++++----- ...timeCountries.js => RealtimeCountries.tsx} | 0 .../{RealtimeHeader.js => RealtimeHeader.tsx} | 5 ++- .../{RealtimeHome.js => RealtimeHome.tsx} | 0 .../{RealtimeLog.js => RealtimeLog.tsx} | 4 +- .../{RealtimeUrls.js => RealtimeUrls.tsx} | 21 ++++++---- .../{WebsiteReports.js => WebsiteReports.tsx} | 0 src/components/hooks/useDateRange.ts | 14 ++++--- src/components/metrics/RealtimeChart.tsx | 7 ++-- src/lib/date.ts | 40 ++++++++++--------- src/lib/types.ts | 11 ++++- 12 files changed, 75 insertions(+), 51 deletions(-) rename src/app/(main)/websites/[id]/event-data/{WebsiteEventData.js => WebsiteEventData.tsx} (96%) rename src/app/(main)/websites/[id]/realtime/{Realtime.js => Realtime.tsx} (84%) rename src/app/(main)/websites/[id]/realtime/{RealtimeCountries.js => RealtimeCountries.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHeader.js => RealtimeHeader.tsx} (85%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHome.js => RealtimeHome.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeLog.js => RealtimeLog.tsx} (97%) rename src/app/(main)/websites/[id]/realtime/{RealtimeUrls.js => RealtimeUrls.tsx} (85%) rename src/app/(main)/websites/[id]/reports/{WebsiteReports.js => WebsiteReports.tsx} (100%) diff --git a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx similarity index 96% rename from src/app/(main)/websites/[id]/event-data/WebsiteEventData.js rename to src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx index b67ee95e..61a4dc62 100644 --- a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js +++ b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx @@ -6,7 +6,7 @@ import { EventDataMetricsBar } from './EventDataMetricsBar'; import { useDateRange, useApi, useNavigation } from 'components/hooks'; import styles from './WebsiteEventData.module.css'; -function useData(websiteId, event) { +function useData(websiteId: string, event: string) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; const { get, useQuery } = useApi(); diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.tsx similarity index 84% rename from src/app/(main)/websites/[id]/realtime/Realtime.js rename to src/app/(main)/websites/[id]/realtime/Realtime.tsx index 37df458c..6de65d7a 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -1,7 +1,7 @@ 'use client'; import { useMemo, useState, useEffect } from 'react'; import { subMinutes, startOfMinute } from 'date-fns'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; @@ -15,9 +15,10 @@ import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import { useWebsite } from 'components/hooks'; +import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; -function mergeData(state = [], data = [], time) { +function mergeData(state = [], data = [], time: number) { const ids = state.map(({ __id }) => __id); return state .concat(data.filter(({ __id }) => !ids.includes(__id))) @@ -25,7 +26,7 @@ function mergeData(state = [], data = [], time) { } export function Realtime({ websiteId }) { - const [currentData, setCurrentData] = useState(); + const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); const { data: website } = useWebsite(websiteId); const { data, isLoading, error } = useQuery({ @@ -33,7 +34,6 @@ export function Realtime({ websiteId }) { queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), enabled: !!(websiteId && website), refetchInterval: REALTIME_INTERVAL, - cache: false, }); useEffect(() => { @@ -50,9 +50,9 @@ export function Realtime({ websiteId }) { } }, [data]); - const realtimeData = useMemo(() => { + const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { - return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] }; + return { pageviews: [], sessions: [], events: [], countries: [], visitors: [], timestamp: 0 }; } currentData.countries = percentFilter( @@ -75,7 +75,7 @@ export function Realtime({ websiteId }) { } return arr; }, []) - .sort(firstBy('y', -1)), + .sort(thenby.firstBy('y', -1)), ); currentData.visitors = currentData.sessions.reduce((arr, val) => { @@ -89,18 +89,18 @@ export function Realtime({ websiteId }) { }, [currentData]); if (isLoading || error) { - return ; + return ; } return ( <> - + - - + + diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeCountries.js b/src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeCountries.js rename to src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeHeader.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx index 75f2f2d4..ad03efd1 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx @@ -1,10 +1,11 @@ import MetricCard from 'components/metrics/MetricCard'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; import styles from './RealtimeHeader.module.css'; -export function RealtimeHeader({ data = {} }) { +export function RealtimeHeader({ data }: { data: RealtimeData }) { const { formatMessage, labels } = useMessages(); - const { pageviews, visitors, events, countries } = data; + const { pageviews, visitors, events, countries } = data || {}; return (
diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js b/src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeHome.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx similarity index 97% rename from src/app/(main)/websites/[id]/realtime/RealtimeLog.js rename to src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index b388b37b..e33320ec 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { StatusLight, Icon, Text } from 'react-basics'; import { FixedSizeList } from 'react-window'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import FilterButtons from 'components/common/FilterButtons'; import Empty from 'components/common/Empty'; import useLocale from 'components/hooks/useLocale'; @@ -130,7 +130,7 @@ export function RealtimeLog({ data, websiteDomain }) { } const { pageviews, visitors, events } = data; - const logs = [...pageviews, ...visitors, ...events].sort(firstBy('createdAt', -1)); + const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); if (filter !== TYPE_ALL) { return logs.filter(({ __type }) => __type === filter); diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeUrls.js rename to src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx index 674858b2..27a9ec5a 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx @@ -1,15 +1,22 @@ -import { useMemo, useState } from 'react'; +import { Key, useMemo, useState } from 'react'; import { ButtonGroup, Button, Flexbox } from 'react-basics'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { percentFilter } from 'lib/filters'; import ListTable from 'components/metrics/ListTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; -export function RealtimeUrls({ websiteDomain, data = {} }) { +export function RealtimeUrls({ + websiteDomain, + data, +}: { + websiteDomain: string; + data: RealtimeData; +}) { const { formatMessage, labels } = useMessages(); - const { pageviews } = data; - const [filter, setFilter] = useState(FILTER_REFERRERS); + const { pageviews } = data || {}; + const [filter, setFilter] = useState(FILTER_REFERRERS); const limit = 15; const buttons = [ @@ -48,7 +55,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); @@ -64,7 +71,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); diff --git a/src/app/(main)/websites/[id]/reports/WebsiteReports.js b/src/app/(main)/websites/[id]/reports/WebsiteReports.tsx similarity index 100% rename from src/app/(main)/websites/[id]/reports/WebsiteReports.js rename to src/app/(main)/websites/[id]/reports/WebsiteReports.tsx diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 71361b69..d8a49331 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -1,9 +1,10 @@ import { getMinimumUnit, parseDateRange } from 'lib/date'; import { setItem } from 'next-basics'; import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; -import useLocale from './useLocale'; import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; +import { DateRange } from 'lib/types'; +import useLocale from './useLocale'; import useApi from './useApi'; export function useDateRange(websiteId?: string) { @@ -14,9 +15,9 @@ export function useDateRange(websiteId?: string) { const globalConfig = appStore(state => state.dateRange); const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale); - const saveDateRange = async value => { + const saveDateRange = async (value: DateRange | string) => { if (websiteId) { - let dateRange = value; + let dateRange: DateRange | string = value; if (typeof value === 'string') { if (value === 'all') { @@ -37,14 +38,17 @@ export function useDateRange(websiteId?: string) { } } - setWebsiteDateRange(websiteId, dateRange); + setWebsiteDateRange(websiteId, dateRange as DateRange); } else { setItem(DATE_RANGE_CONFIG, value); setDateRange(value); } }; - return [dateRange, saveDateRange]; + return [dateRange, saveDateRange] as [ + { startDate: Date; endDate: Date }, + (value: string | DateRange) => void, + ]; } export default useDateRange; diff --git a/src/components/metrics/RealtimeChart.tsx b/src/components/metrics/RealtimeChart.tsx index f1a781bd..1ca0719a 100644 --- a/src/components/metrics/RealtimeChart.tsx +++ b/src/components/metrics/RealtimeChart.tsx @@ -3,6 +3,7 @@ import { format, startOfMinute, subMinutes, isBefore } from 'date-fns'; import PageviewsChart from './PageviewsChart'; import { getDateArray } from 'lib/date'; import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants'; +import { RealtimeData } from 'lib/types'; function mapData(data: any[]) { let last = 0; @@ -24,11 +25,9 @@ function mapData(data: any[]) { } export interface RealtimeChartProps { - data: { - pageviews: any[]; - visitors: any[]; - }; + data: RealtimeData; unit: string; + className?: string; } export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) { diff --git a/src/lib/date.ts b/src/lib/date.ts index 51057309..81c37e69 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -32,6 +32,7 @@ import { subWeeks, } from 'date-fns'; import { getDateLocale } from 'lib/lang'; +import { DateRange } from 'lib/types'; export const TIME_UNIT = { minute: 'minute', @@ -54,13 +55,13 @@ export function getTimezone() { return moment.tz.guess(); } -export function getLocalTime(t) { +export function getLocalTime(t: string | number | Date) { return addMinutes(new Date(t), new Date().getTimezoneOffset()); } -export function parseDateRange(value, locale = 'en-US') { +export function parseDateRange(value: string | object, locale = 'en-US'): DateRange { if (typeof value === 'object') { - return value; + return value as DateRange; } if (value === 'all') { @@ -93,7 +94,7 @@ export function parseDateRange(value, locale = 'en-US') { if (!match) return null; const { num, unit } = match.groups; - const selectedUnit = { num, unit }; + const selectedUnit = { num: +num, unit }; if (+num === 1) { switch (unit) { @@ -172,7 +173,7 @@ export function parseDateRange(value, locale = 'en-US') { switch (unit) { case 'day': return { - startDate: subDays(startOfDay(now), num - 1), + startDate: subDays(startOfDay(now), +num - 1), endDate: endOfDay(now), unit, value, @@ -180,7 +181,7 @@ export function parseDateRange(value, locale = 'en-US') { }; case 'hour': return { - startDate: subHours(startOfHour(now), num - 1), + startDate: subHours(startOfHour(now), +num - 1), endDate: endOfHour(now), unit, value, @@ -189,7 +190,10 @@ export function parseDateRange(value, locale = 'en-US') { } } -export function incrementDateRange(value, increment) { +export function incrementDateRange( + value: { startDate: any; endDate: any; selectedUnit: any }, + increment: number, +) { const { startDate, endDate, selectedUnit } = value; const { num, unit } = selectedUnit; @@ -235,7 +239,7 @@ export function incrementDateRange(value, increment) { } } -export function getAllowedUnits(startDate, endDate) { +export function getAllowedUnits(startDate: Date, endDate: Date) { const units = ['minute', 'hour', 'day', 'month', 'year']; const minUnit = getMinimumUnit(startDate, endDate); const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit); @@ -243,7 +247,7 @@ export function getAllowedUnits(startDate, endDate) { return index >= 0 ? units.splice(index) : []; } -export function getMinimumUnit(startDate, endDate) { +export function getMinimumUnit(startDate: number | Date, endDate: number | Date) { if (differenceInMinutes(endDate, startDate) <= 60) { return 'minute'; } else if (differenceInHours(endDate, startDate) <= 48) { @@ -257,25 +261,25 @@ export function getMinimumUnit(startDate, endDate) { return 'year'; } -export function getDateFromString(str) { +export function getDateFromString(str: string) { const [ymd, hms] = str.split(' '); const [year, month, day] = ymd.split('-'); if (hms) { const [hour, min, sec] = hms.split(':'); - return new Date(year, month - 1, day, hour, min, sec); + return new Date(+year, +month - 1, +day, +hour, +min, +sec); } - return new Date(year, month - 1, day); + return new Date(+year, +month - 1, +day); } -export function getDateArray(data, startDate, endDate, unit) { +export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: string) { const arr = []; const [diff, add, normalize] = dateFuncs[unit]; const n = diff(endDate, startDate) + 1; - function findData(date) { + function findData(date: Date) { const d = data.find(({ x }) => { return normalize(getDateFromString(x)).getTime() === date.getTime(); }); @@ -293,7 +297,7 @@ export function getDateArray(data, startDate, endDate, unit) { return arr; } -export function getDateLength(startDate, endDate, unit) { +export function getDateLength(startDate: Date, endDate: Date, unit: string | number) { const [diff] = dateFuncs[unit]; return diff(endDate, startDate) + 1; } @@ -310,7 +314,7 @@ export const CUSTOM_FORMATS = { }, }; -export function formatDate(date, str, locale = 'en-US') { +export function formatDate(date: string | number | Date, str: string, locale = 'en-US') { return format( typeof date === 'string' ? new Date(date) : date, CUSTOM_FORMATS?.[locale]?.[str] || str, @@ -320,10 +324,10 @@ export function formatDate(date, str, locale = 'en-US') { ); } -export function maxDate(...args) { +export function maxDate(...args: Date[]) { return max(args.filter(n => isDate(n))); } -export function minDate(...args) { +export function minDate(...args: any[]) { return min(args.filter(n => isDate(n))); } diff --git a/src/lib/types.ts b/src/lib/types.ts index a7fab1e7..900f2e5a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -180,7 +180,7 @@ export interface DateRange { endDate: Date; value: string; unit?: TimeUnit; - selectedUnit?: TimeUnit; + selectedUnit?: { num: number; unit: TimeUnit }; } export interface QueryFilters { @@ -207,3 +207,12 @@ export interface QueryOptions { joinSession?: boolean; columns?: { [key: string]: string }; } + +export interface RealtimeData { + pageviews: any[]; + sessions: any[]; + events: any[]; + timestamp: number; + countries?: any[]; + visitors?: any[]; +} From c520a329d25a7c8b66cf8c21372cabebca5adb9e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 01:25:02 -0800 Subject: [PATCH 49/62] Fixed activity log timestamp. --- package.json | 1 + src/app/(main)/websites/[id]/realtime/Realtime.tsx | 2 +- src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx | 6 +++--- yarn.lock | 7 +++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a158696c..782e039b 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "@types/node": "^20.9.0", "@types/react": "^18.2.41", "@types/react-dom": "^18.2.17", + "@types/react-window": "^1.8.8", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 6de65d7a..285d9845 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -95,7 +95,7 @@ export function Realtime({ websiteId }) { return ( <> - + diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index e33320ec..7cae0abc 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -8,11 +8,11 @@ import useLocale from 'components/hooks/useLocale'; import useCountryNames from 'components/hooks/useCountryNames'; import { BROWSERS } from 'lib/constants'; import { stringToColor } from 'lib/format'; -import { formatDate } from 'lib/date'; import { safeDecodeURI } from 'next-basics'; import Icons from 'components/icons'; import styles from './RealtimeLog.module.css'; import useMessages from 'components/hooks/useMessages'; +import { format } from 'date-fns'; const TYPE_ALL = 'all'; const TYPE_PAGEVIEW = 'pageview'; @@ -50,7 +50,7 @@ export function RealtimeLog({ data, websiteDomain }) { }, ]; - const getTime = ({ createdAt }) => formatDate(new Date(createdAt), 'pp', locale); + const getTime = ({ timestamp }) => format(timestamp, 'h:mm:ss'); const getColor = ({ id, sessionId }) => stringToColor(sessionId || id); @@ -146,7 +146,7 @@ export function RealtimeLog({ data, websiteDomain }) {
{logs?.length === 0 && } {logs?.length > 0 && ( - + {Row} )} diff --git a/yarn.lock b/yarn.lock index bdb0d484..1ecdaaf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2431,6 +2431,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-window@^1.8.8": + version "1.8.8" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@16 || 17 || 18": version "18.2.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b" From 7a5f28870f3f87452cc0d0e33ecba933308228f2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 01:41:07 -0800 Subject: [PATCH 50/62] Fixed realtime chart rendering of initial payload. --- .../websites/[id]/realtime/Realtime.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 285d9845..e77af289 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -38,15 +38,19 @@ export function Realtime({ websiteId }) { useEffect(() => { if (data) { - const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); - const time = date.getTime(); + if (!currentData) { + setCurrentData(data); + } else { + const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); + const time = date.getTime(); - setCurrentData(state => ({ - pageviews: mergeData(state?.pageviews, data.pageviews, time), - sessions: mergeData(state?.sessions, data.sessions, time), - events: mergeData(state?.events, data.events, time), - timestamp: data.timestamp, - })); + setCurrentData(state => ({ + pageviews: mergeData(state?.pageviews, data.pageviews, time), + sessions: mergeData(state?.sessions, data.sessions, time), + events: mergeData(state?.events, data.events, time), + timestamp: data.timestamp, + })); + } } }, [data]); From 92a513e4d06c861968d337e120eafac837242c6e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 20:55:50 -0800 Subject: [PATCH 51/62] Fixed realtime chart rendering. --- .../websites/[id]/realtime/Realtime.tsx | 27 +++++++--------- src/pages/api/realtime/[id].ts | 6 ++-- src/queries/analytics/events/getEvents.ts | 4 +++ src/queries/analytics/getRealtimeData.ts | 32 ++++++++++++++----- src/queries/analytics/sessions/getSessions.ts | 6 +++- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index e77af289..34e29281 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -19,9 +19,9 @@ import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; function mergeData(state = [], data = [], time: number) { - const ids = state.map(({ __id }) => __id); + const ids = state.map(({ id }) => id); return state - .concat(data.filter(({ __id }) => !ids.includes(__id))) + .concat(data.filter(({ id }) => !ids.includes(id))) .filter(({ timestamp }) => timestamp >= time); } @@ -38,21 +38,18 @@ export function Realtime({ websiteId }) { useEffect(() => { if (data) { - if (!currentData) { - setCurrentData(data); - } else { - const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); - const time = date.getTime(); + const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); + const time = date.getTime(); + const { pageviews, sessions, events, timestamp } = data; - setCurrentData(state => ({ - pageviews: mergeData(state?.pageviews, data.pageviews, time), - sessions: mergeData(state?.sessions, data.sessions, time), - events: mergeData(state?.events, data.events, time), - timestamp: data.timestamp, - })); - } + setCurrentData(state => ({ + pageviews: mergeData(state?.pageviews, pageviews, time), + sessions: mergeData(state?.sessions, sessions, time), + events: mergeData(state?.events, events, time), + timestamp, + })); } - }, [data]); + }, [data, currentData]); const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { diff --git a/src/pages/api/realtime/[id].ts b/src/pages/api/realtime/[id].ts index 212d4a0f..0edd589d 100644 --- a/src/pages/api/realtime/[id].ts +++ b/src/pages/api/realtime/[id].ts @@ -1,4 +1,4 @@ -import { subMinutes } from 'date-fns'; +import { startOfMinute, subMinutes } from 'date-fns'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; @@ -6,6 +6,8 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRealtimeData } from 'queries'; import * as yup from 'yup'; +import { REALTIME_RANGE } from 'lib/constants'; + export interface RealtimeRequestQuery { id: string; startAt: number; @@ -32,7 +34,7 @@ export default async ( return unauthorized(res); } - let startTime = subMinutes(new Date(), 30); + let startTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); if (+startAt > startTime.getTime()) { startTime = new Date(+startAt); diff --git a/src/queries/analytics/events/getEvents.ts b/src/queries/analytics/events/getEvents.ts index fe074ec2..9ef27973 100644 --- a/src/queries/analytics/events/getEvents.ts +++ b/src/queries/analytics/events/getEvents.ts @@ -18,6 +18,9 @@ function relationalQuery(websiteId: string, startDate: Date, eventType: number) gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -39,6 +42,7 @@ function clickhouseQuery(websiteId: string, startDate: Date, eventType: number) where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} and event_type = {eventType:UInt32} + order by created_at asc `, { websiteId, diff --git a/src/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts index 337e7475..868a5c70 100644 --- a/src/queries/analytics/getRealtimeData.ts +++ b/src/queries/analytics/getRealtimeData.ts @@ -1,4 +1,3 @@ -import { md5 } from 'next-basics'; import { getSessions, getEvents } from 'queries/index'; import { EVENT_TYPE } from 'lib/constants'; @@ -9,18 +8,35 @@ export async function getRealtimeData(websiteId: string, startDate: Date) { getEvents(websiteId, startDate, EVENT_TYPE.customEvent), ]); - const decorate = (id: string, data: any[]) => { - return data.map((props: { [key: string]: any }) => ({ - ...props, - __id: md5(id, ...Object.values(props)), - __type: id, - timestamp: props.timestamp ? props.timestamp * 1000 : new Date(props.createdAt).getTime(), + const decorate = (type: string, data: any[]) => { + return data.map((values: { [key: string]: any }) => ({ + ...values, + __type: type, + timestamp: values.timestamp ? values.timestamp * 1000 : new Date(values.createdAt).getTime(), })); }; + const set = new Set(); + const uniques = (type: string, data: any[]) => { + return data.reduce((arr, values: { [key: string]: any }) => { + if (!set.has(values.id)) { + set.add(values.id); + + return arr.concat({ + ...values, + __type: type, + timestamp: values.timestamp + ? values.timestamp * 1000 + : new Date(values.createdAt).getTime(), + }); + } + return arr; + }, []); + }; + return { pageviews: decorate('pageview', pageviews), - sessions: decorate('session', sessions), + sessions: uniques('session', sessions), events: decorate('event', events), timestamp: Date.now(), }; diff --git a/src/queries/analytics/sessions/getSessions.ts b/src/queries/analytics/sessions/getSessions.ts index d67edd5e..b92e3af9 100644 --- a/src/queries/analytics/sessions/getSessions.ts +++ b/src/queries/analytics/sessions/getSessions.ts @@ -17,6 +17,9 @@ async function relationalQuery(websiteId: string, startDate: Date) { gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -25,7 +28,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { return rawQuery( ` - select distinct + select session_id as id, website_id as websiteId, created_at as createdAt, @@ -43,6 +46,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { from website_event where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} + order by created_at asc `, { websiteId, From 44e243ad12b181bcaf05d644feb10e53f8b92acd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 21:30:57 -0800 Subject: [PATCH 52/62] Moved SettingsProvider to root Providers component. --- src/app/(main)/settings/layout.tsx | 28 ++++++------------- .../websites/[id]/realtime/Realtime.tsx | 2 +- src/app/Providers.tsx | 26 +++++++++++++---- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index 1c30d2db..e36b5b53 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -4,7 +4,6 @@ import useUser from 'components/hooks/useUser'; import useMessages from 'components/hooks/useMessages'; import SideNav from 'components/layout/SideNav'; import styles from './layout.module.css'; -import SettingsContext from './SettingsContext'; export default function SettingsLayout({ children }) { const { user } = useUser(); @@ -25,25 +24,14 @@ export default function SettingsLayout({ children }) { return null; } - const hostUrl = process.env.hostUrl || location.origin; - - const config = { - settingsUrl: '/settings/websites', - shareUrl: hostUrl, - trackingCodeUrl: hostUrl, - websitesUrl: `/websites`, - }; - return ( - -
- {!cloudMode && ( -
- -
- )} -
{children}
-
-
+
+ {!cloudMode && ( +
+ +
+ )} +
{children}
+
); } diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 34e29281..7f17190d 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -64,7 +64,7 @@ export function Realtime({ websiteId }) { } return arr; }, []) - .reduce((arr, { country }) => { + .reduce((arr: { x: any; y: number }[], { country }: any) => { if (country) { const row = arr.find(({ x }) => x === country); diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index c3d62699..0abebf86 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -3,6 +3,7 @@ import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactBasicsProvider } from 'react-basics'; import ErrorBoundary from 'components/common/ErrorBoundary'; +import SettingsContext from 'app/(main)/settings/SettingsContext'; import useLocale from 'components/hooks/useLocale'; import 'chartjs-adapter-date-fns'; @@ -24,14 +25,29 @@ function MessagesProvider({ children }) { ); } +function SettingsProvider({ children }) { + const hostUrl = process.env.hostUrl || location.origin; + + const config = { + settingsUrl: '/settings/websites', + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }; + + return {children}; +} + export function Providers({ children }) { return ( - - - {children} - - + + + + {children} + + + ); } From 08bd9e835720901f6d3c8867adfa492d4eb5c128 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 22:18:47 -0800 Subject: [PATCH 53/62] Upgrade to Next 14. --- package.json | 2 +- .../websites/[id]/realtime/Realtime.tsx | 10 +- src/app/Providers.tsx | 19 +-- yarn.lock | 111 +++++++++--------- 4 files changed, 74 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index 782e039b..db10755c 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", - "next": "13.5.6", + "next": "14.0.4", "next-basics": "^0.39.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 7f17190d..ee710d73 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -6,16 +6,16 @@ import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; import WorldMap from 'components/metrics/WorldMap'; +import useApi from 'components/hooks/useApi'; +import { useWebsite } from 'components/hooks'; +import { percentFilter } from 'lib/filters'; +import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; +import { RealtimeData } from 'lib/types'; import RealtimeLog from './RealtimeLog'; import RealtimeHeader from './RealtimeHeader'; import RealtimeUrls from './RealtimeUrls'; import RealtimeCountries from './RealtimeCountries'; import WebsiteHeader from '../WebsiteHeader'; -import useApi from 'components/hooks/useApi'; -import { percentFilter } from 'lib/filters'; -import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; -import { useWebsite } from 'components/hooks'; -import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; function mergeData(state = [], data = [], time: number) { diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index 0abebf86..c2ddd2ff 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useEffect, useState } from 'react'; import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactBasicsProvider } from 'react-basics'; @@ -26,14 +27,18 @@ function MessagesProvider({ children }) { } function SettingsProvider({ children }) { - const hostUrl = process.env.hostUrl || location.origin; + const [config, setConfig] = useState({}); - const config = { - settingsUrl: '/settings/websites', - shareUrl: hostUrl, - trackingCodeUrl: hostUrl, - websitesUrl: `/websites`, - }; + useEffect(() => { + const hostUrl = process.env.hostUrl || window?.location.origin; + + setConfig({ + settingsUrl: '/settings/websites', + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }); + }, []); return {children}; } diff --git a/yarn.lock b/yarn.lock index 1ecdaaf3..b24830a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1771,10 +1771,10 @@ "@netlify/node-cookies" "^0.1.0" urlpattern-polyfill "8.0.2" -"@next/env@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" - integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== +"@next/env@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a" + integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ== "@next/eslint-plugin-next@12.3.4": version "12.3.4" @@ -1783,50 +1783,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz#b15d139d8971360fca29be3bdd703c108c9a45fb" - integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== +"@next/swc-darwin-arm64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618" + integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg== -"@next/swc-darwin-x64@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" - integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== +"@next/swc-darwin-x64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b" + integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw== -"@next/swc-linux-arm64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" - integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== +"@next/swc-linux-arm64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21" + integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w== -"@next/swc-linux-arm64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" - integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== +"@next/swc-linux-arm64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd" + integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ== -"@next/swc-linux-x64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" - integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== +"@next/swc-linux-x64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32" + integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A== -"@next/swc-linux-x64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" - integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== +"@next/swc-linux-x64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247" + integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw== -"@next/swc-win32-arm64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" - integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== +"@next/swc-win32-arm64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3" + integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w== -"@next/swc-win32-ia32-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" - integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== +"@next/swc-win32-ia32-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600" + integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg== -"@next/swc-win32-x64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" - integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== +"@next/swc-win32-x64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1" + integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -4949,7 +4949,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6330,28 +6330,29 @@ next-basics@^0.39.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@13.5.6: - version "13.5.6" - resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" - integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== +next@14.0.4: + version "14.0.4" + resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc" + integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA== dependencies: - "@next/env" "13.5.6" + "@next/env" "14.0.4" "@swc/helpers" "0.5.2" busboy "1.6.0" caniuse-lite "^1.0.30001406" + graceful-fs "^4.2.11" postcss "8.4.31" styled-jsx "5.1.1" watchpack "2.4.0" optionalDependencies: - "@next/swc-darwin-arm64" "13.5.6" - "@next/swc-darwin-x64" "13.5.6" - "@next/swc-linux-arm64-gnu" "13.5.6" - "@next/swc-linux-arm64-musl" "13.5.6" - "@next/swc-linux-x64-gnu" "13.5.6" - "@next/swc-linux-x64-musl" "13.5.6" - "@next/swc-win32-arm64-msvc" "13.5.6" - "@next/swc-win32-ia32-msvc" "13.5.6" - "@next/swc-win32-x64-msvc" "13.5.6" + "@next/swc-darwin-arm64" "14.0.4" + "@next/swc-darwin-x64" "14.0.4" + "@next/swc-linux-arm64-gnu" "14.0.4" + "@next/swc-linux-arm64-musl" "14.0.4" + "@next/swc-linux-x64-gnu" "14.0.4" + "@next/swc-linux-x64-musl" "14.0.4" + "@next/swc-win32-arm64-msvc" "14.0.4" + "@next/swc-win32-ia32-msvc" "14.0.4" + "@next/swc-win32-x64-msvc" "14.0.4" nice-try@^1.0.4: version "1.0.5" From 3a28fea8aca027a8c54dfdc943a20d2810229392 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 22:45:55 -0800 Subject: [PATCH 54/62] Fixed broken links behavior. --- src/app/(main)/websites/[id]/realtime/Realtime.tsx | 2 +- src/app/share/[...id]/Header.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index ee710d73..bd9f74bc 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -49,7 +49,7 @@ export function Realtime({ websiteId }) { timestamp, })); } - }, [data, currentData]); + }, [data]); const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { diff --git a/src/app/share/[...id]/Header.tsx b/src/app/share/[...id]/Header.tsx index 41e93f52..2b82908d 100644 --- a/src/app/share/[...id]/Header.tsx +++ b/src/app/share/[...id]/Header.tsx @@ -19,8 +19,8 @@ export function Header() {
- - + +
From cad719fd235f400d78fe0c4459864498b411f669 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 10 Dec 2023 02:02:24 -0800 Subject: [PATCH 55/62] Added search to metrics table. --- .../(main)/websites/[id]/WebsiteDetails.tsx | 4 +- ...ule.css => WebsiteExpandedView.module.css} | 0 ...teMenuView.tsx => WebsiteExpandedView.tsx} | 11 ++--- src/components/hooks/useDateRange.ts | 2 +- src/components/hooks/useFormat.ts | 11 ++++- src/components/layout/SideNav.tsx | 2 +- src/components/metrics/CitiesTable.tsx | 4 +- .../metrics/MetricsTable.module.css | 20 +++++++++ src/components/metrics/MetricsTable.tsx | 38 +++++++++++++--- src/components/metrics/OSTable.tsx | 9 ++-- src/components/metrics/PagesTable.tsx | 25 +++++------ .../metrics/QueryParametersTable.tsx | 45 +++++++++---------- 12 files changed, 111 insertions(+), 60 deletions(-) rename src/app/(main)/websites/[id]/{WebsiteMenuView.module.css => WebsiteExpandedView.module.css} (100%) rename src/app/(main)/websites/[id]/{WebsiteMenuView.tsx => WebsiteExpandedView.tsx} (93%) diff --git a/src/app/(main)/websites/[id]/WebsiteDetails.tsx b/src/app/(main)/websites/[id]/WebsiteDetails.tsx index 4d3a18e7..7d8d2d99 100644 --- a/src/app/(main)/websites/[id]/WebsiteDetails.tsx +++ b/src/app/(main)/websites/[id]/WebsiteDetails.tsx @@ -6,7 +6,7 @@ import FilterTags from 'components/metrics/FilterTags'; import useNavigation from 'components/hooks/useNavigation'; import { useWebsite } from 'components/hooks'; import WebsiteChart from './WebsiteChart'; -import WebsiteMenuView from './WebsiteMenuView'; +import WebsiteExpandedView from './WebsiteExpandedView'; import WebsiteHeader from './WebsiteHeader'; import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteTableView from './WebsiteTableView'; @@ -34,7 +34,7 @@ export default function WebsiteDetails({ websiteId }: { websiteId: string }) { {website && ( <> {!view && } - {view && } + {view && } )} diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.module.css b/src/app/(main)/websites/[id]/WebsiteExpandedView.module.css similarity index 100% rename from src/app/(main)/websites/[id]/WebsiteMenuView.module.css rename to src/app/(main)/websites/[id]/WebsiteExpandedView.module.css diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx similarity index 93% rename from src/app/(main)/websites/[id]/WebsiteMenuView.tsx rename to src/app/(main)/websites/[id]/WebsiteExpandedView.tsx index 670ea469..e97cd002 100644 --- a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx +++ b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx @@ -15,7 +15,7 @@ import SideNav from 'components/layout/SideNav'; import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import LinkButton from 'components/common/LinkButton'; -import styles from './WebsiteMenuView.module.css'; +import styles from './WebsiteExpandedView.module.css'; const views = { url: PagesTable, @@ -33,7 +33,7 @@ const views = { query: QueryParametersTable, }; -export default function WebsiteMenuView({ +export default function WebsiteExpandedView({ websiteId, websiteDomain, }: { @@ -113,11 +113,11 @@ export default function WebsiteMenuView({ const DetailsComponent = views[view] || (() => null); - const handleChange = view => { + const handleChange = (view: any) => { router.push(makeUrl({ view })); }; - const renderValue = value => items.find(({ key }) => key === value)?.label; + const renderValue = (value: string) => items.find(({ key }) => key === value)?.label; return (
@@ -146,9 +146,10 @@ export default function WebsiteMenuView({ websiteDomain={websiteDomain} limit={false} animate={false} - showFilters={true} virtualize={true} itemCount={25} + allowFilter={true} + allowSearch={true} />
diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index d8a49331..efaa717f 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -46,7 +46,7 @@ export function useDateRange(websiteId?: string) { }; return [dateRange, saveDateRange] as [ - { startDate: Date; endDate: Date }, + { startDate: Date; endDate: Date; modified?: number }, (value: string | DateRange) => void, ]; } diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index f804eb72..06585e49 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -18,14 +18,19 @@ export function useFormat() { }; const formatRegion = (value: string): string => { - return regions[value] ? regions[value] : value; + const [country] = value.split('-'); + return regions[value] ? `${regions[value]}, ${countryNames[country]}` : value; + }; + + const formatCity = (value: string, country?: string): string => { + return `${value}, ${countryNames[country]}`; }; const formatDevice = (value: string): string => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value: string, type: string): string => { + const formatValue = (value: string, type: string, data?: { [key: string]: any }): string => { switch (type) { case 'browser': return formatBrowser(value); @@ -33,6 +38,8 @@ export function useFormat() { return formatCountry(value); case 'region': return formatRegion(value); + case 'city': + return formatCity(value, data?.country); case 'device': return formatDevice(value); default: diff --git a/src/components/layout/SideNav.tsx b/src/components/layout/SideNav.tsx index f38bdba0..0b5c9856 100644 --- a/src/components/layout/SideNav.tsx +++ b/src/components/layout/SideNav.tsx @@ -9,7 +9,7 @@ export interface SideNavProps { items: any[]; shallow?: boolean; scroll?: boolean; - className?: boolean; + className?: string; onSelect?: () => void; } diff --git a/src/components/metrics/CitiesTable.tsx b/src/components/metrics/CitiesTable.tsx index 69b89962..067e07e9 100644 --- a/src/components/metrics/CitiesTable.tsx +++ b/src/components/metrics/CitiesTable.tsx @@ -11,8 +11,8 @@ export function CitiesTable(props: MetricsTableProps) { const countryNames = useCountryNames(locale); const renderLabel = (city: string, country: string) => { - const name = countryNames[country]; - return name ? `${city}, ${name}` : city; + const countryName = countryNames[country]; + return countryName ? `${city}, ${countryName}` : city; }; const renderLink = ({ x: city, country }) => { diff --git a/src/components/metrics/MetricsTable.module.css b/src/components/metrics/MetricsTable.module.css index c00e4356..f04d9ae4 100644 --- a/src/components/metrics/MetricsTable.module.css +++ b/src/components/metrics/MetricsTable.module.css @@ -6,13 +6,33 @@ flex: 1; } +.actions { + display: flex; + gap: 20px; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + .footer { display: flex; justify-content: center; } +.search { + max-width: 300px; +} + @media only screen and (max-width: 992px) { .container { min-height: auto; } + + .actions { + flex-direction: column; + } + + .search { + max-width: 100%; + } } diff --git a/src/components/metrics/MetricsTable.tsx b/src/components/metrics/MetricsTable.tsx index d4ad793d..48beac68 100644 --- a/src/components/metrics/MetricsTable.tsx +++ b/src/components/metrics/MetricsTable.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { Loading, Icon, Text } from 'react-basics'; +import { ReactNode, useMemo, useState } from 'react'; +import { Loading, Icon, Text, SearchField } from 'react-basics'; import classNames from 'classnames'; import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; @@ -12,6 +12,7 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; +import useFormat from 'components//hooks/useFormat'; import styles from './MetricsTable.module.css'; export interface MetricsTableProps extends ListTableProps { @@ -22,6 +23,9 @@ export interface MetricsTableProps extends ListTableProps { limit?: number; delay?: number; onDataLoad?: (data: any) => void; + onSearch?: (search: string) => void; + allowSearch?: boolean; + children?: ReactNode; } export function MetricsTable({ @@ -32,8 +36,12 @@ export function MetricsTable({ limit, onDataLoad, delay = null, + allowSearch = false, + children, ...props }: MetricsTableProps) { + const [search, setSearch] = useState(''); + const { formatValue } = useFormat(); const [{ startDate, endDate, modified }] = useDateRange(websiteId); const { makeUrl, @@ -42,7 +50,6 @@ export function MetricsTable({ const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { dir } = useLocale(); - const { data, isLoading, isFetched, error } = useQuery({ queryKey: [ 'websites:metrics', @@ -94,24 +101,43 @@ export function MetricsTable({ } } + if (search) { + items = items.filter(({ x, ...data }) => { + const value = formatValue(x, type, data); + + return value.toLowerCase().includes(search.toLowerCase()); + }); + } + items = percentFilter(items); if (limit) { - items = items.filter((e, i) => i < limit); + items = items.slice(0, limit - 1); } return items; } return []; - }, [data, error, dataFilter, limit]); + }, [data, dataFilter, search, limit, formatValue, type]); return (
- {!data && isLoading && !isFetched && } {error && } +
+ {allowSearch && ( + + )} + {children} +
{data && !error && ( )} + {!data && isLoading && !isFetched && }
{data && !error && limit && ( diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index c39cba22..102bafd3 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -1,4 +1,4 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; @@ -8,7 +8,7 @@ const names = { 'Sun OS': 'SunOS', }; -export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) { +export function OSTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); function renderLink({ x: os }) { @@ -28,12 +28,11 @@ export function OSTable({ websiteId, limit }: { websiteId: string; limit?: numbe return ( ); } diff --git a/src/components/metrics/PagesTable.tsx b/src/components/metrics/PagesTable.tsx index 23676467..11379a2e 100644 --- a/src/components/metrics/PagesTable.tsx +++ b/src/components/metrics/PagesTable.tsx @@ -6,10 +6,10 @@ import useNavigation from 'components/hooks/useNavigation'; import { emptyFilter } from 'lib/filters'; export interface PagesTableProps extends MetricsTableProps { - showFilters?: boolean; + allowFilter?: boolean; } -export function PagesTable({ showFilters, ...props }: PagesTableProps) { +export function PagesTable({ allowFilter, ...props }: PagesTableProps) { const { router, makeUrl, @@ -37,17 +37,16 @@ export function PagesTable({ showFilters, ...props }: PagesTableProps) { }; return ( - <> - {showFilters && } - - + + {allowFilter && } + ); } diff --git a/src/components/metrics/QueryParametersTable.tsx b/src/components/metrics/QueryParametersTable.tsx index 65cac664..90489460 100644 --- a/src/components/metrics/QueryParametersTable.tsx +++ b/src/components/metrics/QueryParametersTable.tsx @@ -13,9 +13,9 @@ const filters = { }; export function QueryParametersTable({ - showFilters, + allowFilter, ...props -}: { showFilters: boolean } & MetricsTableProps) { +}: { allowFilter: boolean } & MetricsTableProps) { const [filter, setFilter] = useState(FILTER_COMBINED); const { formatMessage, labels } = useMessages(); @@ -28,27 +28,26 @@ export function QueryParametersTable({ ]; return ( - <> - {showFilters && } - - filter === FILTER_RAW ? ( - x - ) : ( -
-
{safeDecodeURI(p)}
-
{safeDecodeURI(v)}
-
- ) - } - delay={0} - /> - + + filter === FILTER_RAW ? ( + x + ) : ( +
+
{safeDecodeURI(p)}
+
{safeDecodeURI(v)}
+
+ ) + } + delay={0} + > + {allowFilter && } +
); } From 765874731d51337c7bdac449e11176e6bc8af2e0 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 10 Dec 2023 20:12:13 -0800 Subject: [PATCH 56/62] Added search to real-time activity log. --- .../[id]/realtime/RealtimeLog.module.css | 22 +++++++++ .../websites/[id]/realtime/RealtimeLog.tsx | 49 +++++++++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css b/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css index f400cc1b..e9c0fc1b 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css @@ -66,3 +66,25 @@ .row .link:hover { color: var(--primary400); } + +.search { + max-width: 300px; +} + +.actions { + display: flex; + gap: 20px; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +@media only screen and (max-width: 992px) { + .actions { + flex-direction: column; + } + + .search { + max-width: 100%; + } +} diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index 7cae0abc..5293c1f0 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -1,18 +1,19 @@ import { useMemo, useState } from 'react'; -import { StatusLight, Icon, Text } from 'react-basics'; +import { StatusLight, Icon, Text, SearchField } from 'react-basics'; import { FixedSizeList } from 'react-window'; +import { format } from 'date-fns'; import thenby from 'thenby'; +import { safeDecodeURI } from 'next-basics'; import FilterButtons from 'components/common/FilterButtons'; import Empty from 'components/common/Empty'; import useLocale from 'components/hooks/useLocale'; import useCountryNames from 'components/hooks/useCountryNames'; +import Icons from 'components/icons'; +import useMessages from 'components/hooks/useMessages'; +import useFormat from 'components//hooks/useFormat'; import { BROWSERS } from 'lib/constants'; import { stringToColor } from 'lib/format'; -import { safeDecodeURI } from 'next-basics'; -import Icons from 'components/icons'; import styles from './RealtimeLog.module.css'; -import useMessages from 'components/hooks/useMessages'; -import { format } from 'date-fns'; const TYPE_ALL = 'all'; const TYPE_PAGEVIEW = 'pageview'; @@ -26,7 +27,9 @@ const icons = { }; export function RealtimeLog({ data, websiteDomain }) { + const [search, setSearch] = useState(''); const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { formatValue } = useFormat(); const { locale } = useLocale(); const countryNames = useCountryNames(locale); const [filter, setFilter] = useState(TYPE_ALL); @@ -56,7 +59,15 @@ export function RealtimeLog({ data, websiteDomain }) { const getIcon = ({ __type }) => icons[__type]; - const getDetail = log => { + const getDetail = (log: { + __type: any; + eventName: any; + urlPath: any; + browser: any; + os: any; + country: any; + device: any; + }) => { const { __type, eventName, urlPath: url, browser, os, country, device } = log; if (__type === TYPE_EVENT) { @@ -130,18 +141,38 @@ export function RealtimeLog({ data, websiteDomain }) { } const { pageviews, visitors, events } = data; - const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); + let logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); + + if (search) { + logs = logs.filter(({ eventName, urlPath, browser, os, country, device }) => { + return [ + eventName, + urlPath, + os, + formatValue(browser, 'browser'), + formatValue(country, 'country'), + formatValue(device, 'device'), + ] + .filter(n => n) + .map(n => n.toLowerCase()) + .join('') + .includes(search.toLowerCase()); + }); + } if (filter !== TYPE_ALL) { return logs.filter(({ __type }) => __type === filter); } return logs; - }, [data, filter]); + }, [data, filter, formatValue, search]); return (
- +
+ + +
{formatMessage(labels.activityLog)}
{logs?.length === 0 && } From a851ebf1247ac65e6246a20b72bafd07f31c61d2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 11 Dec 2023 00:15:26 -0800 Subject: [PATCH 57/62] Bump version 2.9.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db10755c..0f437c35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.8.0", + "version": "2.9.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", From 907685b96e0caae51846fc6dfe55d9ddfe16d7cd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 12 Dec 2023 19:00:44 -0800 Subject: [PATCH 58/62] Added limit to metrics queries. --- .../(main)/websites/[id]/WebsiteExpandedView.tsx | 1 - src/components/metrics/MetricsTable.tsx | 1 + src/lib/prisma.ts | 6 +++++- src/lib/types.ts | 1 + src/pages/api/websites/[id]/metrics.ts | 7 +++++-- .../analytics/pageviews/getPageviewMetrics.ts | 14 ++++++++++---- .../analytics/sessions/getSessionMetrics.ts | 14 ++++++++++---- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx index e97cd002..9fb1b3f6 100644 --- a/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx +++ b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx @@ -144,7 +144,6 @@ export default function WebsiteExpandedView({ relationalQuery(...args), @@ -13,7 +13,12 @@ export async function getPageviewMetrics( }); } -async function relationalQuery(websiteId: string, column: string, filters: QueryFilters) { +async function relationalQuery( + websiteId: string, + column: string, + filters: QueryFilters, + limit: number = 100, +) { const { rawQuery, parseFilters } = prisma; const { filterQuery, joinSession, params } = await parseFilters( websiteId, @@ -42,7 +47,7 @@ async function relationalQuery(websiteId: string, column: string, filters: Query ${filterQuery} group by 1 order by 2 desc - limit 100 + limit ${limit} `, params, ); @@ -52,6 +57,7 @@ async function clickhouseQuery( websiteId: string, column: string, filters: QueryFilters, + limit: number = 100, ): Promise<{ x: string; y: number }[]> { const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -75,7 +81,7 @@ async function clickhouseQuery( ${filterQuery} group by x order by y desc - limit 100 + limit ${limit} `, params, ).then(a => { diff --git a/src/queries/analytics/sessions/getSessionMetrics.ts b/src/queries/analytics/sessions/getSessionMetrics.ts index 3573ac1e..c6877a3f 100644 --- a/src/queries/analytics/sessions/getSessionMetrics.ts +++ b/src/queries/analytics/sessions/getSessionMetrics.ts @@ -5,7 +5,7 @@ import { EVENT_TYPE, SESSION_COLUMNS } from 'lib/constants'; import { QueryFilters } from 'lib/types'; export async function getSessionMetrics( - ...args: [websiteId: string, column: string, filters: QueryFilters] + ...args: [websiteId: string, column: string, filters: QueryFilters, limit?: number] ) { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -13,7 +13,12 @@ export async function getSessionMetrics( }); } -async function relationalQuery(websiteId: string, column: string, filters: QueryFilters) { +async function relationalQuery( + websiteId: string, + column: string, + filters: QueryFilters, + limit: number = 100, +) { const { parseFilters, rawQuery } = prisma; const { filterQuery, joinSession, params } = await parseFilters( websiteId, @@ -42,7 +47,7 @@ async function relationalQuery(websiteId: string, column: string, filters: Query group by 1 ${includeCountry ? ', 3' : ''} order by 2 desc - limit 100`, + limit ${limit}`, params, ); } @@ -51,6 +56,7 @@ async function clickhouseQuery( websiteId: string, column: string, filters: QueryFilters, + limit: number = 100, ): Promise<{ x: string; y: number }[]> { const { parseFilters, rawQuery } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -73,7 +79,7 @@ async function clickhouseQuery( group by x ${includeCountry ? ', country' : ''} order by y desc - limit 100 + limit ${limit} `, params, ).then(a => { From 9735769413abab1a966249621459d5e9f8b2e5fe Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 12 Dec 2023 19:20:34 -0800 Subject: [PATCH 59/62] Removed Node 16 from GH workflow. --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 775f9ecf..66e16a03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,6 @@ jobs: strategy: matrix: include: - - node-version: 16.x - db-type: postgresql - - node-version: 16.x - db-type: mysql - node-version: 18.x db-type: postgresql - node-version: 18.x From e1c65cdf2ac6db01497a2bab922d8497a94ab457 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 12 Dec 2023 20:05:45 -0800 Subject: [PATCH 60/62] Updated loading for reports. --- src/app/(main)/reports/[id]/Report.tsx | 5 +++-- src/app/(main)/reports/[id]/ReportBody.tsx | 8 ++++++++ src/app/(main)/reports/[id]/ReportMenu.tsx | 8 ++++++++ src/components/hooks/useReport.ts | 10 +++++----- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/(main)/reports/[id]/Report.tsx b/src/app/(main)/reports/[id]/Report.tsx index b100ad8e..c1cc502f 100644 --- a/src/app/(main)/reports/[id]/Report.tsx +++ b/src/app/(main)/reports/[id]/Report.tsx @@ -1,5 +1,6 @@ 'use client'; import { createContext, ReactNode } from 'react'; +import { Loading } from 'react-basics'; import { useReport } from 'components/hooks'; import styles from './Report.module.css'; import classNames from 'classnames'; @@ -17,11 +18,11 @@ export function Report({ reportId, defaultParameters, children, className }: Rep const report = useReport(reportId, defaultParameters); if (!report) { - return null; + return reportId ? : null; } return ( - +
{children}
); diff --git a/src/app/(main)/reports/[id]/ReportBody.tsx b/src/app/(main)/reports/[id]/ReportBody.tsx index a116bf8e..6f4627f6 100644 --- a/src/app/(main)/reports/[id]/ReportBody.tsx +++ b/src/app/(main)/reports/[id]/ReportBody.tsx @@ -1,6 +1,14 @@ import styles from './ReportBody.module.css'; +import { useContext } from 'react'; +import { ReportContext } from './Report'; export function ReportBody({ children }) { + const { report } = useContext(ReportContext); + + if (!report) { + return null; + } + return
{children}
; } diff --git a/src/app/(main)/reports/[id]/ReportMenu.tsx b/src/app/(main)/reports/[id]/ReportMenu.tsx index 72bc197a..9478a903 100644 --- a/src/app/(main)/reports/[id]/ReportMenu.tsx +++ b/src/app/(main)/reports/[id]/ReportMenu.tsx @@ -1,6 +1,14 @@ import styles from './ReportMenu.module.css'; +import { useContext } from 'react'; +import { ReportContext } from './Report'; export function ReportMenu({ children }) { + const { report } = useContext(ReportContext); + + if (!report) { + return null; + } + return
{children}
; } diff --git a/src/components/hooks/useReport.ts b/src/components/hooks/useReport.ts index 1686e222..7769ed6c 100644 --- a/src/components/hooks/useReport.ts +++ b/src/components/hooks/useReport.ts @@ -4,7 +4,7 @@ import { useTimezone } from './useTimezone'; import useApi from './useApi'; import useMessages from './useMessages'; -export function useReport(reportId, defaultParameters) { +export function useReport(reportId: string, defaultParameters: { [key: string]: any }) { const [report, setReport] = useState(null); const [isRunning, setIsRunning] = useState(false); const { get, post } = useApi(); @@ -17,7 +17,7 @@ export function useReport(reportId, defaultParameters) { parameters: {}, }; - const loadReport = async id => { + const loadReport = async (id: string) => { const data: any = await get(`/reports/${id}`); const { dateRange } = data?.parameters || {}; @@ -32,7 +32,7 @@ export function useReport(reportId, defaultParameters) { }; const runReport = useCallback( - async parameters => { + async (parameters: { [key: string]: any }) => { setIsRunning(true); const { type } = report; @@ -50,11 +50,11 @@ export function useReport(reportId, defaultParameters) { setIsRunning(false); }, - [report], + [report, timezone], ); const updateReport = useCallback( - async data => { + async (data: { [x: string]: any; parameters: any }) => { setReport( produce((state: any) => { const { parameters, ...rest } = data; From 442ad61779c80224e124a580b68df12715e32100 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 12 Dec 2023 21:23:12 -0800 Subject: [PATCH 61/62] Created admin API endpoints. --- .../(main)/settings/users/UsersDataTable.tsx | 2 +- .../settings/websites/WebsiteSettings.tsx | 6 +- .../settings/websites/WebsitesTable.tsx | 6 +- src/app/Providers.tsx | 5 +- src/pages/api/admin/users.ts | 53 +++++++++++++++ src/pages/api/admin/websites.ts | 66 +++++++++++++++++++ src/pages/api/users/index.ts | 16 +---- src/pages/api/websites/index.ts | 28 +------- 8 files changed, 133 insertions(+), 49 deletions(-) create mode 100644 src/pages/api/admin/users.ts create mode 100644 src/pages/api/admin/websites.ts diff --git a/src/app/(main)/settings/users/UsersDataTable.tsx b/src/app/(main)/settings/users/UsersDataTable.tsx index b7716451..2495d023 100644 --- a/src/app/(main)/settings/users/UsersDataTable.tsx +++ b/src/app/(main)/settings/users/UsersDataTable.tsx @@ -11,7 +11,7 @@ export function UsersDataTable() { const modified = useCache((state: any) => state?.users); const queryResult = useFilterQuery({ queryKey: ['users', { modified }], - queryFn: (params: { [key: string]: any }) => get(`/users`, params), + queryFn: (params: { [key: string]: any }) => get(`/admin/users`, params), }); return ( diff --git a/src/app/(main)/settings/websites/WebsiteSettings.tsx b/src/app/(main)/settings/websites/WebsiteSettings.tsx index 4607b423..0c5ce614 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.tsx +++ b/src/app/(main)/settings/websites/WebsiteSettings.tsx @@ -17,7 +17,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) { const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { websitesUrl, settingsUrl } = useContext(SettingsContext); + const { websitesUrl, websitesPath, settingsPath } = useContext(SettingsContext); const { data, isLoading } = useQuery({ queryKey: ['website', websiteId], queryFn: () => get(`${websitesUrl}/${websiteId}`), @@ -38,7 +38,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) { const handleReset = async (value: string) => { if (value === 'delete') { - router.push(settingsUrl); + router.push(settingsPath); } else if (value === 'reset') { showSuccess(); } @@ -57,7 +57,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) { return ( <> - +