From 8cc571f548d2a28b50d86bd7540a555c1f8a5e92 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Thu, 11 Dec 2025 08:53:13 +0300 Subject: [PATCH 001/107] fix: handle denied storage access --- src/tracker/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tracker/index.js b/src/tracker/index.js index ad3648ac..85d27430 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -12,7 +12,13 @@ if (!currentScript) return; const { hostname, href, origin } = location; - const localStorage = href.startsWith('data:') ? undefined : window.localStorage; + + let localStorage; + try { + localStorage = href.startsWith('data:') ? undefined : window.localStorage; + } catch { + /* (DOMException) SecurityError: Access is denied for this document. */ + } const _data = 'data-'; const _false = 'false'; From b088a2ee6edb05ee5ccf103c2b2a2fe2a7721049 Mon Sep 17 00:00:00 2001 From: Kristofor Carle Date: Thu, 11 Dec 2025 14:53:08 -0500 Subject: [PATCH 002/107] fix prisma session race condition error --- src/queries/sql/sessions/saveSessionData.ts | 36 ++++++++------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/queries/sql/sessions/saveSessionData.ts b/src/queries/sql/sessions/saveSessionData.ts index 74093177..cce6cd28 100644 --- a/src/queries/sql/sessions/saveSessionData.ts +++ b/src/queries/sql/sessions/saveSessionData.ts @@ -46,31 +46,23 @@ export async function relationalQuery({ createdAt, })); - const existing = await client.sessionData.findMany({ - where: { - sessionId, - }, - select: { - id: true, - sessionId: true, - dataKey: true, - }, - }); - for (const data of flattenedData) { const { sessionId, dataKey, ...props } = data; - const record = existing.find(e => e.sessionId === sessionId && e.dataKey === dataKey); - if (record) { - await client.sessionData.update({ - where: { - id: record.id, - }, - data: { - ...props, - }, - }); - } else { + // Try to update existing record using compound where clause + // This is safer than using id from a previous query due to race conditions + const updateResult = await client.sessionData.updateMany({ + where: { + sessionId, + dataKey, + }, + data: { + ...props, + }, + }); + + // If no record was updated, create a new one + if (updateResult.count === 0) { await client.sessionData.create({ data, }); From 5b97fb908aa867e0320a506923b334f47bdb6608 Mon Sep 17 00:00:00 2001 From: Egor Fedorov Date: Thu, 11 Dec 2025 23:24:01 +0300 Subject: [PATCH 003/107] fix(events): use correct key for event values --- src/components/hooks/queries/useEventDataValuesQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/hooks/queries/useEventDataValuesQuery.ts b/src/components/hooks/queries/useEventDataValuesQuery.ts index 6529e142..db2a2d7e 100644 --- a/src/components/hooks/queries/useEventDataValuesQuery.ts +++ b/src/components/hooks/queries/useEventDataValuesQuery.ts @@ -16,7 +16,7 @@ export function useEventDataValuesQuery( return useQuery({ queryKey: [ 'websites:event-data:values', - { websiteId, event, propertyName, startAt, endAt, unit, timezone, ...filters }, + { websiteId, startAt, endAt, unit, timezone, ...filters, event, propertyName }, ], queryFn: () => get(`/websites/${websiteId}/event-data/values`, { From e9cdabab5ae30a86d4c68b4801d13af046ce1cfb Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Dec 2025 17:58:20 -0800 Subject: [PATCH 004/107] Upgraded react and next. --- package.json | 6 +- pnpm-lock.yaml | 1948 ++++++++++++++++++++++++------------------------ 2 files changed, 976 insertions(+), 978 deletions(-) diff --git a/package.json b/package.json index 09a69135..39ed6e86 100644 --- a/package.json +++ b/package.json @@ -102,15 +102,15 @@ "kafkajs": "^2.1.0", "lucide-react": "^0.543.0", "maxmind": "^5.0.0", - "next": "^15.5.7", + "next": "^15.5.9", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "papaparse": "^5.5.3", "pg": "^8.16.3", "prisma": "^6.18.0", "pure-rand": "^7.0.1", - "react": "^19.2.1", - "react-dom": "^19.2.1", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-error-boundary": "^4.0.4", "react-intl": "^7.1.14", "react-simple-maps": "^2.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10eed821..3f2b1ce6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 5.2.8 '@hello-pangea/dnd': specifier: ^17.0.0 - version: 17.0.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 17.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@prisma/adapter-pg': specifier: ^6.18.0 version: 6.19.0 @@ -37,16 +37,16 @@ importers: version: 0.4.1(@prisma/client@6.19.0(prisma@6.19.0(typescript@5.9.3))(typescript@5.9.3)) '@react-spring/web': specifier: ^10.0.3 - version: 10.0.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 10.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@svgr/cli': specifier: ^8.1.0 version: 8.1.0(typescript@5.9.3) '@tanstack/react-query': specifier: ^5.90.11 - version: 5.90.11(react@19.2.1) + version: 5.90.11(react@19.2.3) '@umami/react-zen': specifier: ^0.211.0 - version: 0.211.0(@babel/core@7.28.3)(@types/react@19.2.7)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.1)) + version: 0.211.0(@babel/core@7.28.3)(@types/react@19.2.7)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.3)) '@umami/redis-client': specifier: ^0.29.0 version: 0.29.0 @@ -127,13 +127,13 @@ importers: version: 2.2.4 lucide-react: specifier: ^0.543.0 - version: 0.543.0(react@19.2.1) + version: 0.543.0(react@19.2.3) maxmind: specifier: ^5.0.0 version: 5.0.1 next: - specifier: ^15.5.7 - version: 15.5.7(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + specifier: ^15.5.9 + version: 15.5.9(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) node-fetch: specifier: ^3.2.8 version: 3.3.2 @@ -153,26 +153,26 @@ importers: specifier: ^7.0.1 version: 7.0.1 react: - specifier: ^19.2.1 - version: 19.2.1 + specifier: ^19.2.3 + version: 19.2.3 react-dom: - specifier: ^19.2.1 - version: 19.2.1(react@19.2.1) + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) react-error-boundary: specifier: ^4.0.4 - version: 4.1.2(react@19.2.1) + version: 4.1.2(react@19.2.3) react-intl: specifier: ^7.1.14 - version: 7.1.14(react@19.2.1)(typescript@5.9.3) + version: 7.1.14(react@19.2.3)(typescript@5.9.3) react-simple-maps: specifier: ^2.3.0 - version: 2.3.0(prop-types@15.8.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.3.0(prop-types@15.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-use-measure: specifier: ^2.0.4 - version: 2.1.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 2.1.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-window: specifier: ^1.8.6 - version: 1.8.11(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 1.8.11(react-dom@19.2.3(react@19.2.3))(react@19.2.3) request-ip: specifier: ^3.3.0 version: 3.3.0 @@ -196,7 +196,7 @@ importers: version: 4.1.13 zustand: specifier: ^5.0.9 - version: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) + version: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@biomejs/biome': specifier: ^2.3.8 @@ -331,8 +331,6 @@ importers: specifier: ^5.9.3 version: 5.9.3 - dist: {} - packages: '@ampproject/remapping@2.3.0': @@ -1693,11 +1691,11 @@ packages: resolution: {integrity: sha512-HXm94tteOuA0FYwhkxjYIPe0zta+Dsu0wz7LnhfqVlaYcRaOLjHtd2vgfmpz3np/fx9TQg3gCfqGkXt2a9i7Aw==} engines: {node: '>=18.0.0'} - '@next/env@15.5.7': - resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==} + '@next/env@15.5.9': + resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} - '@next/env@16.0.7': - resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} + '@next/env@16.0.10': + resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} '@next/swc-darwin-arm64@15.5.7': resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==} @@ -1705,8 +1703,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@16.0.7': - resolution: {integrity: sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==} + '@next/swc-darwin-arm64@16.0.10': + resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1717,8 +1715,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@16.0.7': - resolution: {integrity: sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==} + '@next/swc-darwin-x64@16.0.10': + resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1729,8 +1727,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@16.0.7': - resolution: {integrity: sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==} + '@next/swc-linux-arm64-gnu@16.0.10': + resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1741,8 +1739,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.7': - resolution: {integrity: sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==} + '@next/swc-linux-arm64-musl@16.0.10': + resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1753,8 +1751,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.7': - resolution: {integrity: sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==} + '@next/swc-linux-x64-gnu@16.0.10': + resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1765,8 +1763,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.7': - resolution: {integrity: sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==} + '@next/swc-linux-x64-musl@16.0.10': + resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1777,8 +1775,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@16.0.7': - resolution: {integrity: sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==} + '@next/swc-win32-arm64-msvc@16.0.10': + resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1789,8 +1787,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.7': - resolution: {integrity: sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==} + '@next/swc-win32-x64-msvc@16.0.10': + resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -5246,8 +5244,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next@15.5.7: - resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==} + next@15.5.9: + resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -5267,8 +5265,8 @@ packages: sass: optional: true - next@16.0.7: - resolution: {integrity: sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==} + next@16.0.10: + resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -6131,10 +6129,10 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom@19.2.1: - resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: - react: ^19.2.1 + react: ^19.2.3 react-error-boundary@4.1.2: resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==} @@ -6207,8 +6205,8 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.2.1: - resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} read-babelrc-up@1.1.0: @@ -8022,17 +8020,17 @@ snapshots: optionalDependencies: ts-jest: 29.4.6(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.12)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(typescript@5.9.3) - '@hello-pangea/dnd@17.0.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@hello-pangea/dnd@17.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.28.3 css-box-model: 1.2.1 memoize-one: 6.0.0 raf-schd: 4.0.3 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.1)(redux@5.0.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) redux: 5.0.1 - use-memo-one: 1.1.3(react@19.2.1) + use-memo-one: 1.1.3(react@19.2.3) transitivePeerDependencies: - '@types/react' @@ -8478,56 +8476,56 @@ snapshots: '@netlify/plugin-nextjs@5.15.1': {} - '@next/env@15.5.7': {} + '@next/env@15.5.9': {} - '@next/env@16.0.7': {} + '@next/env@16.0.10': {} '@next/swc-darwin-arm64@15.5.7': optional: true - '@next/swc-darwin-arm64@16.0.7': + '@next/swc-darwin-arm64@16.0.10': optional: true '@next/swc-darwin-x64@15.5.7': optional: true - '@next/swc-darwin-x64@16.0.7': + '@next/swc-darwin-x64@16.0.10': optional: true '@next/swc-linux-arm64-gnu@15.5.7': optional: true - '@next/swc-linux-arm64-gnu@16.0.7': + '@next/swc-linux-arm64-gnu@16.0.10': optional: true '@next/swc-linux-arm64-musl@15.5.7': optional: true - '@next/swc-linux-arm64-musl@16.0.7': + '@next/swc-linux-arm64-musl@16.0.10': optional: true '@next/swc-linux-x64-gnu@15.5.7': optional: true - '@next/swc-linux-x64-gnu@16.0.7': + '@next/swc-linux-x64-gnu@16.0.10': optional: true '@next/swc-linux-x64-musl@15.5.7': optional: true - '@next/swc-linux-x64-musl@16.0.7': + '@next/swc-linux-x64-musl@16.0.10': optional: true '@next/swc-win32-arm64-msvc@15.5.7': optional: true - '@next/swc-win32-arm64-msvc@16.0.7': + '@next/swc-win32-arm64-msvc@16.0.10': optional: true '@next/swc-win32-x64-msvc@15.5.7': optional: true - '@next/swc-win32-x64-msvc@16.0.7': + '@next/swc-win32-x64-msvc@16.0.10': optional: true '@nodelib/fs.scandir@2.1.5': @@ -8596,1110 +8594,1110 @@ snapshots: dependencies: '@prisma/debug': 6.19.0 - '@react-aria/autocomplete@3.0.0-rc.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/autocomplete@3.0.0-rc.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/combobox': 3.14.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/listbox': 3.15.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/searchfield': 3.8.9(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.1) - '@react-stately/combobox': 3.12.0(react@19.2.1) - '@react-types/autocomplete': 3.0.0-alpha.35(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/combobox': 3.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/listbox': 3.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/searchfield': 3.8.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.3) + '@react-stately/combobox': 3.12.0(react@19.2.3) + '@react-types/autocomplete': 3.0.0-alpha.35(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/breadcrumbs@3.5.29(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/breadcrumbs@3.5.29(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/link': 3.8.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/breadcrumbs': 3.7.17(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/link': 3.8.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/breadcrumbs': 3.7.17(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/button@3.14.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/button@3.14.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/toggle': 3.9.2(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/toggle': 3.9.2(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/calendar@3.9.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/calendar@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/calendar': 3.9.0(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/calendar': 3.8.0(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/calendar': 3.9.0(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/calendar': 3.8.0(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/checkbox@3.16.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/checkbox@3.16.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/toggle': 3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/checkbox': 3.7.2(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/toggle': 3.9.2(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/form': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/toggle': 3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/checkbox': 3.7.2(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/toggle': 3.9.2(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/collections@3.0.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/collections@3.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - use-sync-external-store: 1.6.0(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) - '@react-aria/color@3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/color@3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/numberfield': 3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/slider': 3.8.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/color': 3.9.2(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-types/color': 3.1.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/numberfield': 3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/slider': 3.8.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/spinbutton': 3.6.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/color': 3.9.2(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-types/color': 3.1.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/combobox@3.14.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/combobox@3.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/listbox': 3.15.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/listbox': 3.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/menu': 3.19.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/combobox': 3.12.0(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/combobox': 3.13.9(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/menu': 3.19.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/combobox': 3.12.0(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/combobox': 3.13.9(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/datepicker@3.15.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/datepicker@3.15.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/form': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/datepicker': 3.15.2(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/calendar': 3.8.0(react@19.2.1) - '@react-types/datepicker': 3.13.2(react@19.2.1) - '@react-types/dialog': 3.5.22(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/form': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/spinbutton': 3.6.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/datepicker': 3.15.2(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/calendar': 3.8.0(react@19.2.3) + '@react-types/datepicker': 3.13.2(react@19.2.3) + '@react-types/dialog': 3.5.22(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/dialog@3.5.31(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/dialog@3.5.31(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/dialog': 3.5.22(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/dialog': 3.5.22(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/disclosure@3.1.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/disclosure@3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/disclosure': 3.0.8(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/disclosure': 3.0.8(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/dnd@3.11.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/dnd@3.11.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@internationalized/string': 3.2.7 - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/dnd': 3.7.1(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/dnd': 3.7.1(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/focus@3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/focus@3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 clsx: 2.1.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/form@3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/form@3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/grid@3.14.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/grid@3.14.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/grid': 3.11.6(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/grid': 3.11.6(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/gridlist@3.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/gridlist@3.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/grid': 3.14.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-stately/tree': 3.9.3(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/grid': 3.14.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-stately/tree': 3.9.3(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/i18n@3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/i18n@3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 '@internationalized/message': 3.1.8 '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/interactions@3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/interactions@3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-stately/flags': 3.1.2 - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/label@3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/label@3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/landmark@3.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/landmark@3.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - use-sync-external-store: 1.6.0(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) - '@react-aria/link@3.8.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/link@3.8.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/link': 3.6.5(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/link': 3.6.5(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/listbox@3.15.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/listbox@3.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-types/listbox': 3.7.4(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-types/listbox': 3.7.4(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) '@react-aria/live-announcer@3.4.4': dependencies: '@swc/helpers': 0.5.17 - '@react-aria/menu@3.19.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/menu@3.19.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/menu': 3.9.8(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/tree': 3.9.3(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/menu': 3.10.5(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/menu': 3.9.8(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/tree': 3.9.3(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/menu': 3.10.5(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/meter@3.4.27(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/meter@3.4.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/progress': 3.4.27(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/meter': 3.4.13(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/progress': 3.4.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/meter': 3.4.13(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/numberfield@3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/numberfield@3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/numberfield': 3.10.2(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/numberfield': 3.8.15(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/spinbutton': 3.6.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/numberfield': 3.10.2(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/numberfield': 3.8.15(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/overlays@3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/overlays@3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/overlays': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/overlays': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/progress@3.4.27(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/progress@3.4.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/progress': 3.5.16(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/progress': 3.5.16(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/radio@3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/radio@3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/form': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/radio': 3.11.2(react@19.2.1) - '@react-types/radio': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/form': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/radio': 3.11.2(react@19.2.3) + '@react-types/radio': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/searchfield@3.8.9(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/searchfield@3.8.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/searchfield': 3.5.16(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/searchfield': 3.6.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/searchfield': 3.5.16(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/searchfield': 3.6.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/select@3.17.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/select@3.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/listbox': 3.15.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/menu': 3.19.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/select': 3.8.0(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/select': 3.11.0(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/form': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/listbox': 3.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/menu': 3.19.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/select': 3.8.0(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/select': 3.11.0(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/selection@3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/selection@3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/separator@3.4.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/separator@3.4.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/slider@3.8.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/slider@3.8.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/slider': 3.7.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/slider': 3.8.2(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/slider': 3.7.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/slider': 3.8.2(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/spinbutton@3.6.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/spinbutton@3.6.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/ssr@3.9.10(react@19.2.1)': + '@react-aria/ssr@3.9.10(react@19.2.3)': dependencies: '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-aria/switch@3.7.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/switch@3.7.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/toggle': 3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/toggle': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/switch': 3.5.15(react@19.2.1) + '@react-aria/toggle': 3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/toggle': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/switch': 3.5.15(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/table@3.17.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/table@3.17.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/grid': 3.14.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/grid': 3.14.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) '@react-stately/flags': 3.1.2 - '@react-stately/table': 3.15.1(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/table': 3.13.4(react@19.2.1) + '@react-stately/table': 3.15.1(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/table': 3.13.4(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/tabs@3.10.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/tabs@3.10.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/tabs': 3.8.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/tabs': 3.3.19(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/tabs': 3.8.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/tabs': 3.3.19(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/tag@3.7.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/tag@3.7.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/gridlist': 3.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/gridlist': 3.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/textfield@3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/textfield@3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/textfield': 3.12.6(react@19.2.1) + '@react-aria/form': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/textfield': 3.12.6(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/toast@3.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/toast@3.0.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/landmark': 3.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/toast': 3.1.2(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/landmark': 3.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/toast': 3.1.2(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/toggle@3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/toggle@3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/toggle': 3.9.2(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/toggle': 3.9.2(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/toolbar@3.0.0-beta.21(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/toolbar@3.0.0-beta.21(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/tooltip@3.8.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/tooltip@3.8.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/tooltip': 3.5.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/tooltip': 3.4.21(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/tooltip': 3.5.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/tooltip': 3.4.21(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/tree@3.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/tree@3.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/gridlist': 3.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/tree': 3.9.3(react@19.2.1) - '@react-types/button': 3.14.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/gridlist': 3.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/tree': 3.9.3(react@19.2.3) + '@react-types/button': 3.14.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/utils@3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/utils@3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.1) + '@react-aria/ssr': 3.9.10(react@19.2.3) '@react-stately/flags': 3.1.2 - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 clsx: 2.1.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/virtualizer@4.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/virtualizer@4.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/virtualizer': 4.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-aria/visually-hidden@3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-aria/visually-hidden@3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-spring/animated@10.0.3(react@19.2.1)': + '@react-spring/animated@10.0.3(react@19.2.3)': dependencies: - '@react-spring/shared': 10.0.3(react@19.2.1) + '@react-spring/shared': 10.0.3(react@19.2.3) '@react-spring/types': 10.0.3 - react: 19.2.1 + react: 19.2.3 - '@react-spring/animated@9.7.5(react@19.2.1)': + '@react-spring/animated@9.7.5(react@19.2.3)': dependencies: - '@react-spring/shared': 9.7.5(react@19.2.1) + '@react-spring/shared': 9.7.5(react@19.2.3) '@react-spring/types': 9.7.5 - react: 19.2.1 + react: 19.2.3 - '@react-spring/core@10.0.3(react@19.2.1)': + '@react-spring/core@10.0.3(react@19.2.3)': dependencies: - '@react-spring/animated': 10.0.3(react@19.2.1) - '@react-spring/shared': 10.0.3(react@19.2.1) + '@react-spring/animated': 10.0.3(react@19.2.3) + '@react-spring/shared': 10.0.3(react@19.2.3) '@react-spring/types': 10.0.3 - react: 19.2.1 + react: 19.2.3 - '@react-spring/core@9.7.5(react@19.2.1)': + '@react-spring/core@9.7.5(react@19.2.3)': dependencies: - '@react-spring/animated': 9.7.5(react@19.2.1) - '@react-spring/shared': 9.7.5(react@19.2.1) + '@react-spring/animated': 9.7.5(react@19.2.3) + '@react-spring/shared': 9.7.5(react@19.2.3) '@react-spring/types': 9.7.5 - react: 19.2.1 + react: 19.2.3 '@react-spring/rafz@10.0.3': {} '@react-spring/rafz@9.7.5': {} - '@react-spring/shared@10.0.3(react@19.2.1)': + '@react-spring/shared@10.0.3(react@19.2.3)': dependencies: '@react-spring/rafz': 10.0.3 '@react-spring/types': 10.0.3 - react: 19.2.1 + react: 19.2.3 - '@react-spring/shared@9.7.5(react@19.2.1)': + '@react-spring/shared@9.7.5(react@19.2.3)': dependencies: '@react-spring/rafz': 9.7.5 '@react-spring/types': 9.7.5 - react: 19.2.1 + react: 19.2.3 '@react-spring/types@10.0.3': {} '@react-spring/types@9.7.5': {} - '@react-spring/web@10.0.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-spring/web@10.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-spring/animated': 10.0.3(react@19.2.1) - '@react-spring/core': 10.0.3(react@19.2.1) - '@react-spring/shared': 10.0.3(react@19.2.1) + '@react-spring/animated': 10.0.3(react@19.2.3) + '@react-spring/core': 10.0.3(react@19.2.3) + '@react-spring/shared': 10.0.3(react@19.2.3) '@react-spring/types': 10.0.3 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-spring/web@9.7.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-spring/web@9.7.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-spring/animated': 9.7.5(react@19.2.1) - '@react-spring/core': 9.7.5(react@19.2.1) - '@react-spring/shared': 9.7.5(react@19.2.1) + '@react-spring/animated': 9.7.5(react@19.2.3) + '@react-spring/core': 9.7.5(react@19.2.3) + '@react-spring/shared': 9.7.5(react@19.2.3) '@react-spring/types': 9.7.5 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-stately/autocomplete@3.0.0-beta.3(react@19.2.1)': + '@react-stately/autocomplete@3.0.0-beta.3(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/calendar@3.9.0(react@19.2.1)': + '@react-stately/calendar@3.9.0(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/calendar': 3.8.0(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/calendar': 3.8.0(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/checkbox@3.7.2(react@19.2.1)': + '@react-stately/checkbox@3.7.2(react@19.2.3)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/collections@3.12.8(react@19.2.1)': + '@react-stately/collections@3.12.8(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/color@3.9.2(react@19.2.1)': + '@react-stately/color@3.9.2(react@19.2.3)': dependencies: '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/numberfield': 3.10.2(react@19.2.1) - '@react-stately/slider': 3.7.2(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/color': 3.1.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/numberfield': 3.10.2(react@19.2.3) + '@react-stately/slider': 3.7.2(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/color': 3.1.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/combobox@3.12.0(react@19.2.1)': + '@react-stately/combobox@3.12.0(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/combobox': 3.13.9(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/combobox': 3.13.9(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/data@3.14.1(react@19.2.1)': + '@react-stately/data@3.14.1(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/datepicker@3.15.2(react@19.2.1)': + '@react-stately/datepicker@3.15.2(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/datepicker': 3.13.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/datepicker': 3.13.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/disclosure@3.0.8(react@19.2.1)': + '@react-stately/disclosure@3.0.8(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/dnd@3.7.1(react@19.2.1)': + '@react-stately/dnd@3.7.1(react@19.2.3)': dependencies: - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 '@react-stately/flags@3.1.2': dependencies: '@swc/helpers': 0.5.17 - '@react-stately/form@3.2.2(react@19.2.1)': + '@react-stately/form@3.2.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/grid@3.11.6(react@19.2.1)': + '@react-stately/grid@3.11.6(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/layout@4.5.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-stately/layout@4.5.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/table': 3.15.1(react@19.2.1) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/table': 3.13.4(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/table': 3.15.1(react@19.2.3) + '@react-stately/virtualizer': 4.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/table': 3.13.4(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-stately/list@3.13.1(react@19.2.1)': + '@react-stately/list@3.13.1(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/menu@3.9.8(react@19.2.1)': + '@react-stately/menu@3.9.8(react@19.2.3)': dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-types/menu': 3.10.5(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-types/menu': 3.10.5(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/numberfield@3.10.2(react@19.2.1)': + '@react-stately/numberfield@3.10.2(react@19.2.3)': dependencies: '@internationalized/number': 3.6.5 - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/numberfield': 3.8.15(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/numberfield': 3.8.15(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/overlays@3.6.20(react@19.2.1)': + '@react-stately/overlays@3.6.20(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/overlays': 3.9.2(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/overlays': 3.9.2(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/radio@3.11.2(react@19.2.1)': + '@react-stately/radio@3.11.2(react@19.2.3)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/radio': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/radio': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/searchfield@3.5.16(react@19.2.1)': + '@react-stately/searchfield@3.5.16(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/searchfield': 3.6.6(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/searchfield': 3.6.6(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/select@3.8.0(react@19.2.1)': + '@react-stately/select@3.8.0(react@19.2.3)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/select': 3.11.0(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/select': 3.11.0(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/selection@3.20.6(react@19.2.1)': + '@react-stately/selection@3.20.6(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/slider@3.7.2(react@19.2.1)': + '@react-stately/slider@3.7.2(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/slider': 3.8.2(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/slider': 3.8.2(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/table@3.15.1(react@19.2.1)': + '@react-stately/table@3.15.1(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) '@react-stately/flags': 3.1.2 - '@react-stately/grid': 3.11.6(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/table': 3.13.4(react@19.2.1) + '@react-stately/grid': 3.11.6(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/table': 3.13.4(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/tabs@3.8.6(react@19.2.1)': + '@react-stately/tabs@3.8.6(react@19.2.3)': dependencies: - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/tabs': 3.3.19(react@19.2.1) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/tabs': 3.3.19(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/toast@3.1.2(react@19.2.1)': + '@react-stately/toast@3.1.2(react@19.2.3)': dependencies: '@swc/helpers': 0.5.17 - react: 19.2.1 - use-sync-external-store: 1.6.0(react@19.2.1) + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) - '@react-stately/toggle@3.9.2(react@19.2.1)': + '@react-stately/toggle@3.9.2(react@19.2.3)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/checkbox': 3.10.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/checkbox': 3.10.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/tooltip@3.5.8(react@19.2.1)': + '@react-stately/tooltip@3.5.8(react@19.2.3)': dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-types/tooltip': 3.4.21(react@19.2.1) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-types/tooltip': 3.4.21(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/tree@3.9.3(react@19.2.1)': + '@react-stately/tree@3.9.3(react@19.2.3)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/utils@3.10.8(react@19.2.1)': + '@react-stately/utils@3.10.8(react@19.2.3)': dependencies: '@swc/helpers': 0.5.17 - react: 19.2.1 + react: 19.2.3 - '@react-stately/virtualizer@4.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@react-stately/virtualizer@4.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) + '@react-types/shared': 3.32.1(react@19.2.3) '@swc/helpers': 0.5.17 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@react-types/autocomplete@3.0.0-alpha.35(react@19.2.1)': + '@react-types/autocomplete@3.0.0-alpha.35(react@19.2.3)': dependencies: - '@react-types/combobox': 3.13.9(react@19.2.1) - '@react-types/searchfield': 3.6.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/combobox': 3.13.9(react@19.2.3) + '@react-types/searchfield': 3.6.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/breadcrumbs@3.7.17(react@19.2.1)': + '@react-types/breadcrumbs@3.7.17(react@19.2.3)': dependencies: - '@react-types/link': 3.6.5(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/link': 3.6.5(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/button@3.14.1(react@19.2.1)': + '@react-types/button@3.14.1(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/calendar@3.8.0(react@19.2.1)': + '@react-types/calendar@3.8.0(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/checkbox@3.10.2(react@19.2.1)': + '@react-types/checkbox@3.10.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/color@3.1.2(react@19.2.1)': + '@react-types/color@3.1.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/slider': 3.8.2(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/slider': 3.8.2(react@19.2.3) + react: 19.2.3 - '@react-types/combobox@3.13.9(react@19.2.1)': + '@react-types/combobox@3.13.9(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/datepicker@3.13.2(react@19.2.1)': + '@react-types/datepicker@3.13.2(react@19.2.3)': dependencies: '@internationalized/date': 3.10.0 - '@react-types/calendar': 3.8.0(react@19.2.1) - '@react-types/overlays': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/calendar': 3.8.0(react@19.2.3) + '@react-types/overlays': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/dialog@3.5.22(react@19.2.1)': + '@react-types/dialog@3.5.22(react@19.2.3)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/overlays': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/form@3.7.16(react@19.2.1)': + '@react-types/form@3.7.16(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/grid@3.3.6(react@19.2.1)': + '@react-types/grid@3.3.6(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/link@3.6.5(react@19.2.1)': + '@react-types/link@3.6.5(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/listbox@3.7.4(react@19.2.1)': + '@react-types/listbox@3.7.4(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/menu@3.10.5(react@19.2.1)': + '@react-types/menu@3.10.5(react@19.2.3)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/overlays': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/meter@3.4.13(react@19.2.1)': + '@react-types/meter@3.4.13(react@19.2.3)': dependencies: - '@react-types/progress': 3.5.16(react@19.2.1) - react: 19.2.1 + '@react-types/progress': 3.5.16(react@19.2.3) + react: 19.2.3 - '@react-types/numberfield@3.8.15(react@19.2.1)': + '@react-types/numberfield@3.8.15(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/overlays@3.9.2(react@19.2.1)': + '@react-types/overlays@3.9.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/progress@3.5.16(react@19.2.1)': + '@react-types/progress@3.5.16(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/radio@3.9.2(react@19.2.1)': + '@react-types/radio@3.9.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/searchfield@3.6.6(react@19.2.1)': + '@react-types/searchfield@3.6.6(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/textfield': 3.12.6(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/textfield': 3.12.6(react@19.2.3) + react: 19.2.3 - '@react-types/select@3.11.0(react@19.2.1)': + '@react-types/select@3.11.0(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/shared@3.32.1(react@19.2.1)': + '@react-types/shared@3.32.1(react@19.2.3)': dependencies: - react: 19.2.1 + react: 19.2.3 - '@react-types/slider@3.8.2(react@19.2.1)': + '@react-types/slider@3.8.2(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/switch@3.5.15(react@19.2.1)': + '@react-types/switch@3.5.15(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/table@3.13.4(react@19.2.1)': + '@react-types/table@3.13.4(react@19.2.3)': dependencies: - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/tabs@3.3.19(react@19.2.1)': + '@react-types/tabs@3.3.19(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/textfield@3.12.6(react@19.2.1)': + '@react-types/textfield@3.12.6(react@19.2.3)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - '@react-types/tooltip@3.4.21(react@19.2.1)': + '@react-types/tooltip@3.4.21(react@19.2.3)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-types/overlays': 3.9.2(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 '@redis/bloom@1.2.0(@redis/client@1.6.1)': dependencies: @@ -9983,10 +9981,10 @@ snapshots: '@tanstack/query-core@5.90.11': {} - '@tanstack/react-query@5.90.11(react@19.2.1)': + '@tanstack/react-query@5.90.11(react@19.2.3)': dependencies: '@tanstack/query-core': 5.90.11 - react: 19.2.1 + react: 19.2.3 '@trysound/sax@0.2.0': {} @@ -10117,24 +10115,24 @@ snapshots: '@types/node': 24.10.1 optional: true - '@umami/react-zen@0.211.0(@babel/core@7.28.3)(@types/react@19.2.7)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.1))': + '@umami/react-zen@0.211.0(@babel/core@7.28.3)(@types/react@19.2.7)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.3))': dependencies: '@fontsource/jetbrains-mono': 5.2.8 '@internationalized/date': 3.10.0 - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-spring/web': 9.7.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-spring/web': 9.7.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) classnames: 2.5.1 glob: 13.0.0 highlight.js: 11.11.1 - lucide-react: 0.555.0(react@19.2.1) - next: 16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-aria-components: 1.13.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-dom: 19.2.1(react@19.2.1) - react-hook-form: 7.67.0(react@19.2.1) - react-icons: 5.5.0(react@19.2.1) + lucide-react: 0.555.0(react@19.2.3) + next: 16.0.10(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-aria-components: 1.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) + react-hook-form: 7.67.0(react@19.2.3) + react-icons: 5.5.0(react@19.2.3) thenby: 1.3.4 - zustand: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) + zustand: 5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) transitivePeerDependencies: - '@babel/core' - '@opentelemetry/api' @@ -12696,13 +12694,13 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@0.543.0(react@19.2.1): + lucide-react@0.543.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - lucide-react@0.555.0(react@19.2.1): + lucide-react@0.555.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 magic-string@0.30.17: dependencies: @@ -12875,15 +12873,15 @@ snapshots: neo-async@2.6.2: {} - next@15.5.7(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + next@15.5.9(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@next/env': 15.5.7 + '@next/env': 15.5.9 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001741 postcss: 8.4.31 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -12899,24 +12897,24 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.0.7(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + next@16.0.10(@babel/core@7.28.3)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@next/env': 16.0.7 + '@next/env': 16.0.10 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001759 postcss: 8.4.31 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.7 - '@next/swc-darwin-x64': 16.0.7 - '@next/swc-linux-arm64-gnu': 16.0.7 - '@next/swc-linux-arm64-musl': 16.0.7 - '@next/swc-linux-x64-gnu': 16.0.7 - '@next/swc-linux-x64-musl': 16.0.7 - '@next/swc-win32-arm64-msvc': 16.0.7 - '@next/swc-win32-x64-msvc': 16.0.7 + '@next/swc-darwin-arm64': 16.0.10 + '@next/swc-darwin-x64': 16.0.10 + '@next/swc-linux-arm64-gnu': 16.0.10 + '@next/swc-linux-arm64-musl': 16.0.10 + '@next/swc-linux-x64-gnu': 16.0.10 + '@next/swc-linux-x64-musl': 16.0.10 + '@next/swc-win32-arm64-msvc': 16.0.10 + '@next/swc-win32-x64-msvc': 16.0.10 babel-plugin-react-compiler: 19.1.0-rc.2 sharp: 0.34.5 transitivePeerDependencies: @@ -13711,106 +13709,106 @@ snapshots: defu: 6.1.4 destr: 2.0.5 - react-aria-components@1.13.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-aria-components@1.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@internationalized/date': 3.10.0 '@internationalized/string': 3.2.7 - '@react-aria/autocomplete': 3.0.0-rc.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/collections': 3.0.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/dnd': 3.11.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@react-aria/autocomplete': 3.0.0-rc.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/collections': 3.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/dnd': 3.11.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/virtualizer': 4.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.1) - '@react-stately/layout': 4.5.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/table': 3.15.1(react@19.2.1) - '@react-stately/utils': 3.10.8(react@19.2.1) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/form': 3.7.16(react@19.2.1) - '@react-types/grid': 3.3.6(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - '@react-types/table': 3.13.4(react@19.2.1) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/virtualizer': 4.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.3) + '@react-stately/layout': 4.5.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/table': 3.15.1(react@19.2.3) + '@react-stately/utils': 3.10.8(react@19.2.3) + '@react-stately/virtualizer': 4.4.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/form': 3.7.16(react@19.2.3) + '@react-types/grid': 3.3.6(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + '@react-types/table': 3.13.4(react@19.2.3) '@swc/helpers': 0.5.17 client-only: 0.0.1 - react: 19.2.1 - react-aria: 3.44.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react-dom: 19.2.1(react@19.2.1) - react-stately: 3.42.0(react@19.2.1) - use-sync-external-store: 1.6.0(react@19.2.1) + react: 19.2.3 + react-aria: 3.44.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) + react-stately: 3.42.0(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) - react-aria@3.44.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-aria@3.44.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@internationalized/string': 3.2.7 - '@react-aria/breadcrumbs': 3.5.29(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/button': 3.14.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/calendar': 3.9.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/checkbox': 3.16.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/color': 3.1.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/combobox': 3.14.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/datepicker': 3.15.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/dialog': 3.5.31(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/disclosure': 3.1.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/dnd': 3.11.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/focus': 3.21.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/gridlist': 3.14.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/i18n': 3.12.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/interactions': 3.25.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/label': 3.7.22(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/landmark': 3.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/link': 3.8.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/listbox': 3.15.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/menu': 3.19.3(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/meter': 3.4.27(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/numberfield': 3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/overlays': 3.30.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/progress': 3.4.27(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/radio': 3.12.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/searchfield': 3.8.9(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/select': 3.17.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/selection': 3.26.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/separator': 3.4.13(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/slider': 3.8.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/ssr': 3.9.10(react@19.2.1) - '@react-aria/switch': 3.7.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/table': 3.17.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/tabs': 3.10.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/tag': 3.7.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/textfield': 3.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/toast': 3.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/tooltip': 3.8.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/tree': 3.1.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/utils': 3.31.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + '@react-aria/breadcrumbs': 3.5.29(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/button': 3.14.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/calendar': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/checkbox': 3.16.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/color': 3.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/combobox': 3.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/datepicker': 3.15.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/dialog': 3.5.31(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/disclosure': 3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/dnd': 3.11.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/focus': 3.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/gridlist': 3.14.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/i18n': 3.12.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/interactions': 3.25.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/label': 3.7.22(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/landmark': 3.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/link': 3.8.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/listbox': 3.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/menu': 3.19.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/meter': 3.4.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/numberfield': 3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/overlays': 3.30.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/progress': 3.4.27(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/radio': 3.12.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/searchfield': 3.8.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/select': 3.17.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/selection': 3.26.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/separator': 3.4.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/slider': 3.8.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/ssr': 3.9.10(react@19.2.3) + '@react-aria/switch': 3.7.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/table': 3.17.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/tabs': 3.10.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/tag': 3.7.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/textfield': 3.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/toast': 3.0.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/tooltip': 3.8.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/tree': 3.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/utils': 3.31.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - react-dom@19.2.1(react@19.2.1): + react-dom@19.2.3(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 scheduler: 0.27.0 - react-error-boundary@4.1.2(react@19.2.1): + react-error-boundary@4.1.2(react@19.2.3): dependencies: '@babel/runtime': 7.28.3 - react: 19.2.1 + react: 19.2.3 - react-hook-form@7.67.0(react@19.2.1): + react-hook-form@7.67.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - react-icons@5.5.0(react@19.2.1): + react-icons@5.5.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - react-intl@7.1.14(react@19.2.1)(typescript@5.9.3): + react-intl@7.1.14(react@19.2.3)(typescript@5.9.3): dependencies: '@formatjs/ecma402-abstract': 2.3.6 '@formatjs/icu-messageformat-parser': 2.11.4 @@ -13819,7 +13817,7 @@ snapshots: '@types/react': 19.2.7 hoist-non-react-statics: 3.3.2 intl-messageformat: 10.7.18 - react: 19.2.1 + react: 19.2.3 tslib: 2.8.1 optionalDependencies: typescript: 5.9.3 @@ -13828,69 +13826,69 @@ snapshots: react-is@18.3.1: {} - react-redux@9.2.0(@types/react@19.2.7)(react@19.2.1)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.2.1 - use-sync-external-store: 1.5.0(react@19.2.1) + react: 19.2.3 + use-sync-external-store: 1.5.0(react@19.2.3) optionalDependencies: '@types/react': 19.2.7 redux: 5.0.1 - react-simple-maps@2.3.0(prop-types@15.8.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-simple-maps@2.3.0(prop-types@15.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: d3-geo: 2.0.2 d3-selection: 2.0.0 d3-zoom: 2.0.0 prop-types: 15.8.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) topojson-client: 3.1.0 - react-stately@3.42.0(react@19.2.1): + react-stately@3.42.0(react@19.2.3): dependencies: - '@react-stately/calendar': 3.9.0(react@19.2.1) - '@react-stately/checkbox': 3.7.2(react@19.2.1) - '@react-stately/collections': 3.12.8(react@19.2.1) - '@react-stately/color': 3.9.2(react@19.2.1) - '@react-stately/combobox': 3.12.0(react@19.2.1) - '@react-stately/data': 3.14.1(react@19.2.1) - '@react-stately/datepicker': 3.15.2(react@19.2.1) - '@react-stately/disclosure': 3.0.8(react@19.2.1) - '@react-stately/dnd': 3.7.1(react@19.2.1) - '@react-stately/form': 3.2.2(react@19.2.1) - '@react-stately/list': 3.13.1(react@19.2.1) - '@react-stately/menu': 3.9.8(react@19.2.1) - '@react-stately/numberfield': 3.10.2(react@19.2.1) - '@react-stately/overlays': 3.6.20(react@19.2.1) - '@react-stately/radio': 3.11.2(react@19.2.1) - '@react-stately/searchfield': 3.5.16(react@19.2.1) - '@react-stately/select': 3.8.0(react@19.2.1) - '@react-stately/selection': 3.20.6(react@19.2.1) - '@react-stately/slider': 3.7.2(react@19.2.1) - '@react-stately/table': 3.15.1(react@19.2.1) - '@react-stately/tabs': 3.8.6(react@19.2.1) - '@react-stately/toast': 3.1.2(react@19.2.1) - '@react-stately/toggle': 3.9.2(react@19.2.1) - '@react-stately/tooltip': 3.5.8(react@19.2.1) - '@react-stately/tree': 3.9.3(react@19.2.1) - '@react-types/shared': 3.32.1(react@19.2.1) - react: 19.2.1 + '@react-stately/calendar': 3.9.0(react@19.2.3) + '@react-stately/checkbox': 3.7.2(react@19.2.3) + '@react-stately/collections': 3.12.8(react@19.2.3) + '@react-stately/color': 3.9.2(react@19.2.3) + '@react-stately/combobox': 3.12.0(react@19.2.3) + '@react-stately/data': 3.14.1(react@19.2.3) + '@react-stately/datepicker': 3.15.2(react@19.2.3) + '@react-stately/disclosure': 3.0.8(react@19.2.3) + '@react-stately/dnd': 3.7.1(react@19.2.3) + '@react-stately/form': 3.2.2(react@19.2.3) + '@react-stately/list': 3.13.1(react@19.2.3) + '@react-stately/menu': 3.9.8(react@19.2.3) + '@react-stately/numberfield': 3.10.2(react@19.2.3) + '@react-stately/overlays': 3.6.20(react@19.2.3) + '@react-stately/radio': 3.11.2(react@19.2.3) + '@react-stately/searchfield': 3.5.16(react@19.2.3) + '@react-stately/select': 3.8.0(react@19.2.3) + '@react-stately/selection': 3.20.6(react@19.2.3) + '@react-stately/slider': 3.7.2(react@19.2.3) + '@react-stately/table': 3.15.1(react@19.2.3) + '@react-stately/tabs': 3.8.6(react@19.2.3) + '@react-stately/toast': 3.1.2(react@19.2.3) + '@react-stately/toggle': 3.9.2(react@19.2.3) + '@react-stately/tooltip': 3.5.8(react@19.2.3) + '@react-stately/tree': 3.9.3(react@19.2.3) + '@react-types/shared': 3.32.1(react@19.2.3) + react: 19.2.3 - react-use-measure@2.1.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-use-measure@2.1.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 optionalDependencies: - react-dom: 19.2.1(react@19.2.1) + react-dom: 19.2.3(react@19.2.3) - react-window@1.8.11(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + react-window@1.8.11(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime': 7.28.3 memoize-one: 5.2.1 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - react@19.2.1: {} + react@19.2.3: {} read-babelrc-up@1.1.0: dependencies: @@ -14491,10 +14489,10 @@ snapshots: style-search@0.1.0: {} - styled-jsx@5.1.6(@babel/core@7.28.3)(react@19.2.1): + styled-jsx@5.1.6(@babel/core@7.28.3)(react@19.2.3): dependencies: client-only: 0.0.1 - react: 19.2.1 + react: 19.2.3 optionalDependencies: '@babel/core': 7.28.3 @@ -14894,17 +14892,17 @@ snapshots: dependencies: punycode: 2.3.1 - use-memo-one@1.1.3(react@19.2.1): + use-memo-one@1.1.3(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - use-sync-external-store@1.5.0(react@19.2.1): + use-sync-external-store@1.5.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 - use-sync-external-store@1.6.0(react@19.2.1): + use-sync-external-store@1.6.0(react@19.2.3): dependencies: - react: 19.2.1 + react: 19.2.3 util-deprecate@1.0.2: {} @@ -15094,9 +15092,9 @@ snapshots: zod@4.1.13: {} - zustand@5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)): + zustand@5.0.9(@types/react@19.2.7)(immer@10.2.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: '@types/react': 19.2.7 immer: 10.2.0 - react: 19.2.1 - use-sync-external-store: 1.6.0(react@19.2.1) + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) From 7bea47d9e8e3152edebbb72bf8643d3138767430 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Dec 2025 18:04:10 -0800 Subject: [PATCH 005/107] Bumped version v3.0.3. Updated workflow. --- .github/workflows/cd.yml | 21 ++++++++++++++++----- package.json | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a9509bce..f0cb2e87 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,13 +3,18 @@ name: Create docker images on: push: tags: - - 'v*.*.*' + - "v*.*.*" workflow_dispatch: inputs: version: - description: 'Optional image version (e.g. 3.0.0, v3.0.0, or 3.0.0-beta.1)' + description: "Optional image version (e.g. 3.0.0, v3.0.0, or 3.0.0-beta.1)" required: false - default: '' + default: "" + include_latest: + description: "Include latest tag" + required: false + type: boolean + default: true jobs: build: @@ -46,6 +51,7 @@ jobs: INPUT="${{ github.event.inputs.version }}" REF_TYPE="${{ github.ref_type }}" REF_NAME="${{ github.ref_name }}" + INCLUDE_LATEST="${{ github.event.inputs.include_latest }}" # Determine version source if [[ -n "$INPUT" ]]; then @@ -66,8 +72,13 @@ jobs: # prerelease: only version tag TAGS="$VERSION" else - # stable release: version + hierarchy + latest - TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},postgresql-latest,latest" + # stable release: version + hierarchy + TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},postgresql-latest" + + # Add latest tag based on trigger and input + if [[ "$REF_TYPE" == "tag" ]] || [[ "$INCLUDE_LATEST" == "true" ]]; then + TAGS="${TAGS},latest" + fi fi else # Non-tag build (e.g. from main branch) diff --git a/package.json b/package.json index 39ed6e86..76b1e1fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "3.0.2", + "version": "3.0.3", "description": "A modern, privacy-focused alternative to Google Analytics.", "author": "Umami Software, Inc. ", "license": "MIT", From 2a71cc721bf99874fa773e681ea14e9bf826da1e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Dec 2025 18:25:23 -0800 Subject: [PATCH 006/107] Fixed CI build. --- .github/workflows/ci.yml | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eee0a02d..e33a9680 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Node.js CI on: [push] env: - DATABASE_TYPE: postgresql + DATABASE_URL: "postgresql://user:pass@localhost:5432/dummy" SKIP_DB_CHECK: 1 jobs: @@ -11,27 +11,18 @@ jobs: if: github.repository == 'umami-software/umami' runs-on: ubuntu-latest - strategy: - matrix: - include: - - node-version: 18.18 - pnpm-version: 10 - db-type: postgresql - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 # required so that setup-node will work - with: - version: ${{ matrix.pnpm-version }} - run_install: false - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'pnpm' - env: - DATABASE_TYPE: ${{ matrix.db-type }} - - run: npm install --global pnpm - - run: pnpm install - - run: pnpm test - - run: pnpm build + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Use Node.js 18.18 + uses: actions/setup-node@v4 + with: + node-version: 18.18 + cache: "pnpm" + - run: npm install --global pnpm + - run: pnpm install + - run: pnpm test + - run: pnpm build From 9b0d1b092e4ca85ffdb70421a038feaec235b167 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Dec 2025 19:31:36 -0800 Subject: [PATCH 007/107] Updated worfkflow. --- .github/workflows/cd.yml | 44 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f0cb2e87..a6ba1197 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -27,6 +27,9 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -88,24 +91,25 @@ jobs: echo "tags=$TAGS" >> $GITHUB_OUTPUT echo "Computed tags: $TAGS" - - name: Build and push Docker image - run: | - TAGS="${{ steps.compute.outputs.tags }}" + - name: Build and push to GHCR + uses: mr-smithers-excellent/docker-build-push@v6 + with: + image: ${{ github.repository }} + tags: ${{ steps.compute.outputs.tags }} + registry: ghcr.io + multiPlatform: true + platform: linux/amd64,linux/arm64 + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - # Set image targets conditionally - if [[ "${{ github.repository }}" == "umami-software/umami" ]]; then - IMAGES=("umamisoftware/umami" "ghcr.io/${{ github.repository }}") - else - IMAGES=("ghcr.io/${{ github.repository }}") - fi - - for IMAGE in "${IMAGES[@]}"; do - echo "Building and pushing $IMAGE with tags: $TAGS" - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --push \ - $(echo "$TAGS" | tr ',' '\n' | sed "s|^|--tag ${IMAGE}:|") \ - --cache-from type=gha \ - --cache-to type=gha,mode=max \ - . - done + - name: Build and push to Docker Hub + if: github.repository == 'umami-software/umami' + uses: mr-smithers-excellent/docker-build-push@v6 + with: + image: umamisoftware/umami + tags: ${{ steps.compute.outputs.tags }} + registry: docker.io + multiPlatform: true + platform: linux/amd64,linux/arm64 + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} From 860e6390f14e7572b27d3ea1230258cff8c9bc96 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Dec 2025 20:16:56 -0800 Subject: [PATCH 008/107] Updated Docker build. --- .github/workflows/cd.yml | 60 ++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a6ba1197..24761faf 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -65,7 +65,8 @@ jobs: VERSION="" fi - TAGS="" + GHCR_TAGS="" + DOCKER_TAGS="" if [[ -n "$VERSION" ]]; then MAJOR=$(echo "$VERSION" | cut -d. -f1) @@ -73,43 +74,54 @@ jobs: if [[ "$VERSION" == *-* ]]; then # prerelease: only version tag - TAGS="$VERSION" + GHCR_TAGS="ghcr.io/${{ github.repository }}:$VERSION" + DOCKER_TAGS="umamisoftware/umami:$VERSION" else # stable release: version + hierarchy - TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},postgresql-latest" - + GHCR_TAGS="ghcr.io/${{ github.repository }}:$VERSION" + GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:${MAJOR}.${MINOR}" + GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:${MAJOR}" + GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:postgresql-latest" + + DOCKER_TAGS="umamisoftware/umami:$VERSION" + DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:${MAJOR}.${MINOR}" + DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:${MAJOR}" + DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:postgresql-latest" + # Add latest tag based on trigger and input if [[ "$REF_TYPE" == "tag" ]] || [[ "$INCLUDE_LATEST" == "true" ]]; then - TAGS="${TAGS},latest" + GHCR_TAGS="$GHCR_TAGS,ghcr.io/${{ github.repository }}:latest" + DOCKER_TAGS="$DOCKER_TAGS,umamisoftware/umami:latest" fi fi else # Non-tag build (e.g. from main branch) - TAGS="${REF_NAME}" + GHCR_TAGS="ghcr.io/${{ github.repository }}:${REF_NAME}" + DOCKER_TAGS="umamisoftware/umami:${REF_NAME}" fi - echo "tags=$TAGS" >> $GITHUB_OUTPUT - echo "Computed tags: $TAGS" + echo "ghcr_tags=$GHCR_TAGS" >> $GITHUB_OUTPUT + echo "docker_tags=$DOCKER_TAGS" >> $GITHUB_OUTPUT + echo "Computed GHCR tags: $GHCR_TAGS" + echo "Computed Docker Hub tags: $DOCKER_TAGS" - name: Build and push to GHCR - uses: mr-smithers-excellent/docker-build-push@v6 + uses: docker/build-push-action@v5 with: - image: ${{ github.repository }} - tags: ${{ steps.compute.outputs.tags }} - registry: ghcr.io - multiPlatform: true - platform: linux/amd64,linux/arm64 - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.compute.outputs.ghcr_tags }} + cache-from: type=gha + cache-to: type=gha,mode=max - name: Build and push to Docker Hub if: github.repository == 'umami-software/umami' - uses: mr-smithers-excellent/docker-build-push@v6 + uses: docker/build-push-action@v5 with: - image: umamisoftware/umami - tags: ${{ steps.compute.outputs.tags }} - registry: docker.io - multiPlatform: true - platform: linux/amd64,linux/arm64 - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.compute.outputs.docker_tags }} + cache-from: type=gha + cache-to: type=gha,mode=max From 5fbef149d04991e4686c95e2ffd9491be54e4d8e Mon Sep 17 00:00:00 2001 From: RaenonX Date: Wed, 10 Dec 2025 13:16:54 +0800 Subject: [PATCH 009/107] Added custom slug for links --- src/app/(main)/links/LinkEditForm.tsx | 65 +++++++++++++-------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx index 6c10c7f0..e9ad18f4 100644 --- a/src/app/(main)/links/LinkEditForm.tsx +++ b/src/app/(main)/links/LinkEditForm.tsx @@ -4,13 +4,14 @@ import { Form, FormField, FormSubmitButton, + Grid, Icon, Label, Loading, Row, TextField, } from '@umami/react-zen'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useConfig, useLinkQuery, useMessages } from '@/components/hooks'; import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery'; import { RefreshCw } from '@/components/icons'; @@ -42,7 +43,7 @@ export function LinkEditForm({ const { linksUrl } = useConfig(); const hostUrl = linksUrl || LINKS_URL; const { data, isLoading } = useLinkQuery(linkId); - const [slug, setSlug] = useState(generateId()); + const [defaultSlug] = useState(generateId()); const handleSubmit = async (data: any) => { await mutateAsync(data, { @@ -55,14 +56,6 @@ export function LinkEditForm({ }); }; - const handleSlug = () => { - const slug = generateId(); - - setSlug(slug); - - return slug; - }; - const checkUrl = (url: string) => { if (!isValidUrl(url)) { return formatMessage(labels.invalidUrl); @@ -70,19 +63,19 @@ export function LinkEditForm({ return true; }; - useEffect(() => { - if (data) { - setSlug(data.slug); - } - }, [data]); - if (linkId && isLoading) { return ; } return ( -
- {({ setValue }) => { + + {({ setValue, watch }) => { + const slug = watch('slug'); + return ( <> - - - + + + + + + @@ -121,14 +124,6 @@ export function LinkEditForm({ allowCopy style={{ width: '100%' }} /> - From 53dfc5e76ad2e3fe8fc103b62a96cdda38ad9aef Mon Sep 17 00:00:00 2001 From: RaenonX Date: Wed, 10 Dec 2025 13:18:11 +0800 Subject: [PATCH 010/107] Added `pm2.yml` in `.gitignore` --- .gitignore | 91 +++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 753389d1..de893d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,46 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -node_modules -.pnp -.pnp.js -.pnpm-store -package-lock.json - -# testing -/coverage - -# next.js -/.next -/out - -# production -/build -/public/script.js -/geo -/dist -/generated -/src/generated - -# misc -.DS_Store -.idea -.yarn -*.iml -*.log -.vscode -.tool-versions - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env.* -*.env.* - -*.dev.yml - +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js +.pnpm-store +package-lock.json + +# testing +/coverage + +# next.js +/.next +/out + +# production +/build +/public/script.js +/geo +/dist +/generated +/src/generated +pm2.yml + +# misc +.DS_Store +.idea +.yarn +*.iml +*.log +.vscode +.tool-versions + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.* +*.env.* + +*.dev.yml + From 886544f29752133424acfda2008c2941753f6cf1 Mon Sep 17 00:00:00 2001 From: Jahidul Islam Date: Sat, 13 Dec 2025 16:14:27 +0400 Subject: [PATCH 011/107] Correct UAE emirate names in iso-3166-2.json Updated names of UAE emirates for accuracy. --- public/iso-3166-2.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/public/iso-3166-2.json b/public/iso-3166-2.json index 347313d7..2b3b5a80 100644 --- a/public/iso-3166-2.json +++ b/public/iso-3166-2.json @@ -6,13 +6,13 @@ "AD-06": "Sant Julia de Loria", "AD-07": "Andorra la Vella", "AD-08": "Escaldes-Engordany", - "AE-AJ": "'Ajman", - "AE-AZ": "Abu Zaby", - "AE-DU": "Dubayy", - "AE-FU": "Al Fujayrah", - "AE-RK": "Ra's al Khaymah", - "AE-SH": "Ash Shariqah", - "AE-UQ": "Umm al Qaywayn", + "AE-AJ": "Ajman", + "AE-AZ": "Abu Dhabi", + "AE-DU": "Dubai", + "AE-FU": "Al Fujairah", + "AE-RK": "Ras al Khaimah", + "AE-SH": "Sharjah", + "AE-UQ": "Umm al Quwain", "AF-BAL": "Balkh", "AF-BAM": "Bamyan", "AF-BDG": "Badghis", From 437c168e6f28318c12c7823bd21e5823f3672449 Mon Sep 17 00:00:00 2001 From: journry789 Date: Mon, 15 Dec 2025 13:56:39 +0800 Subject: [PATCH 012/107] This resolves the issue of being unable to obtain the client's IP address due to the IPv6 format. --- src/lib/ip.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/src/lib/ip.ts b/src/lib/ip.ts index 5cd77574..a0e3a825 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -1,3 +1,5 @@ +import ipaddr from 'ipaddr.js'; + export const IP_ADDRESS_HEADERS = [ 'true-client-ip', // CDN 'cf-connecting-ip', // Cloudflare @@ -13,35 +15,87 @@ export const IP_ADDRESS_HEADERS = [ 'x-forwarded', ]; +/** + * Normalize IP strings to a canonical form: + * - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1) + * - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js) + */ +function normalizeIp(ip?: string | null) { + if (!ip) return ip; + + try { + const parsed = ipaddr.parse(ip); + + if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) { + return (parsed as ipaddr.IPv6).toIPv4Address().toString(); + } + + return parsed.toString(); + } catch { + // Fallback: return original if parsing fails + return ip; + } +} + +function resolveIp(ip?: string | null) { + if (!ip) return ip; + + // First, try as-is + const normalized = normalizeIp(ip); + try { + ipaddr.parse(normalized); + return normalized; + } catch { + // try stripping port (handles IPv4:port; leaves IPv6 intact) + const stripped = stripPort(ip); + if (stripped !== ip) { + const normalizedStripped = normalizeIp(stripped); + try { + ipaddr.parse(normalizedStripped); + return normalizedStripped; + } catch { + return normalizedStripped; + } + } + + return normalized; + } +} + export function getIpAddress(headers: Headers) { const customHeader = process.env.CLIENT_IP_HEADER; if (customHeader && headers.get(customHeader)) { - return headers.get(customHeader); + return resolveIp(headers.get(customHeader)); } - const header = IP_ADDRESS_HEADERS.find(name => { - return headers.get(name); - }); + const header = IP_ADDRESS_HEADERS.find(name => headers.get(name)); + if (!header) { + return undefined; + } const ip = headers.get(header); if (header === 'x-forwarded-for') { - return ip?.split(',')?.[0]?.trim(); + return resolveIp(ip?.split(',')?.[0]?.trim()); } if (header === 'forwarded') { const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); if (match) { - return match[1]; + return resolveIp(match[1]); } } - return ip; + return resolveIp(ip); } -export function stripPort(ip: string) { +export function stripPort(ip?: string | null) { + if (!ip) { + return ip; + } + if (ip.startsWith('[')) { const endBracket = ip.indexOf(']'); if (endBracket !== -1) { From 687318bd0996b7593a7e4fa63544a92146a33188 Mon Sep 17 00:00:00 2001 From: kkhys Date: Sat, 20 Dec 2025 11:58:25 +0900 Subject: [PATCH 013/107] fix Japanese translation for label 'breakdown' --- src/lang/ja-JP.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json index 7d2bf403..20b48f40 100644 --- a/src/lang/ja-JP.json +++ b/src/lang/ja-JP.json @@ -23,7 +23,7 @@ "label.behavior": "行動", "label.boards": "ボード", "label.bounce-rate": "直帰率", - "label.breakdown": "故障", + "label.breakdown": "内訳", "label.browser": "ブラウザ", "label.browsers": "ブラウザ", "label.campaigns": "キャンペーン", From ad264f941d056f700e7af40ab1a5d78996379d22 Mon Sep 17 00:00:00 2001 From: Mintihuang <43734212+Mintimate@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:35:28 +0000 Subject: [PATCH 014/107] =?UTF-8?q?feat:=20add=20EdgeOne=20headers=20for?= =?UTF-8?q?=20geolocation=20detection=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/detect.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 68cb6672..910d122b 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -28,6 +28,12 @@ const PROVIDER_HEADERS = [ regionHeader: 'cloudfront-viewer-country-region', cityHeader: 'cloudfront-viewer-city', }, + // EdgeOne headers (requires custom request headers in Rule Priorities, see: https://edgeone.ai/document/46151) + { + countryHeader: 'eo-ipcountry', + regionHeader: 'eo-region-code', + cityHeader: 'eo-ipcity', + }, ]; export function getDevice(userAgent: string, screen: string = '') { From b0ed4bddb67ed7989f86694d104079f4ce65ff6e Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 22 Dec 2025 10:59:33 -0800 Subject: [PATCH 015/107] change reset/delete website to interactive transaction with timeout Closes #3905 --- src/queries/prisma/website.ts | 93 ++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 79cb7247..6c8625d0 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -132,42 +132,46 @@ export async function updateWebsite( } export async function resetWebsite(websiteId: string) { - const { client, transaction } = prisma; + const { transaction } = prisma; const cloudMode = !!process.env.CLOUD_MODE; return transaction( - [ - client.revenue.deleteMany({ + async tx => { + await tx.revenue.deleteMany({ where: { websiteId }, - }), - client.eventData.deleteMany({ + }); + + await tx.eventData.deleteMany({ where: { websiteId }, - }), - client.sessionData.deleteMany({ + }); + + await tx.sessionData.deleteMany({ where: { websiteId }, - }), - client.websiteEvent.deleteMany({ + }); + + await tx.websiteEvent.deleteMany({ where: { websiteId }, - }), - client.session.deleteMany({ + }); + + await tx.session.deleteMany({ where: { websiteId }, - }), - client.website.update({ + }); + + const website = await tx.website.update({ where: { id: websiteId }, data: { resetAt: new Date(), }, - }), - ], + }); + + return website; + }, { timeout: 30000, }, ).then(async data => { if (cloudMode) { - await redis.client.set( - `website:${websiteId}`, - data.find(website => website.id), - ); + await redis.client.set(`website:${websiteId}`, data); } return data; @@ -175,43 +179,52 @@ export async function resetWebsite(websiteId: string) { } export async function deleteWebsite(websiteId: string) { - const { client, transaction } = prisma; + const { transaction } = prisma; const cloudMode = !!process.env.CLOUD_MODE; return transaction( - [ - client.revenue.deleteMany({ + async tx => { + await tx.revenue.deleteMany({ where: { websiteId }, - }), - client.eventData.deleteMany({ + }); + + await tx.eventData.deleteMany({ where: { websiteId }, - }), - client.sessionData.deleteMany({ + }); + + await tx.sessionData.deleteMany({ where: { websiteId }, - }), - client.websiteEvent.deleteMany({ + }); + + await tx.websiteEvent.deleteMany({ where: { websiteId }, - }), - client.session.deleteMany({ + }); + + await tx.session.deleteMany({ where: { websiteId }, - }), - client.report.deleteMany({ + }); + + await tx.report.deleteMany({ where: { websiteId }, - }), - client.segment.deleteMany({ + }); + + await tx.segment.deleteMany({ where: { websiteId }, - }), - cloudMode - ? client.website.update({ + }); + + const website = cloudMode + ? await tx.website.update({ data: { deletedAt: new Date(), }, where: { id: websiteId }, }) - : client.website.delete({ + : await tx.website.delete({ where: { id: websiteId }, - }), - ], + }); + + return website; + }, { timeout: 30000, }, From 6859f00bf643d1a4f58b46cfe8d32e7d673074f3 Mon Sep 17 00:00:00 2001 From: JiPai Date: Tue, 23 Dec 2025 22:39:33 +0800 Subject: [PATCH 016/107] chore(i18n): update zh-CN translation --- public/intl/messages/zh-CN.json | 210 ++++++++++++++++++++++++++++++-- src/lang/zh-CN.json | 49 ++++++-- 2 files changed, 241 insertions(+), 18 deletions(-) diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index b3d2f3c0..1c3f3240 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -5,6 +5,18 @@ "value": "访问代码" } ], + "label.account": [ + { + "type": 0, + "value": "账户" + } + ], + "label.action": [ + { + "type": 0, + "value": "行为" + } + ], "label.actions": [ { "type": 0, @@ -35,12 +47,24 @@ "value": "添加描述" } ], + "label.add-link": [ + { + "type": 0, + "value": "添加链接" + } + ], "label.add-member": [ { "type": 0, "value": "添加成员" } ], + "label.add-pixel": [ + { + "type": 0, + "value": "添加像素" + } + ], "label.add-step": [ { "type": 0, @@ -83,12 +107,24 @@ "value": "所有时间段" } ], + "label.analysis": [ + { + "type": 0, + "value": "分析" + } + ], "label.analytics": [ { "type": 0, "value": "分析" } ], + "label.application": [ + { + "type": 0, + "value": "应用" + } + ], "label.apply": [ { "type": 0, @@ -107,6 +143,12 @@ "value": "查看用户如何与您的营销互动,以及是什么促成了转化。" } ], + "label.audience": [ + { + "type": 0, + "value": "受众" + } + ], "label.average": [ { "type": 0, @@ -125,6 +167,12 @@ "value": "之前" } ], + "label.behavior": [ + { + "type": 0, + "value": "行为" + } + ], "label.boards": [ { "type": 0, @@ -173,12 +221,24 @@ "value": "修改密码" } ], + "label.channel": [ + { + "type": 0, + "value": "渠道" + } + ], "label.channels": [ { "type": 0, "value": "渠道" } ], + "label.chart": [ + { + "type": 0, + "value": "图表" + } + ], "label.cities": [ { "type": 0, @@ -203,6 +263,12 @@ "value": "队列" } ], + "label.cohorts": [ + { + "type": 0, + "value": "队列" + } + ], "label.compare": [ { "type": 0, @@ -317,6 +383,12 @@ "value": "创建者" } ], + "label.criteria": [ + { + "type": 0, + "value": "条件" + } + ], "label.currency": [ { "type": 0, @@ -419,6 +491,12 @@ "value": "台式机" } ], + "label.destination-url": [ + { + "type": 0, + "value": "目标URL" + } + ], "label.details": [ { "type": 0, @@ -455,6 +533,12 @@ "value": "唯一ID" } ], + "label.documentation": [ + { + "type": 0, + "value": "文档" + } + ], "label.does-not-contain": [ { "type": 0, @@ -479,6 +563,12 @@ "value": "域名" } ], + "label.download": [ + { + "type": 0, + "value": "下载" + } + ], "label.dropoff": [ { "type": 0, @@ -506,7 +596,7 @@ "label.email": [ { "type": 0, - "value": "Email" + "value": "邮箱" } ], "label.enable-share-url": [ @@ -527,6 +617,12 @@ "value": "入口 URL" } ], + "label.environment": [ + { + "type": 0, + "value": "环境" + } + ], "label.event": [ { "type": 0, @@ -671,6 +767,12 @@ "value": "分组" } ], + "label.growth": [ + { + "type": 0, + "value": "Growth" + } + ], "label.hostname": [ { "type": 0, @@ -701,6 +803,12 @@ "value": "通过使用筛选器和划分时间段来更深入地研究数据。" } ], + "label.invalid-url": [ + { + "type": 0, + "value": "无效URL" + } + ], "label.is": [ { "type": 0, @@ -863,12 +971,24 @@ "value": "少于等于" } ], + "label.link": [ + { + "type": 0, + "value": "链接" + } + ], "label.links": [ { "type": 0, "value": "链接" } ], + "label.location": [ + { + "type": 0, + "value": "位置" + } + ], "label.login": [ { "type": 0, @@ -1020,7 +1140,7 @@ "label.online": [ { "type": 0, - "value": "Online" + "value": "在线" } ], "label.organic-search": [ @@ -1165,6 +1285,12 @@ "value": "路径" } ], + "label.pixel": [ + { + "type": 0, + "value": "像素" + } + ], "label.pixels": [ { "type": 0, @@ -1185,6 +1311,12 @@ "value": " 提供支持" } ], + "label.preferences": [ + { + "type": 0, + "value": "偏好" + } + ], "label.previous": [ { "type": 0, @@ -1209,6 +1341,12 @@ "value": "个人资料" } ], + "label.profiles": [ + { + "type": 0, + "value": "个人资料" + } + ], "label.properties": [ { "type": 0, @@ -1248,7 +1386,7 @@ "label.referral": [ { "type": 0, - "value": "Referral" + "value": "来源" } ], "label.referrer": [ @@ -1371,6 +1509,24 @@ "value": "保存" } ], + "label.save-cohort": [ + { + "type": 0, + "value": "Save as cohort" + } + ], + "label.save-segment": [ + { + "type": 0, + "value": "Save as segment" + } + ], + "label.screen": [ + { + "type": 0, + "value": "屏幕" + } + ], "label.screens": [ { "type": 0, @@ -1383,6 +1539,18 @@ "value": "搜索" } ], + "label.segment": [ + { + "type": 0, + "value": "细分" + } + ], + "label.segments": [ + { + "type": 0, + "value": "细分" + } + ], "label.select": [ { "type": 0, @@ -1485,6 +1653,24 @@ "value": "总和" } ], + "label.support": [ + { + "type": 0, + "value": "支持" + } + ], + "label.switch-account": [ + { + "type": 0, + "value": "切换账户" + } + ], + "label.table": [ + { + "type": 0, + "value": "平板" + } + ], "label.tablet": [ { "type": 0, @@ -1635,6 +1821,12 @@ "value": "跟踪代码" } ], + "label.traffic": [ + { + "type": 0, + "value": "流量" + } + ], "label.transactions": [ { "type": 0, @@ -1846,7 +2038,7 @@ "message.bad-request": [ { "type": 0, - "value": "Bad request" + "value": "请求错误" } ], "message.collected-data": [ @@ -1946,7 +2138,7 @@ "message.forbidden": [ { "type": 0, - "value": "Forbidden" + "value": "禁止访问" } ], "message.go-to-settings": [ @@ -2046,13 +2238,13 @@ "message.not-found": [ { "type": 0, - "value": "Not found" + "value": "未找到" } ], "message.nothing-selected": [ { "type": 0, - "value": "Nothing selected." + "value": "未选择" } ], "message.page-not-found": [ @@ -2090,7 +2282,7 @@ "message.sever-error": [ { "type": 0, - "value": "Server error" + "value": "服务器错误" } ], "message.share-url": [ @@ -2158,7 +2350,7 @@ "message.unauthorized": [ { "type": 0, - "value": "Unauthorized" + "value": "未授权" } ], "message.user-deleted": [ diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json index c6f01dd5..8fc0ea67 100644 --- a/src/lang/zh-CN.json +++ b/src/lang/zh-CN.json @@ -1,11 +1,15 @@ { "label.access-code": "访问代码", + "label.account": "账户", + "label.action": "行为", "label.actions": "用户行为", "label.activity": "活动日志", "label.add": "添加", "label.add-board": "添加看板", "label.add-description": "添加描述", + "label.add-link": "添加链接", "label.add-member": "添加成员", + "label.add-pixel": "添加像素", "label.add-step": "添加步骤", "label.add-website": "添加网站", "label.admin": "管理员", @@ -13,10 +17,13 @@ "label.after": "之后", "label.all": "所有", "label.all-time": "所有时间段", + "label.analysis": "分析", "label.analytics": "分析", + "label.application": "应用", "label.apply": "应用", "label.attribution": "归因", "label.attribution-description": "查看用户如何与您的营销互动,以及是什么促成了转化。", + "label.audience": "受众", "label.average": "平均", "label.back": "返回", "label.before": "之前", @@ -29,11 +36,14 @@ "label.campaigns": "活动", "label.cancel": "取消", "label.change-password": "修改密码", + "label.channel": "渠道", "label.channels": "渠道", + "label.chart": "图表", "label.cities": "市/县", "label.city": "市/县", "label.clear-all": "清除全部", "label.cohort": "队列", + "label.cohorts": "队列", "label.compare": "比较", "label.compare-dates": "比较日期", "label.confirm": "确认", @@ -53,6 +63,7 @@ "label.create-user": "创建用户", "label.created": "已创建", "label.created-by": "创建者", + "label.criteria": "条件", "label.currency": "货币", "label.current": "当前", "label.current-password": "当前密码", @@ -70,24 +81,28 @@ "label.delete-website": "删除网站", "label.description": "描述", "label.desktop": "台式机", + "label.destination-url": "目标URL", "label.details": "详细信息", "label.device": "设备", "label.devices": "设备", "label.direct": "直接", "label.dismiss": "关闭", "label.distinct-id": "唯一ID", + "label.documentation": "文档", "label.does-not-contain": "不包含", "label.does-not-include": "不包括", "label.doest-not-exist": "不存在", "label.domain": "域名", + "label.download": "下载", "label.dropoff": "丢弃", "label.edit": "编辑", "label.edit-dashboard": "编辑仪表盘", "label.edit-member": "编辑成员", - "label.email": "Email", + "label.email": "邮箱", "label.enable-share-url": "启用共享链接", "label.end-step": "结束步骤", "label.entry": "入口 URL", + "label.environment": "环境", "label.event": "事件", "label.event-data": "事件数据", "label.event-name": "事件名称", @@ -112,11 +127,13 @@ "label.greater-than": "大于", "label.greater-than-equals": "大于或等于", "label.grouped": "分组", + "label.growth": "Growth", "label.hostname": "主机名", "label.includes": "包括", "label.insight": "洞察", "label.insights": "见解", "label.insights-description": "通过使用筛选器和划分时间段来更深入地研究数据。", + "label.invalid-url": "无效URL", "label.is": "等于", "label.is-false": "否", "label.is-not": "不等于", @@ -140,7 +157,9 @@ "label.leave-team": "离开团队", "label.less-than": "少于", "label.less-than-equals": "少于等于", + "label.link": "链接", "label.links": "链接", + "label.location": "位置", "label.login": "登录", "label.logout": "退出", "label.manage": "管理", @@ -161,7 +180,7 @@ "label.none": "无", "label.number-of-records": "{x} {x, plural, one {record} other {records}}", "label.ok": "好的", - "label.online": "Online", + "label.online": "在线", "label.organic-search": "自然搜索", "label.organic-shopping": "自然购物", "label.organic-social": "自然社交", @@ -183,19 +202,22 @@ "label.password": "密码", "label.path": "路径", "label.paths": "路径", + "label.pixel": "像素", "label.pixels": "像素", "label.powered-by": "由 {name} 提供支持", + "label.preferences": "偏好", "label.previous": "先前", "label.previous-period": "上一时期", "label.previous-year": "上一年", "label.profile": "个人资料", + "label.profiles": "个人资料", "label.properties": "属性", "label.property": "属性", "label.queries": "查询", "label.query": "查询", "label.query-parameters": "查询参数", "label.realtime": "实时", - "label.referral": "Referral", + "label.referral": "来源", "label.referrer": "来源", "label.referrers": "来源域名", "label.refresh": "刷新", @@ -216,8 +238,13 @@ "label.role": "角色", "label.run-query": "查询", "label.save": "保存", + "label.save-cohort": "Save as cohort", + "label.save-segment": "Save as segment", + "label.screen": "屏幕", "label.screens": "屏幕尺寸", "label.search": "搜索", + "label.segment": "细分", + "label.segments": "细分", "label.select": "选择", "label.select-date": "选择日期", "label.select-filter": "选择筛选器", @@ -235,6 +262,9 @@ "label.start-step": "开始步骤", "label.steps": "步骤", "label.sum": "总和", + "label.support": "支持", + "label.switch-account": "切换账户", + "label.table": "平板", "label.tablet": "平板", "label.tag": "标签", "label.tags": "标签", @@ -260,6 +290,7 @@ "label.total": "总数", "label.total-records": "总记录数", "label.tracking-code": "跟踪代码", + "label.traffic": "流量", "label.transactions": "交易", "label.transfer": "转移", "label.transfer-website": "转移网站", @@ -292,7 +323,7 @@ "label.yesterday": "昨天", "message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。", "message.active-users": "当前在线 {x} 位访客", - "message.bad-request": "Bad request", + "message.bad-request": "请求错误", "message.collected-data": "已收集的数据", "message.confirm-delete": "你确定要删除 {target} 吗?", "message.confirm-leave": "你确定要离开 {target} 吗?", @@ -302,7 +333,7 @@ "message.delete-website-warning": "所有相关数据将会被删除。", "message.error": "发生错误。", "message.event-log": "{url} 上的 {event}", - "message.forbidden": "Forbidden", + "message.forbidden": "禁止访问", "message.go-to-settings": "去设置", "message.incorrect-username-password": "用户名或密码不正确。", "message.invalid-domain": "无效域名", @@ -316,13 +347,13 @@ "message.no-teams": "您尚未创建任何团队。", "message.no-users": "暂无用户。", "message.no-websites-configured": "你还没有设置任何网站。", - "message.not-found": "Not found", - "message.nothing-selected": "Nothing selected.", + "message.not-found": "未找到", + "message.nothing-selected": "未选择", "message.page-not-found": "页面未找到。", "message.reset-website": "如确定要重置该网站,请在下面输入 {confirmation} 以确认。", "message.reset-website-warning": "此网站的所有统计数据将被删除,但您的跟踪代码将保持不变。", "message.saved": "保存成功。", - "message.sever-error": "Server error", + "message.sever-error": "服务器错误", "message.share-url": "这是 {target} 的共享链接。", "message.team-already-member": "你已是该团队的成员。", "message.team-not-found": "未找到团队。", @@ -332,7 +363,7 @@ "message.transfer-user-website-to-team": "选择要转移此网站的团队。", "message.transfer-website": "将网站所有权转移到您的账户或其他团队。", "message.triggered-event": "触发事件", - "message.unauthorized": "Unauthorized", + "message.unauthorized": "未授权", "message.user-deleted": "用户已删除。", "message.viewed-page": "已浏览页面", "message.visitor-log": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 浏览器进行访问。" From 783098fadcfa32cbdb62bcfaf48fbe317ccbe0fc Mon Sep 17 00:00:00 2001 From: JiPai Date: Tue, 23 Dec 2025 23:04:41 +0800 Subject: [PATCH 017/107] chore(i18n): update zh-CN translation --- public/intl/messages/zh-CN.json | 8 ++++---- src/lang/zh-CN.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index 1c3f3240..a4ad51fa 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -770,7 +770,7 @@ "label.growth": [ { "type": 0, - "value": "Growth" + "value": "增长" } ], "label.hostname": [ @@ -1512,13 +1512,13 @@ "label.save-cohort": [ { "type": 0, - "value": "Save as cohort" + "value": "保存为群组" } ], "label.save-segment": [ { "type": 0, - "value": "Save as segment" + "value": "保存为细分" } ], "label.screen": [ @@ -1668,7 +1668,7 @@ "label.table": [ { "type": 0, - "value": "平板" + "value": "表格" } ], "label.tablet": [ diff --git a/src/lang/zh-CN.json b/src/lang/zh-CN.json index 8fc0ea67..5490df42 100644 --- a/src/lang/zh-CN.json +++ b/src/lang/zh-CN.json @@ -127,7 +127,7 @@ "label.greater-than": "大于", "label.greater-than-equals": "大于或等于", "label.grouped": "分组", - "label.growth": "Growth", + "label.growth": "增长", "label.hostname": "主机名", "label.includes": "包括", "label.insight": "洞察", @@ -238,8 +238,8 @@ "label.role": "角色", "label.run-query": "查询", "label.save": "保存", - "label.save-cohort": "Save as cohort", - "label.save-segment": "Save as segment", + "label.save-cohort": "保存为群组", + "label.save-segment": "保存为细分", "label.screen": "屏幕", "label.screens": "屏幕尺寸", "label.search": "搜索", @@ -264,7 +264,7 @@ "label.sum": "总和", "label.support": "支持", "label.switch-account": "切换账户", - "label.table": "平板", + "label.table": "表格", "label.tablet": "平板", "label.tag": "标签", "label.tags": "标签", From 612b00179ba407c82494dda57dab540f95c63881 Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 25 Dec 2025 00:21:10 +0530 Subject: [PATCH 018/107] feat : Add version settings and API endpoint to display application version --- .../preferences/PreferenceSettings.tsx | 5 +++ .../settings/preferences/VersionSetting.tsx | 31 ++++++++++++++++ src/app/api/version/route.ts | 35 +++++++++++++++++++ src/components/messages.ts | 1 + 4 files changed, 72 insertions(+) create mode 100644 src/app/(main)/settings/preferences/VersionSetting.tsx create mode 100644 src/app/api/version/route.ts diff --git a/src/app/(main)/settings/preferences/PreferenceSettings.tsx b/src/app/(main)/settings/preferences/PreferenceSettings.tsx index a2890ce9..cc2d1b62 100644 --- a/src/app/(main)/settings/preferences/PreferenceSettings.tsx +++ b/src/app/(main)/settings/preferences/PreferenceSettings.tsx @@ -4,6 +4,7 @@ import { DateRangeSetting } from './DateRangeSetting'; import { LanguageSetting } from './LanguageSetting'; import { ThemeSetting } from './ThemeSetting'; import { TimezoneSetting } from './TimezoneSetting'; +import { VersionSetting } from './VersionSetting'; export function PreferenceSettings() { const { user } = useLoginQuery(); @@ -31,6 +32,10 @@ export function PreferenceSettings() { + + + + ); } diff --git a/src/app/(main)/settings/preferences/VersionSetting.tsx b/src/app/(main)/settings/preferences/VersionSetting.tsx new file mode 100644 index 00000000..2cfdbeb7 --- /dev/null +++ b/src/app/(main)/settings/preferences/VersionSetting.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { Text } from '@umami/react-zen'; +import { useEffect, useState } from 'react'; + +export function VersionSetting() { + const [version, setVersion] = useState(''); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchVersion = async () => { + try { + const response = await fetch('/api/version'); + const data = await response.json(); + setVersion(data.version || 'unknown'); + } catch (error) { + setVersion('unknown'); + } finally { + setLoading(false); + } + }; + + fetchVersion(); + }, []); + + if (loading) { + return Loading...; + } + + return {version}; +} diff --git a/src/app/api/version/route.ts b/src/app/api/version/route.ts new file mode 100644 index 00000000..af548f73 --- /dev/null +++ b/src/app/api/version/route.ts @@ -0,0 +1,35 @@ +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { parseRequest } from '@/lib/request'; +import { json } from '@/lib/response'; + +let cachedVersion: string | null = null; + +async function getVersion(): Promise { + if (cachedVersion) { + return cachedVersion; + } + + try { + const packageJsonPath = join(process.cwd(), 'package.json'); + const data = await readFile(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(data); + cachedVersion = packageJson.version || 'unknown'; + } catch (error) { + cachedVersion = 'unknown'; + } + + return cachedVersion; +} + +export async function GET(request: Request) { + const { error } = await parseRequest(request, null, { skipAuth: true }); + + if (error) { + return error(); + } + + const version = await getVersion(); + + return json({ version }); +} diff --git a/src/components/messages.ts b/src/components/messages.ts index 0438c06e..712495d8 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -351,6 +351,7 @@ export const labels = defineMessages({ growth: { id: 'label.growth', defaultMessage: 'Growth' }, account: { id: 'label.account', defaultMessage: 'Account' }, application: { id: 'label.application', defaultMessage: 'Application' }, + version: { id: 'label.version', defaultMessage: 'Version' }, saveSegment: { id: 'label.save-segment', defaultMessage: 'Save as segment' }, saveCohort: { id: 'label.save-cohort', defaultMessage: 'Save as cohort' }, analysis: { id: 'label.analysis', defaultMessage: 'Analysis' }, From 5e3e6b3edda495d80bb1e86ed3ccae949c07a469 Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 25 Dec 2025 09:48:09 +0530 Subject: [PATCH 019/107] refactor: Simplify version display by removing API endpoint and using constant --- .../settings/preferences/VersionSetting.tsx | 27 ++------------ src/app/api/version/route.ts | 35 ------------------- 2 files changed, 2 insertions(+), 60 deletions(-) delete mode 100644 src/app/api/version/route.ts diff --git a/src/app/(main)/settings/preferences/VersionSetting.tsx b/src/app/(main)/settings/preferences/VersionSetting.tsx index 2cfdbeb7..afca1de6 100644 --- a/src/app/(main)/settings/preferences/VersionSetting.tsx +++ b/src/app/(main)/settings/preferences/VersionSetting.tsx @@ -1,31 +1,8 @@ 'use client'; import { Text } from '@umami/react-zen'; -import { useEffect, useState } from 'react'; +import { CURRENT_VERSION } from '@/lib/constants'; export function VersionSetting() { - const [version, setVersion] = useState(''); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchVersion = async () => { - try { - const response = await fetch('/api/version'); - const data = await response.json(); - setVersion(data.version || 'unknown'); - } catch (error) { - setVersion('unknown'); - } finally { - setLoading(false); - } - }; - - fetchVersion(); - }, []); - - if (loading) { - return Loading...; - } - - return {version}; + return {CURRENT_VERSION}; } diff --git a/src/app/api/version/route.ts b/src/app/api/version/route.ts deleted file mode 100644 index af548f73..00000000 --- a/src/app/api/version/route.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import { parseRequest } from '@/lib/request'; -import { json } from '@/lib/response'; - -let cachedVersion: string | null = null; - -async function getVersion(): Promise { - if (cachedVersion) { - return cachedVersion; - } - - try { - const packageJsonPath = join(process.cwd(), 'package.json'); - const data = await readFile(packageJsonPath, 'utf-8'); - const packageJson = JSON.parse(data); - cachedVersion = packageJson.version || 'unknown'; - } catch (error) { - cachedVersion = 'unknown'; - } - - return cachedVersion; -} - -export async function GET(request: Request) { - const { error } = await parseRequest(request, null, { skipAuth: true }); - - if (error) { - return error(); - } - - const version = await getVersion(); - - return json({ version }); -} From 4eddac21c754d900c0db71a8f8c3745d0d3cb7db Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 25 Dec 2025 20:41:14 +0530 Subject: [PATCH 020/107] feat: Add default currency support and update currency handling in Revenue component --- next.config.ts | 2 ++ .../[websiteId]/(reports)/revenue/Revenue.tsx | 15 ++++++++++++--- src/lib/constants.ts | 2 ++ src/lib/format.ts | 4 +++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/next.config.ts b/next.config.ts index 99dcca0d..1a4e2e0e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -8,6 +8,7 @@ const cloudMode = process.env.CLOUD_MODE || ''; const cloudUrl = process.env.CLOUD_URL || ''; const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || ''; const corsMaxAge = process.env.CORS_MAX_AGE || ''; +const defaultCurrency = process.env.DEFAULT_CURRENCY || ''; const defaultLocale = process.env.DEFAULT_LOCALE || ''; const forceSSL = process.env.FORCE_SSL || ''; const frameAncestors = process.env.ALLOWED_FRAME_URLS || ''; @@ -170,6 +171,7 @@ export default { cloudMode, cloudUrl, currentVersion: pkg.version, + defaultCurrency, defaultLocale, }, basePath, diff --git a/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx b/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx index 0e782a16..faee8b9a 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx @@ -12,9 +12,10 @@ import { ListTable } from '@/components/metrics/ListTable'; import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricsBar } from '@/components/metrics/MetricsBar'; import { renderDateLabels } from '@/lib/charts'; -import { CHART_COLORS } from '@/lib/constants'; +import { CHART_COLORS, CURRENCY_CONFIG, DEFAULT_CURRENCY } from '@/lib/constants'; import { generateTimeSeries } from '@/lib/date'; import { formatLongCurrency, formatLongNumber } from '@/lib/format'; +import { getItem, setItem } from '@/lib/storage'; export interface RevenueProps { websiteId: string; @@ -24,7 +25,15 @@ export interface RevenueProps { } export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) { - const [currency, setCurrency] = useState('USD'); + const [currency, setCurrency] = useState( + getItem(CURRENCY_CONFIG) || process.env.defaultCurrency || DEFAULT_CURRENCY, + ); + + const handleCurrencyChange = (value: string) => { + setCurrency(value); + setItem(CURRENCY_CONFIG, value); + }; + const { formatMessage, labels } = useMessages(); const { locale, dateLocale } = useLocale(); const { countryNames } = useCountryNames(locale); @@ -107,7 +116,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) { return ( - + {data && ( diff --git a/src/lib/constants.ts b/src/lib/constants.ts index e5090c3c..502a3df6 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -4,6 +4,7 @@ export const LOCALE_CONFIG = 'umami.locale'; export const TIMEZONE_CONFIG = 'umami.timezone'; export const DATE_RANGE_CONFIG = 'umami.date-range'; export const THEME_CONFIG = 'umami.theme'; +export const CURRENCY_CONFIG = 'umami.currency'; export const DASHBOARD_CONFIG = 'umami.dashboard'; export const LAST_TEAM_CONFIG = 'umami.last-team'; export const VERSION_CHECK = 'umami.version-check'; @@ -25,6 +26,7 @@ export const DEFAULT_WEBSITE_LIMIT = 10; export const DEFAULT_RESET_DATE = '2000-01-01'; export const DEFAULT_PAGE_SIZE = 20; export const DEFAULT_DATE_COMPARE = 'prev'; +export const DEFAULT_CURRENCY = 'USD'; export const REALTIME_RANGE = 30; export const REALTIME_INTERVAL = 10000; diff --git a/src/lib/format.ts b/src/lib/format.ts index 52fd3048..035a1811 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -1,3 +1,5 @@ +import { DEFAULT_CURRENCY } from './constants'; + export function parseTime(val: number) { const days = ~~(val / 86400); const hours = ~~(val / 3600) - days * 24; @@ -94,7 +96,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US' // Fallback to default currency format if an error occurs formattedValue = new Intl.NumberFormat(locale, { style: 'currency', - currency: 'USD', + currency: DEFAULT_CURRENCY, }); } From 97c26bc0759caabdde17d0e35c829bb0b0e424d6 Mon Sep 17 00:00:00 2001 From: Dimitar Yanakiev Date: Fri, 26 Dec 2025 16:34:38 +0200 Subject: [PATCH 021/107] Translated various labels and messages in Bulgarian. --- src/lang/bg-BG.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lang/bg-BG.json b/src/lang/bg-BG.json index 4b0effc8..50099032 100644 --- a/src/lang/bg-BG.json +++ b/src/lang/bg-BG.json @@ -93,7 +93,7 @@ "label.event-name": "Име на събитие", "label.events": "Събития", "label.exists": "Съществува", - "label.exit": "Exit URL", + "label.exit": "URL за изход", "label.false": "Грешно", "label.field": "Поле", "label.fields": "Полета", @@ -135,7 +135,7 @@ "label.last-days": "Последните {x} дни", "label.last-hours": "Последните {x} часа", "label.last-months": "Последните {x} месеца", - "label.last-seen": "Last seen", + "label.last-seen": "Последно видяно", "label.leave": "Напусни", "label.leave-team": "Напусни екип", "label.less-than": "По-малко от", @@ -161,7 +161,7 @@ "label.none": "Няма", "label.number-of-records": "{x} {x, plural, one {един} other {други}}", "label.ok": "Добре", - "label.online": "Online", + "label.online": "Онлайн", "label.organic-search": "Органично търсене", "label.organic-shopping": "Органично пазаруване", "label.organic-social": "Органични социални мрежи", @@ -185,9 +185,9 @@ "label.paths": "Пътища", "label.pixels": "Пиксели", "label.powered-by": "Поддържано от {name}", - "label.previous": "Previous", - "label.previous-period": "Previous period", - "label.previous-year": "Previous year", + "label.previous": "Предишен", + "label.previous-period": "Предишен период", + "label.previous-year": "Предишна година", "label.profile": "Профил", "label.properties": "Свойства", "label.property": "Свойство", @@ -211,8 +211,8 @@ "label.reset-website": "Нулирай уебсайт", "label.retention": "Привързване", "label.retention-description": "Измерете привързаността към вашия уебсайт, като проследявате колко често потребителите се връщат.", - "label.revenue": "Revenue", - "label.revenue-description": "Look into your revenue across time.", + "label.revenue": "Приходи", + "label.revenue-description": "Прегледайте приходите си във времето.", "label.role": "Роля", "label.run-query": "Изпълни запитване", "label.save": "Запази", @@ -260,14 +260,14 @@ "label.total": "Общо", "label.total-records": "Общо записи", "label.tracking-code": "Код за проследяване", - "label.transactions": "Transactions", + "label.transactions": "Транзакции", "label.transfer": "Прехвърли", "label.transfer-website": "Прехвърляне на уебсайт", "label.true": "Вярно", "label.type": "Вид", "label.unique": "Уникален", "label.unique-visitors": "Уникални посетители", - "label.uniqueCustomers": "Unique Customers", + "label.uniqueCustomers": "Уникални клиенти", "label.unknown": "Неизвестен", "label.untitled": "Без заглавие", "label.update": "Актуализирай", @@ -282,7 +282,7 @@ "label.view-only": "Само за преглед", "label.views": "Прегледи", "label.views-per-visit": "Прегледи на посещение", - "label.visit-duration": "Visit duration", + "label.visit-duration": "Продължителност на посещение", "label.visitors": "Посетители", "label.visits": "Посещения", "label.website": "Уебсайт", @@ -292,8 +292,8 @@ "label.yesterday": "Вчера", "message.action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.", "message.active-users": "{x} {x, plural, one {активен един} other {активни други}}", - "message.bad-request": "Bad request", - "message.collected-data": "Collected data", + "message.bad-request": "Невалидна заявка", + "message.collected-data": "Събрани данни", "message.confirm-delete": "Сигурни ли сте, че искате да изтриете {target}?", "message.confirm-leave": "Сигурни ли сте, че искате да напуснете {target}?", "message.confirm-remove": "Сигурни ли сте, че искате да премахнете {target}?", @@ -302,7 +302,7 @@ "message.delete-website-warning": "Всички данни за уебсайта ще бъдат изтрити.", "message.error": "Възникна грешка.", "message.event-log": "{event} на {url}", - "message.forbidden": "Forbidden", + "message.forbidden": "Забранено", "message.go-to-settings": "Отидете в настройките", "message.incorrect-username-password": "Неправилно потребителско име и/или парола.", "message.invalid-domain": "Невалиден домейн. Не включвайте http/https.", @@ -316,13 +316,13 @@ "message.no-teams": "Няма създадени екипи.", "message.no-users": "Няма потребители.", "message.no-websites-configured": "Нямате конфигурирани уебсайтове.", - "message.not-found": "Not found", - "message.nothing-selected": "Nothing selected.", + "message.not-found": "Не е намерено", + "message.nothing-selected": "Няма избрано.", "message.page-not-found": "Страницата не е намерена", "message.reset-website": "За да нулирате този уебсайт, въведете {confirmation} в полето по-долу, за да потвърдите.", "message.reset-website-warning": "Всички статистически данни за този уебсайт ще бъдат изтрити, но вашите настройки ще останат непроменени.", "message.saved": "Запазено.", - "message.sever-error": "Server error", + "message.sever-error": "Сървърна грешка", "message.share-url": "Статистиката за вашия уебсайт е публично достъпна на следния URL адрес:", "message.team-already-member": "Вече сте член на екипа.", "message.team-not-found": "Екипът не е намерен.", @@ -332,7 +332,7 @@ "message.transfer-user-website-to-team": "Изберете екипът на който да бъде прехвърлен уебсайта.", "message.transfer-website": "Прехвърли собствеността на уебсайта към вашия акаунт или към друг екип.", "message.triggered-event": "Активирано събитие", - "message.unauthorized": "Unauthorized", + "message.unauthorized": "Неоторизиран достъп", "message.user-deleted": "Потребителят е изтрит.", "message.viewed-page": "Страницата е видяна", "message.visitor-log": "Посетител от {country}, използващ {browser} на {os} {device}" From b0aa6fd6efa55fbb5402d3a171ff3e1fcf251b41 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 26 Dec 2025 22:13:23 +0530 Subject: [PATCH 022/107] feat: Add current version to API response --- src/app/api/config/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/config/route.ts b/src/app/api/config/route.ts index 4e40caa4..101a1224 100644 --- a/src/app/api/config/route.ts +++ b/src/app/api/config/route.ts @@ -17,5 +17,6 @@ export async function GET(request: Request) { telemetryDisabled: !!process.env.DISABLE_TELEMETRY, trackerScriptName: process.env.TRACKER_SCRIPT_NAME, updatesDisabled: !!process.env.DISABLE_UPDATES, + currentVersion: !!process.env.currentVersion, }); } From 8286af1453f6eaa159b5183a48ccbc572589b532 Mon Sep 17 00:00:00 2001 From: SBOZH Date: Thu, 1 Jan 2026 15:53:20 +0100 Subject: [PATCH 023/107] Fixed PostgreSQL 12/13 syntax error in Journeys feature --- src/queries/sql/reports/getJourney.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queries/sql/reports/getJourney.ts b/src/queries/sql/reports/getJourney.ts index 283e0fad..21a7f22d 100644 --- a/src/queries/sql/reports/getJourney.ts +++ b/src/queries/sql/reports/getJourney.ts @@ -119,7 +119,7 @@ async function relationalQuery( select distinct website_event.visit_id, website_event.referrer_path, - coalesce(nullIf(website_event.event_name, ''), website_event.url_path) event, + coalesce(nullIf(website_event.event_name, ''), website_event.url_path) "event", row_number() OVER (PARTITION BY visit_id ORDER BY website_event.created_at) AS event_number from website_event ${cohortQuery} From dacf13475ad152fce7ca2d03c56a7b477a1d0313 Mon Sep 17 00:00:00 2001 From: AymanAlSuleihi Date: Thu, 1 Jan 2026 15:01:04 +0000 Subject: [PATCH 024/107] Add country image t1.png for Tor --- public/images/country/t1.png | Bin 0 -> 1117 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/country/t1.png diff --git a/public/images/country/t1.png b/public/images/country/t1.png new file mode 100644 index 0000000000000000000000000000000000000000..c45ed0fe9f57d60463fe6755b7a44fec9d9c5b74 GIT binary patch literal 1117 zcmV-j1fu(iP)!MXa?E&`KeKCO|+4Dw-ILT#7MD3<5V&WhLuvuP-}$ zXKzJH$xv;myQs&r_JT;6w#?0oNp4=0C@2KOfs__f&swJQWum~?zwZ1)&d{tf6u52t zi6ugjTb-;d4+KCV074;DC9!)Yo+ALzqgo%HSp7!(v^^sNo0VoYF=5E;NQ^~TRX{g=b65I0%NC^apCY4 z+S_`VxpE@u-a)#~CnJiZUQnO9|D`Qf&aM$cj0*Im!?n{%F1Bv{v;a@PJq<+{TsnG%qIiT*nZ}K)eFQXxaD_%Ln=RHn96UwB@Pip9 ziNwXhpGA| Date: Sat, 3 Jan 2026 00:48:09 +0000 Subject: [PATCH 025/107] Fix double scrollbar in dropdown --- src/components/input/WebsiteSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 8d81eb9a..04c773a7 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -65,7 +65,7 @@ export function WebsiteSelect({ renderValue={renderValue} listProps={{ renderEmptyState: () => , - style: { maxHeight: '400px' }, + style: { maxHeight: 'calc(42vh - 65px)' }, }} > {({ id, name }: any) => {name}} From fbe031bfe990c2aefeebc85f0f90f7c54d13b49c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 7 Jan 2026 09:30:59 -0800 Subject: [PATCH 026/107] update pixel/link edit form on save. --- src/app/(main)/links/LinkDeleteButton.tsx | 5 +++-- src/app/(main)/links/LinkEditForm.tsx | 1 + src/app/(main)/pixels/PixelEditForm.tsx | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/links/LinkDeleteButton.tsx b/src/app/(main)/links/LinkDeleteButton.tsx index 78f85f89..32ccbaf7 100644 --- a/src/app/(main)/links/LinkDeleteButton.tsx +++ b/src/app/(main)/links/LinkDeleteButton.tsx @@ -1,5 +1,5 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm'; -import { useDeleteQuery, useMessages } from '@/components/hooks'; +import { useDeleteQuery, useMessages, useModified } from '@/components/hooks'; import { Trash } from '@/components/icons'; import { DialogButton } from '@/components/input/DialogButton'; import { messages } from '@/components/messages'; @@ -15,7 +15,8 @@ export function LinkDeleteButton({ onSave?: () => void; }) { const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages(); - const { mutateAsync, isPending, error, touch } = useDeleteQuery(`/links/${linkId}`); + const { mutateAsync, isPending, error } = useDeleteQuery(`/links/${linkId}`); + const { touch } = useModified(); const handleConfirm = async (close: () => void) => { await mutateAsync(null, { diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx index e9ad18f4..a6c0164d 100644 --- a/src/app/(main)/links/LinkEditForm.tsx +++ b/src/app/(main)/links/LinkEditForm.tsx @@ -50,6 +50,7 @@ export function LinkEditForm({ onSuccess: async () => { toast(formatMessage(messages.saved)); touch('links'); + touch(`link:${linkId}`); onSave?.(); onClose?.(); }, diff --git a/src/app/(main)/pixels/PixelEditForm.tsx b/src/app/(main)/pixels/PixelEditForm.tsx index aedd3a3b..46241c1c 100644 --- a/src/app/(main)/pixels/PixelEditForm.tsx +++ b/src/app/(main)/pixels/PixelEditForm.tsx @@ -48,6 +48,7 @@ export function PixelEditForm({ onSuccess: async () => { toast(formatMessage(messages.saved)); touch('pixels'); + touch(`pixel:${pixelId}`); onSave?.(); onClose?.(); }, From fbf03d65636c5368e5daa4844b81abed4671830d Mon Sep 17 00:00:00 2001 From: AymanAlSuleihi Date: Fri, 9 Jan 2026 19:25:07 +0000 Subject: [PATCH 027/107] Fix metrics bar not updating on compare mode switch --- src/app/api/websites/[websiteId]/stats/route.ts | 7 ++++++- src/components/hooks/queries/useWebsiteStatsQuery.ts | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 07c8b969..09d691f1 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -12,6 +12,7 @@ export async function GET( ) { const schema = z.object({ ...dateRangeParams, + compare: z.enum(['prev', 'yoy']).optional(), ...filterParams, }); @@ -31,7 +32,11 @@ export async function GET( const data = await getWebsiteStats(websiteId, filters); - const { startDate, endDate } = getCompareDate('prev', filters.startDate, filters.endDate); + const { startDate, endDate } = getCompareDate( + filters.compare || 'prev', + filters.startDate, + filters.endDate, + ); const comparison = await getWebsiteStats(websiteId, { ...filters, diff --git a/src/components/hooks/queries/useWebsiteStatsQuery.ts b/src/components/hooks/queries/useWebsiteStatsQuery.ts index e9a0c48c..69bae09f 100644 --- a/src/components/hooks/queries/useWebsiteStatsQuery.ts +++ b/src/components/hooks/queries/useWebsiteStatsQuery.ts @@ -1,5 +1,6 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useDateParameters } from '@/components/hooks/useDateParameters'; +import { useDateRange } from '@/components/hooks/useDateRange'; import { useApi } from '../useApi'; import { useFilterParameters } from '../useFilterParameters'; @@ -24,12 +25,16 @@ export function useWebsiteStatsQuery( ) { const { get, useQuery } = useApi(); const { startAt, endAt, unit, timezone } = useDateParameters(); + const { compare } = useDateRange(); const filters = useFilterParameters(); return useQuery({ - queryKey: ['websites:stats', { websiteId, startAt, endAt, unit, timezone, ...filters }], + queryKey: [ + 'websites:stats', + { websiteId, startAt, endAt, unit, timezone, compare, ...filters }, + ], queryFn: () => - get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, ...filters }), + get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, compare, ...filters }), enabled: !!websiteId, ...options, }); From 6420f2c81338705d1ad83295fb5b53dcfb8aa8b1 Mon Sep 17 00:00:00 2001 From: AymanAlSuleihi Date: Fri, 9 Jan 2026 22:54:50 +0000 Subject: [PATCH 028/107] Remove redundant compare param definition --- src/app/api/websites/[websiteId]/stats/route.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 09d691f1..12492537 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -12,7 +12,6 @@ export async function GET( ) { const schema = z.object({ ...dateRangeParams, - compare: z.enum(['prev', 'yoy']).optional(), ...filterParams, }); @@ -32,11 +31,9 @@ export async function GET( const data = await getWebsiteStats(websiteId, filters); - const { startDate, endDate } = getCompareDate( - filters.compare || 'prev', - filters.startDate, - filters.endDate, - ); + const compare = filters.compare === 'yoy' ? 'yoy' : 'prev'; + + const { startDate, endDate } = getCompareDate(compare, filters.startDate, filters.endDate); const comparison = await getWebsiteStats(websiteId, { ...filters, From a049fbb5b0b2e8c3325f058bea26050c849bd91f Mon Sep 17 00:00:00 2001 From: AymanAlSuleihi Date: Fri, 9 Jan 2026 23:41:16 +0000 Subject: [PATCH 029/107] Update compare parameter to use enum for valid values --- src/app/api/websites/[websiteId]/stats/route.ts | 2 +- src/lib/schema.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 12492537..b7177b5d 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -31,7 +31,7 @@ export async function GET( const data = await getWebsiteStats(websiteId, filters); - const compare = filters.compare === 'yoy' ? 'yoy' : 'prev'; + const compare = filters.compare ?? 'prev'; const { startDate, endDate } = getCompareDate(compare, filters.startDate, filters.endDate); diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 38f7339a..aafd8e43 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -20,7 +20,7 @@ export const dateRangeParams = { endDate: z.coerce.date().optional(), timezone: timezoneParam.optional(), unit: unitParam.optional(), - compare: z.string().optional(), + compare: z.enum(['prev', 'yoy']).optional(), }; export const filterParams = { From 1a664d8719ccd2243bed2c6a8c87e083a317b40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jere=20M=C3=A4ennen=C3=A4?= Date: Sun, 11 Jan 2026 14:25:41 +0200 Subject: [PATCH 030/107] Update fi-FI.json --- src/lang/fi-FI.json | 168 ++++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/src/lang/fi-FI.json b/src/lang/fi-FI.json index daaa62f0..68629470 100644 --- a/src/lang/fi-FI.json +++ b/src/lang/fi-FI.json @@ -37,7 +37,7 @@ "label.compare-dates": "Vertaa päivämääriä", "label.confirm": "Vahvista", "label.confirm-password": "Vahvista salasana", - "label.contains": "Contains", + "label.contains": "Sisältää", "label.content": "Sisältö", "label.continue": "Jatka", "label.conversion": "Konversio", @@ -96,7 +96,7 @@ "label.false": "Epätosi", "label.field": "Kenttä", "label.fields": "Kentät", - "label.filter": "Filter", + "label.filter": "Suodatin", "label.filter-combined": "Yhdistetty", "label.filter-raw": "Käsittelemätön", "label.filters": "Suodattimet", @@ -151,7 +151,7 @@ "label.members": "Jäsenet", "label.min": "Minimi", "label.mobile": "Puhelin", - "label.model": "Model", + "label.model": "Malli", "label.more": "Lisää", "label.my-account": "Oma tili", "label.my-websites": "Omat verkkosivut", @@ -184,9 +184,9 @@ "label.paths": "Polut", "label.pixels": "Pikselit", "label.powered-by": "Voimanlähteenä {name}", - "label.previous": "Previous", - "label.previous-period": "Previous period", - "label.previous-year": "Previous year", + "label.previous": "Edellinen", + "label.previous-period": "Edellinen ajanjakso", + "label.previous-year": "Edellinen vuosi", "label.profile": "Profiili", "label.properties": "Ominaisuudet", "label.property": "Ominaisuus", @@ -195,16 +195,16 @@ "label.query-parameters": "Kyselyn parametrit", "label.realtime": "Juuri nyt", "label.referral": "Viittaus", - "label.referrer": "Referrer", + "label.referrer": "Viittaaja", "label.referrers": "Viittaajat", "label.refresh": "Päivitä", - "label.regenerate": "Regenerate", - "label.region": "Region", - "label.regions": "Regions", + "label.regenerate": "Luo uudelleen", + "label.region": "Alue", + "label.regions": "Alueet", "label.remaining": "Jäljellä", - "label.remove": "Remove", - "label.remove-member": "Remove member", - "label.reports": "Reports", + "label.remove": "Poista", + "label.remove-member": "Poista jäsen", + "label.reports": "Raportit", "label.required": "Vaaditaan", "label.reset": "Nollaa", "label.reset-website": "Nollaa tilastot", @@ -212,19 +212,19 @@ "label.retention-description": "Measure your website stickiness by tracking how often users return.", "label.revenue": "Tulot", "label.revenue-description": "Katso tulosi ajan mittaan.", - "label.role": "Role", - "label.run-query": "Run query", + "label.role": "Rooli", + "label.run-query": "Suorita kysely", "label.save": "Tallenna", "label.screens": "Näytöt", - "label.search": "Search", - "label.select": "Select", - "label.select-date": "Select date", + "label.search": "Haku", + "label.select": "Valitse", + "label.select-date": "Valitse päivämäärä", "label.select-filter": "Valitse suodatin", - "label.select-role": "Select role", - "label.select-website": "Select website", + "label.select-role": "Valitse rooli", + "label.select-website": "Valitse verkkosivu", "label.session": "Istunto", "label.session-data": "Istuntotiedot", - "label.sessions": "Sessions", + "label.sessions": "Istunnot", "label.settings": "Asetukset", "label.share": "Jaa", "label.share-url": "Jaa URL", @@ -233,107 +233,107 @@ "label.sources": "Lähteet", "label.start-step": "Aloitusvaihe", "label.steps": "Vaiheet", - "label.sum": "Sum", + "label.sum": "Summa", "label.tablet": "Tabletti", "label.tag": "Tunniste", "label.tags": "Tunnisteet", - "label.team": "Team", - "label.team-id": "Team ID", - "label.team-manager": "Team manager", - "label.team-member": "Team member", - "label.team-name": "Team name", - "label.team-owner": "Team owner", + "label.team": "Tiimi", + "label.team-id": "Tiimin ID", + "label.team-manager": "Tiimin johtaja", + "label.team-member": "Tiimin jäsen", + "label.team-name": "Tiimin nimi", + "label.team-owner": "Tiimin omistaja", "label.team-settings": "Tiimin asetukset", "label.team-view-only": "Team view only", - "label.team-websites": "Team websites", - "label.teams": "Teams", + "label.team-websites": "Tiimin verkkosivut", + "label.teams": "Tiimit", "label.terms": "Ehdot", "label.theme": "Teema", "label.this-month": "Tämä kuukausi", "label.this-week": "Tämä viikko", "label.this-year": "Tämä vuosi", "label.timezone": "Aikavyöhyke", - "label.title": "Title", + "label.title": "Otsikko", "label.today": "Tänään", "label.toggle-charts": "Kytke kaaviot päälle/pois", - "label.total": "Total", - "label.total-records": "Total records", + "label.total": "Yhteensä", + "label.total-records": "Tietueita yhteensä", "label.tracking-code": "Seurantakoodi", - "label.transactions": "Transactions", - "label.transfer": "Transfer", - "label.transfer-website": "Transfer website", - "label.true": "True", - "label.type": "Type", - "label.unique": "Unique", + "label.transactions": "Transaktiot", + "label.transfer": "Siirrä", + "label.transfer-website": "Siirrä verkkosivu", + "label.true": "Tosi", + "label.type": "Tyyppi", + "label.unique": "Uniikki", "label.unique-visitors": "Yksittäiset kävijät", - "label.uniqueCustomers": "Unique Customers", + "label.uniqueCustomers": "Uniikit asiakkaat", "label.unknown": "Tuntematon", - "label.untitled": "Untitled", - "label.update": "Update", - "label.user": "User", + "label.untitled": "Nimetön", + "label.update": "Päivitä", + "label.user": "Käyttäjä", "label.username": "Käyttäjänimi", - "label.users": "Users", + "label.users": "Käyttäjät", "label.utm": "UTM", - "label.utm-description": "Track your campaigns through UTM parameters.", - "label.value": "Value", - "label.view": "View", + "label.utm-description": "Seuraa kampanjoitasi UTM-parametrien avulla.", + "label.value": "Arvo", + "label.view": "Näytä", "label.view-details": "Katso tiedot", - "label.view-only": "View only", + "label.view-only": "Vain katselu", "label.views": "Näyttökerrat", - "label.views-per-visit": "Views per visit", + "label.views-per-visit": "Katselukerrat vierailua kohti", "label.visit-duration": "Keskimääräinen vierailuaika", "label.visitors": "Vierailijat", - "label.visits": "Visits", - "label.website": "Website", - "label.website-id": "Website ID", + "label.visits": "Vierailut", + "label.website": "Verkkosivu", + "label.website-id": "Verkkosivun ID", "label.websites": "Verkkosivut", - "label.window": "Window", - "label.yesterday": "Yesterday", - "label.behavior": "Behavior", - "message.action-confirmation": "Type {confirmation} in the box below to confirm.", + "label.window": "Ikkuna", + "label.yesterday": "Eilen", + "label.behavior": "Käyttäytyminen", + "message.action-confirmation": "Kirjoita {confirmation} alla olevaan kenttään vahvistaaksesi.", "message.active-users": "{x} {x, plural, one {vierailija} other {vierailijaa}}", - "message.bad-request": "Bad request", - "message.collected-data": "Collected data", + "message.bad-request": "Virheellinen pyyntö", + "message.collected-data": "Kerätty data", "message.confirm-delete": "Haluatko varmasti poistaa sivuston {target}?", - "message.confirm-leave": "Are you sure you want to leave {target}?", - "message.confirm-remove": "Are you sure you want to remove {target}?", + "message.confirm-leave": "Haluatko varmasti poistua {target}?", + "message.confirm-remove": "Haluatko varmasti poistaa {target}?", "message.confirm-reset": "Haluatko varmasti poistaa sivuston {target} tilastot?", - "message.delete-team-warning": "Deleting a team will also delete all team websites.", + "message.delete-team-warning": "Tiimin poistaminen poistaa myös kaikki tiimin sivustot.", "message.delete-website-warning": "Kaikki siihen liittyvät tiedot poistetaan.", "message.error": "Jotain meni pieleen.", "message.event-log": "{event} on {url}", - "message.forbidden": "Forbidden", + "message.forbidden": "Kielletty", "message.go-to-settings": "Mene asetuksiin", "message.incorrect-username-password": "Väärä käyttäjänimi/salasana.", "message.invalid-domain": "Virheellinen verkkotunnus", - "message.min-password-length": "Minimum length of {n} characters", - "message.new-version-available": "A new version of Umami {version} is available!", + "message.min-password-length": "Vähimmäispituus {n} merkkiä", + "message.new-version-available": "Umamista on saatavilla uusi versio {version}!", "message.no-data-available": "Tietoja ei ole käytettävissä.", - "message.no-event-data": "No event data is available.", + "message.no-event-data": "Tapahtumatietoja ei ole saatavilla.", "message.no-match-password": "Salasanat eivät täsmää", - "message.no-results-found": "No results were found.", - "message.no-team-websites": "This team does not have any websites.", - "message.no-teams": "You have not created any teams.", - "message.no-users": "There are no users.", + "message.no-results-found": "Tuloksia ei löytynyt.", + "message.no-team-websites": "Tällä tiimillä ei ole verkkosivustoja.", + "message.no-teams": "Et ole luonut yhtään tiimiä.", + "message.no-users": "Käyttäjiä ei ole.", "message.no-websites-configured": "Sinulla ei ole määritettyjä verkkosivustoja.", - "message.not-found": "Not found", - "message.nothing-selected": "Nothing selected.", + "message.not-found": "Ei löytynyt", + "message.nothing-selected": "Mitään ei ole valittu.", "message.page-not-found": "Sivua ei löydetty.", - "message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.", + "message.reset-website": "Nollaa verkkosivusto kirjoittamalla {confirmation} alla olevaan kenttään vahvistaaksesi.", "message.reset-website-warning": "Kaikki sivuston tilastot poistetaan, mutta seurantakoodi pysyy muuttumattomana.", "message.saved": "Tallennettu onnistuneesti.", - "message.sever-error": "Server error", + "message.sever-error": "Palvelinvirhe", "message.share-url": "Tämä on julkisesti jaettu URL sivustolle {target}.", - "message.team-already-member": "You are already a member of the team.", - "message.team-not-found": "Team not found.", - "message.team-websites-info": "Websites can be viewed by anyone on the team.", + "message.team-already-member": "Olet jo tiimissä.", + "message.team-not-found": "Tiimiä ei löydetty.", + "message.team-websites-info": "Verkkosivuja voi tarkastella kuka tahansa tiimin jäsen.", "message.tracking-code": "Seurantakoodi", - "message.transfer-team-website-to-user": "Transfer this website to your account?", - "message.transfer-user-website-to-team": "Select the team to transfer this website to.", - "message.transfer-website": "Transfer website ownership to your account or another team.", - "message.triggered-event": "Triggered event", - "message.unauthorized": "Unauthorized", - "message.user-deleted": "User deleted.", - "message.viewed-page": "Viewed page", + "message.transfer-team-website-to-user": "Siirretäänkö tämä verkkosivu tilillesi?", + "message.transfer-user-website-to-team": "Valitse tiimi, johon verkkosivusto siirretään.", + "message.transfer-website": "Siirrä verkkosivusto tiliisi tai toiselle tiimille.", + "message.triggered-event": "Laukaistu tapahtuma", + "message.unauthorized": "Ei oikeuksia", + "message.user-deleted": "Käyttäjä poistettu.", + "message.viewed-page": "Katsottu sivu", "message.visitor-log": "Vierailija maasta {country} selaimella {browser} laitteella {os} {device}" -} +} \ No newline at end of file From ebfbc282eebbe9dd87f6255129bc6417549faa3a Mon Sep 17 00:00:00 2001 From: Dorian TETU Date: Tue, 13 Jan 2026 16:39:39 +0100 Subject: [PATCH 031/107] fix: autofill background color --- src/styles/global.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/styles/global.css b/src/styles/global.css index e9fca9fd..6e767563 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -41,3 +41,18 @@ a:hover { border: 4px solid rgba(0, 0, 0, 0); background-clip: padding-box; } + +/* Fix autofill background color to match dark theme */ +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active, +textarea:-webkit-autofill, +textarea:-webkit-autofill:hover, +textarea:-webkit-autofill:focus, +select:-webkit-autofill, +select:-webkit-autofill:hover, +select:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0 1000px var(--background-color) inset !important; + transition: color 5000s ease-in-out 0s; +} From b6013c3ee8d21a6b82efc398e2fa103c71a9e425 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 14 Jan 2026 10:28:48 -0800 Subject: [PATCH 032/107] Revert "refactor 6 month retention. use auth instead of cache:website". Fix share page retention bug. This reverts commit 741c6039e60020e62dd81c20f3f01e365d7d2b73. --- src/app/api/reports/attribution/route.ts | 4 ++-- src/app/api/reports/breakdown/route.ts | 4 ++-- src/app/api/reports/funnel/route.ts | 4 ++-- src/app/api/reports/goal/route.ts | 4 ++-- src/app/api/reports/retention/route.ts | 4 ++-- src/app/api/reports/revenue/route.ts | 4 ++-- src/app/api/reports/utm/route.ts | 4 ++-- .../api/websites/[websiteId]/event-data/events/route.ts | 2 +- .../api/websites/[websiteId]/event-data/fields/route.ts | 2 +- .../websites/[websiteId]/event-data/properties/route.ts | 2 +- src/app/api/websites/[websiteId]/event-data/stats/route.ts | 2 +- .../api/websites/[websiteId]/event-data/values/route.ts | 2 +- src/app/api/websites/[websiteId]/events/route.ts | 2 +- src/app/api/websites/[websiteId]/events/series/route.ts | 2 +- src/app/api/websites/[websiteId]/export/route.ts | 2 +- src/app/api/websites/[websiteId]/metrics/expanded/route.ts | 2 +- src/app/api/websites/[websiteId]/metrics/route.ts | 2 +- src/app/api/websites/[websiteId]/pageviews/route.ts | 2 +- .../websites/[websiteId]/session-data/properties/route.ts | 2 +- .../api/websites/[websiteId]/session-data/values/route.ts | 2 +- .../[websiteId]/sessions/[sessionId]/activity/route.ts | 2 +- src/app/api/websites/[websiteId]/sessions/route.ts | 2 +- src/app/api/websites/[websiteId]/sessions/stats/route.ts | 2 +- src/app/api/websites/[websiteId]/sessions/weekly/route.ts | 2 +- src/app/api/websites/[websiteId]/stats/route.ts | 2 +- src/app/api/websites/[websiteId]/values/route.ts | 2 +- src/lib/request.ts | 7 +++---- 27 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/app/api/reports/attribution/route.ts b/src/app/api/reports/attribution/route.ts index ab9b1ddb..bd7d86dc 100644 --- a/src/app/api/reports/attribution/route.ts +++ b/src/app/api/reports/attribution/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getAttribution(websiteId, parameters as AttributionParameters, filters); diff --git a/src/app/api/reports/breakdown/route.ts b/src/app/api/reports/breakdown/route.ts index a06636c7..3c593145 100644 --- a/src/app/api/reports/breakdown/route.ts +++ b/src/app/api/reports/breakdown/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters); diff --git a/src/app/api/reports/funnel/route.ts b/src/app/api/reports/funnel/route.ts index f6e21029..c13f6f1c 100644 --- a/src/app/api/reports/funnel/route.ts +++ b/src/app/api/reports/funnel/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getFunnel(websiteId, parameters as FunnelParameters, filters); diff --git a/src/app/api/reports/goal/route.ts b/src/app/api/reports/goal/route.ts index db2aabce..3bd0415d 100644 --- a/src/app/api/reports/goal/route.ts +++ b/src/app/api/reports/goal/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getGoal(websiteId, parameters as GoalParameters, filters); diff --git a/src/app/api/reports/retention/route.ts b/src/app/api/reports/retention/route.ts index 5adf7bb8..d1a7d698 100644 --- a/src/app/api/reports/retention/route.ts +++ b/src/app/api/reports/retention/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); + const parameters = await setWebsiteDate(websiteId, body.parameters); const data = await getRetention(websiteId, parameters as RetentionParameters, filters); diff --git a/src/app/api/reports/revenue/route.ts b/src/app/api/reports/revenue/route.ts index f4146b96..6a556612 100644 --- a/src/app/api/reports/revenue/route.ts +++ b/src/app/api/reports/revenue/route.ts @@ -17,8 +17,8 @@ export async function POST(request: Request) { return unauthorized(); } - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); + const parameters = await setWebsiteDate(websiteId, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); const data = await getRevenue(websiteId, parameters as RevenuParameters, filters); diff --git a/src/app/api/reports/utm/route.ts b/src/app/api/reports/utm/route.ts index d4af4da0..577fdab7 100644 --- a/src/app/api/reports/utm/route.ts +++ b/src/app/api/reports/utm/route.ts @@ -18,8 +18,8 @@ export async function POST(request: Request) { return unauthorized(); } - const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); - const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); + const filters = await getQueryFilters(body.filters, websiteId); + const parameters = await setWebsiteDate(websiteId, body.parameters); const data = { utm_source: [], diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts index 444afa27..eb6ee6ed 100644 --- a/src/app/api/websites/[websiteId]/event-data/events/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataEvents(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts index e034d937..bce6a977 100644 --- a/src/app/api/websites/[websiteId]/event-data/fields/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataFields(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts index 29719fb2..52d15cfb 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataProperties(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts index 1d1e76df..042e989a 100644 --- a/src/app/api/websites/[websiteId]/event-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts index ed01fb2b..12e8f2dc 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -30,7 +30,7 @@ export async function GET( } const { propertyName } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventDataValues(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/events/route.ts b/src/app/api/websites/[websiteId]/events/route.ts index dfabb87f..74ec3ece 100644 --- a/src/app/api/websites/[websiteId]/events/route.ts +++ b/src/app/api/websites/[websiteId]/events/route.ts @@ -29,7 +29,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteEvents(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts index d5b92559..977e9c81 100644 --- a/src/app/api/websites/[websiteId]/events/series/route.ts +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -29,7 +29,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getEventStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/export/route.ts b/src/app/api/websites/[websiteId]/export/route.ts index f9749f34..eec81c6d 100644 --- a/src/app/api/websites/[websiteId]/export/route.ts +++ b/src/app/api/websites/[websiteId]/export/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([ getEventMetrics(websiteId, { type: 'event' }, filters), diff --git a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts index 7e6fbbfd..d52c1773 100644 --- a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts @@ -37,7 +37,7 @@ export async function GET( } const { type, limit, offset, search } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); if (search) { filters[type] = `c.${search}`; diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 2d0e6a60..12784adb 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -37,7 +37,7 @@ export async function GET( } const { type, limit, offset, search } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); if (search) { filters[type] = `c.${search}`; diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index dc921bc0..af59bce4 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const [pageviews, sessions] = await Promise.all([ getPageviewStats(websiteId, filters), diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts index a0aed73c..2d8db153 100644 --- a/src/app/api/websites/[websiteId]/session-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionDataProperties(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts index db710654..7d06870a 100644 --- a/src/app/api/websites/[websiteId]/session-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -29,7 +29,7 @@ export async function GET( } const { propertyName } = query; - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionDataValues(websiteId, { ...filters, diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts index 3e70bea0..41b766d0 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -25,7 +25,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getSessionActivity(websiteId, sessionId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts index f344476c..ed4757a1 100644 --- a/src/app/api/websites/[websiteId]/sessions/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteSessions(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/stats/route.ts b/src/app/api/websites/[websiteId]/sessions/stats/route.ts index 74b4e5e8..459830ed 100644 --- a/src/app/api/websites/[websiteId]/sessions/stats/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const metrics = await getWebsiteSessionStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts index f6dfd206..b9ccf3ef 100644 --- a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -28,7 +28,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWeeklyTraffic(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index a96fa5b0..b7177b5d 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -27,7 +27,7 @@ export async function GET( return unauthorized(); } - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); const data = await getWebsiteStats(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index c2c95b37..172325e3 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -42,7 +42,7 @@ export async function GET( value: segment.name, })); } else { - const filters = await getQueryFilters(query, websiteId, auth.user?.id); + const filters = await getQueryFilters(query, websiteId); values = await getValues(websiteId, FILTER_COLUMNS[type], filters); } diff --git a/src/lib/request.ts b/src/lib/request.ts index d6543b18..7f9163cc 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -81,12 +81,12 @@ export function getRequestFilters(query: Record) { return result; } -export async function setWebsiteDate(websiteId: string, userId: string, data: Record) { +export async function setWebsiteDate(websiteId: string, data: Record) { const website = await fetchWebsite(websiteId); const cloudMode = !!process.env.CLOUD_MODE; if (cloudMode && website && !website.teamId) { - const account = await fetchAccount(userId); + const account = await fetchAccount(website.userId); if (!account?.hasSubscription) { data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6))); @@ -103,13 +103,12 @@ export async function setWebsiteDate(websiteId: string, userId: string, data: Re export async function getQueryFilters( params: Record, websiteId?: string, - userId?: string, ): Promise { const dateRange = getRequestDateRange(params); const filters = getRequestFilters(params); if (websiteId) { - await setWebsiteDate(websiteId, userId, dateRange); + await setWebsiteDate(websiteId, dateRange); if (params.segment) { const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) From 889a404650be079a4299c96c0c53456f57d83732 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 14 Jan 2026 15:33:06 -0800 Subject: [PATCH 033/107] share table schema + migration --- prisma/migrations/15_add_share/migration.sql | 41 ++++++++++++++++++++ prisma/schema.prisma | 15 ++++++- src/lib/constants.ts | 7 ++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/15_add_share/migration.sql diff --git a/prisma/migrations/15_add_share/migration.sql b/prisma/migrations/15_add_share/migration.sql new file mode 100644 index 00000000..2a867bb5 --- /dev/null +++ b/prisma/migrations/15_add_share/migration.sql @@ -0,0 +1,41 @@ +-- CreateTable +CREATE TABLE "share" ( + "share_id" UUID NOT NULL, + "entity_id" UUID NOT NULL, + "share_type" INTEGER NOT NULL, + "share_code" VARCHAR(50), + "parameters" JSONB NOT NULL, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMPTZ(6), + + CONSTRAINT "share_pkey" PRIMARY KEY ("share_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "share_share_id_key" ON "share"("share_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "share_share_code_key" ON "share"("share_code"); + +-- CreateIndex +CREATE INDEX "share_entity_id_idx" ON "share"("entity_id"); + +-- MigrateData +INSERT INTO "share" (share_id, entity_id, share_type, share_code, parameters, created_at) +SELECT gen_random_uuid(), + website_id, + 1, + share_id, + '{}'::jsonb, + now() +FROM "website" +WHERE share_id IS NOT NULL; + +-- DropIndex +DROP INDEX "website_share_id_idx"; + +-- DropIndex +DROP INDEX "website_share_id_key"; + +-- AlterTable +ALTER TABLE "website" DROP COLUMN "share_id"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index aeb11648..e0600db0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -67,7 +67,6 @@ model Website { id String @id @unique @map("website_id") @db.Uuid name String @db.VarChar(100) domain String? @db.VarChar(500) - shareId String? @unique @map("share_id") @db.VarChar(50) resetAt DateTime? @map("reset_at") @db.Timestamptz(6) userId String? @map("user_id") @db.Uuid teamId String? @map("team_id") @db.Uuid @@ -88,7 +87,6 @@ model Website { @@index([userId]) @@index([teamId]) @@index([createdAt]) - @@index([shareId]) @@index([createdBy]) @@map("website") } @@ -316,3 +314,16 @@ model Pixel { @@index([createdAt]) @@map("pixel") } + +model Share { + id String @id() @unique() @map("share_id") @db.Uuid + entityId String @map("entity_id") @db.Uuid + shareType Int @map("share_type") @db.Integer + shareCode String? @unique @map("share_code") @db.VarChar(50) + parameters Json + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) + + @@index([entityId]) + @@map("share") +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 502a3df6..bfc80a13 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -95,6 +95,13 @@ export const EVENT_TYPE = { pixelEvent: 4, } as const; +export const ENTITY_TYPE = { + website: 1, + link: 2, + pixel: 3, + board: 4, +} as const; + export const DATA_TYPE = { string: 1, number: 2, From 29f2c7b7d46621a693cef0bb5ea7ed4f1baaae2d Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 15 Jan 2026 16:25:56 -0800 Subject: [PATCH 034/107] share api, queries, permissions, migration, entity lib --- prisma/migrations/15_add_share/migration.sql | 6 +- prisma/schema.prisma | 2 +- src/app/api/share/[shareId]/route.ts | 81 +++++++++++++++++--- src/app/api/share/[slug]/route.ts | 19 +++++ src/app/api/share/route.ts | 38 +++++++++ src/lib/entity.ts | 11 +++ src/permissions/entity.ts | 65 ++++++++++++++++ src/permissions/index.ts | 1 + src/queries/prisma/index.ts | 1 + src/queries/prisma/share.ts | 46 +++++++++++ src/queries/prisma/website.ts | 9 --- 11 files changed, 256 insertions(+), 23 deletions(-) create mode 100644 src/app/api/share/[slug]/route.ts create mode 100644 src/app/api/share/route.ts create mode 100644 src/lib/entity.ts create mode 100644 src/permissions/entity.ts create mode 100644 src/queries/prisma/share.ts diff --git a/prisma/migrations/15_add_share/migration.sql b/prisma/migrations/15_add_share/migration.sql index 2a867bb5..d9f1e7cf 100644 --- a/prisma/migrations/15_add_share/migration.sql +++ b/prisma/migrations/15_add_share/migration.sql @@ -3,7 +3,7 @@ CREATE TABLE "share" ( "share_id" UUID NOT NULL, "entity_id" UUID NOT NULL, "share_type" INTEGER NOT NULL, - "share_code" VARCHAR(50), + "slug" VARCHAR(100) NOT NULL, "parameters" JSONB NOT NULL, "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMPTZ(6), @@ -15,13 +15,13 @@ CREATE TABLE "share" ( CREATE UNIQUE INDEX "share_share_id_key" ON "share"("share_id"); -- CreateIndex -CREATE UNIQUE INDEX "share_share_code_key" ON "share"("share_code"); +CREATE UNIQUE INDEX "share_slug_key" ON "share"("slug"); -- CreateIndex CREATE INDEX "share_entity_id_idx" ON "share"("entity_id"); -- MigrateData -INSERT INTO "share" (share_id, entity_id, share_type, share_code, parameters, created_at) +INSERT INTO "share" (share_id, entity_id, share_type, slug, parameters, created_at) SELECT gen_random_uuid(), website_id, 1, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e0600db0..01cc372f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -319,7 +319,7 @@ model Share { id String @id() @unique() @map("share_id") @db.Uuid entityId String @map("entity_id") @db.Uuid shareType Int @map("share_type") @db.Integer - shareCode String? @unique @map("share_code") @db.VarChar(50) + slug String @unique() @db.VarChar(100) parameters Json createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) diff --git a/src/app/api/share/[shareId]/route.ts b/src/app/api/share/[shareId]/route.ts index bef87c4f..da7dcf56 100644 --- a/src/app/api/share/[shareId]/route.ts +++ b/src/app/api/share/[shareId]/route.ts @@ -1,19 +1,80 @@ -import { secret } from '@/lib/crypto'; -import { createToken } from '@/lib/jwt'; -import { json, notFound } from '@/lib/response'; -import { getSharedWebsite } from '@/queries/prisma'; +import z from 'zod'; +import { parseRequest } from '@/lib/request'; +import { json, notFound, ok, unauthorized } from '@/lib/response'; +import { anyObjectParam } from '@/lib/schema'; +import { canDeleteEntity, canUpdateEntity, canViewEntity } from '@/permissions'; +import { deleteShare, getShare, updateShare } from '@/queries/prisma'; + +export async function GET(request: Request, { params }: { params: Promise<{ shareId: string }> }) { + const { auth, error } = await parseRequest(request); + + if (error) { + return error(); + } -export async function GET(_request: Request, { params }: { params: Promise<{ shareId: string }> }) { const { shareId } = await params; - const website = await getSharedWebsite(shareId); + const share = await getShare(shareId); - if (!website) { + if (!(await canViewEntity(auth, share.entityId))) { + return unauthorized(); + } + + return json(share); +} + +export async function POST(request: Request, { params }: { params: Promise<{ shareId: string }> }) { + const schema = z.object({ + slug: z.string().max(100), + parameters: anyObjectParam, + }); + + const { auth, body, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { shareId } = await params; + const { slug, parameters } = body; + + const share = await getShare(shareId); + + if (!share) { return notFound(); } - const data = { websiteId: website.id }; - const token = createToken(data, secret()); + if (!(await canUpdateEntity(auth, share.entityId))) { + return unauthorized(); + } - return json({ ...data, token }); + const result = await updateShare(shareId, { + slug, + parameters, + } as any); + + return json(result); +} + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ shareId: string }> }, +) { + const { auth, error } = await parseRequest(request); + + if (error) { + return error(); + } + + const { shareId } = await params; + + const share = await getShare(shareId); + + if (!(await canDeleteEntity(auth, share.entityId))) { + return unauthorized(); + } + + await deleteShare(shareId); + + return ok(); } diff --git a/src/app/api/share/[slug]/route.ts b/src/app/api/share/[slug]/route.ts new file mode 100644 index 00000000..678795e0 --- /dev/null +++ b/src/app/api/share/[slug]/route.ts @@ -0,0 +1,19 @@ +import { secret } from '@/lib/crypto'; +import { createToken } from '@/lib/jwt'; +import { json, notFound } from '@/lib/response'; +import { getShareByCode } from '@/queries/prisma'; + +export async function GET(_request: Request, { params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + + const share = await getShareByCode(slug); + + if (!share) { + return notFound(); + } + + const data = { shareId: share.id }; + const token = createToken(data, secret()); + + return json({ ...data, token }); +} diff --git a/src/app/api/share/route.ts b/src/app/api/share/route.ts new file mode 100644 index 00000000..99f5df0e --- /dev/null +++ b/src/app/api/share/route.ts @@ -0,0 +1,38 @@ +import z from 'zod'; +import { uuid } from '@/lib/crypto'; +import { parseRequest } from '@/lib/request'; +import { json, unauthorized } from '@/lib/response'; +import { anyObjectParam } from '@/lib/schema'; +import { canUpdateEntity } from '@/permissions'; +import { createShare } from '@/queries/prisma'; + +export async function POST(request: Request) { + const schema = z.object({ + entityId: z.uuid(), + shareType: z.coerce.number().int(), + slug: z.string().max(100), + parameters: anyObjectParam, + }); + + const { auth, body, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { entityId, shareType, slug, parameters } = body; + + if (!(await canUpdateEntity(auth, entityId))) { + return unauthorized(); + } + + const share = await createShare({ + id: uuid(), + entityId, + shareType, + slug, + parameters, + }); + + return json(share); +} diff --git a/src/lib/entity.ts b/src/lib/entity.ts new file mode 100644 index 00000000..1b64e5dd --- /dev/null +++ b/src/lib/entity.ts @@ -0,0 +1,11 @@ +import { getLink, getPixel, getWebsite } from '@/queries/prisma'; + +export async function getEntity(entityId: string) { + const website = await getWebsite(entityId); + const link = await getLink(entityId); + const pixel = await getPixel(entityId); + + const entity = website || link || pixel; + + return entity; +} diff --git a/src/permissions/entity.ts b/src/permissions/entity.ts new file mode 100644 index 00000000..a9194d2c --- /dev/null +++ b/src/permissions/entity.ts @@ -0,0 +1,65 @@ +import { hasPermission } from '@/lib/auth'; +import { PERMISSIONS } from '@/lib/constants'; +import { getEntity } from '@/lib/entity'; +import type { Auth } from '@/lib/types'; +import { getTeamUser } from '@/queries/prisma'; + +export async function canViewEntity({ user }: Auth, entityId: string) { + if (user?.isAdmin) { + return true; + } + + const entity = await getEntity(entityId); + + if (entity.userId) { + return user.id === entity.userId; + } + + if (entity.teamId) { + const teamUser = await getTeamUser(entity.teamId, user.id); + + return !!teamUser; + } + + return false; +} + +export async function canUpdateEntity({ user }: Auth, entityId: string) { + if (user.isAdmin) { + return true; + } + + const entity = await getEntity(entityId); + + if (entity.userId) { + return user.id === entity.userId; + } + + if (entity.teamId) { + const teamUser = await getTeamUser(entity.teamId, user.id); + + return teamUser && hasPermission(teamUser.role, PERMISSIONS.websiteUpdate); + } + + return false; +} + +export async function canDeleteEntity({ user }: Auth, entityId: string) { + if (user.isAdmin) { + return true; + } + + const entity = await getEntity(entityId); + + if (entity.userId) { + return user.id === entity.userId; + } + + if (entity.teamId) { + const teamUser = await getTeamUser(entity.teamId, user.id); + + return teamUser && hasPermission(teamUser.role, PERMISSIONS.websiteDelete); + } + + return false; +} diff --git a/src/permissions/index.ts b/src/permissions/index.ts index a70808e6..475cdaa4 100644 --- a/src/permissions/index.ts +++ b/src/permissions/index.ts @@ -1,3 +1,4 @@ +export * from './entity'; export * from './link'; export * from './pixel'; export * from './report'; diff --git a/src/queries/prisma/index.ts b/src/queries/prisma/index.ts index b9730f51..4dedb2b5 100644 --- a/src/queries/prisma/index.ts +++ b/src/queries/prisma/index.ts @@ -2,6 +2,7 @@ export * from './link'; export * from './pixel'; export * from './report'; export * from './segment'; +export * from './share'; export * from './team'; export * from './teamUser'; export * from './user'; diff --git a/src/queries/prisma/share.ts b/src/queries/prisma/share.ts new file mode 100644 index 00000000..e37dc95b --- /dev/null +++ b/src/queries/prisma/share.ts @@ -0,0 +1,46 @@ +import type { Prisma } from '@/generated/prisma/client'; +import prisma from '@/lib/prisma'; + +export async function findShare(criteria: Prisma.ShareFindUniqueArgs) { + return prisma.client.share.findUnique(criteria); +} + +export async function getShare(entityId: string) { + return findShare({ + where: { + id: entityId, + }, + }); +} + +export async function getShareByCode(slug: string) { + return findShare({ + where: { + slug, + }, + }); +} + +export async function createShare( + data: Prisma.ShareCreateInput | Prisma.ShareUncheckedCreateInput, +) { + return prisma.client.share.create({ + data, + }); +} + +export async function updateShare( + shareId: string, + data: Prisma.ShareUpdateInput | Prisma.ShareUncheckedUpdateInput, +) { + return prisma.client.share.update({ + where: { + id: shareId, + }, + data, + }); +} + +export async function deleteShare(shareId: string) { + return prisma.client.share.delete({ where: { id: shareId } }); +} diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 6c8625d0..fe57589c 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -16,15 +16,6 @@ export async function getWebsite(websiteId: string) { }); } -export async function getSharedWebsite(shareId: string) { - return findWebsite({ - where: { - shareId, - deletedAt: null, - }, - }); -} - export async function getWebsites(criteria: Prisma.WebsiteFindManyArgs, filters: QueryFilters) { const { search } = filters; const { getSearchParameters, pagedQuery } = prisma; From 520c91c62142476ace9ce5ae9954487a57615400 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 16 Jan 2026 16:29:08 -0800 Subject: [PATCH 035/107] fix share api routes --- src/app/api/share/{ => id}/[shareId]/route.ts | 0 src/components/hooks/queries/useShareTokenQuery.ts | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/app/api/share/{ => id}/[shareId]/route.ts (100%) diff --git a/src/app/api/share/[shareId]/route.ts b/src/app/api/share/id/[shareId]/route.ts similarity index 100% rename from src/app/api/share/[shareId]/route.ts rename to src/app/api/share/id/[shareId]/route.ts diff --git a/src/components/hooks/queries/useShareTokenQuery.ts b/src/components/hooks/queries/useShareTokenQuery.ts index dbad3dcd..446e33da 100644 --- a/src/components/hooks/queries/useShareTokenQuery.ts +++ b/src/components/hooks/queries/useShareTokenQuery.ts @@ -3,7 +3,7 @@ import { useApi } from '../useApi'; const selector = (state: { shareToken: string }) => state.shareToken; -export function useShareTokenQuery(shareId: string): { +export function useShareTokenQuery(slug: string): { shareToken: any; isLoading?: boolean; error?: Error; @@ -11,9 +11,9 @@ export function useShareTokenQuery(shareId: string): { const shareToken = useApp(selector); const { get, useQuery } = useApi(); const { isLoading, error } = useQuery({ - queryKey: ['share', shareId], + queryKey: ['share', slug], queryFn: async () => { - const data = await get(`/share/${shareId}`); + const data = await get(`/share/${slug}`); setShareToken(data); From 3fe7afbb12b3dc6915e041f24408248394df91fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 01:20:18 +0000 Subject: [PATCH 036/107] Bump tar from 6.2.1 to 7.5.4 Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.1 to 7.5.4. - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v6.2.1...v7.5.4) --- updated-dependencies: - dependency-name: tar dependency-version: 7.5.4 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 78 ++++++++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 76b1e1fa..456d1a2c 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,7 @@ "stylelint-config-css-modules": "^4.5.1", "stylelint-config-prettier": "^9.0.3", "stylelint-config-recommended": "^14.0.0", - "tar": "^6.1.2", + "tar": "^7.5.4", "ts-jest": "^29.4.6", "ts-node": "^10.9.1", "tsup": "^8.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f2b1ce6..a042f9ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,8 +313,8 @@ importers: specifier: ^14.0.0 version: 14.0.1(stylelint@15.11.0(typescript@5.9.3)) tar: - specifier: ^6.1.2 - version: 6.2.1 + specifier: ^7.5.4 + version: 7.5.4 ts-jest: specifier: ^29.4.6 version: 29.4.6(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.12)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(typescript@5.9.3) @@ -1567,6 +1567,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -3318,9 +3322,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} @@ -4112,10 +4116,6 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5195,21 +5195,13 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -6755,9 +6747,9 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} + tar@7.5.4: + resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==} + engines: {node: '>=18'} terser@5.43.1: resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} @@ -7156,6 +7148,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -8249,6 +8245,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -10569,7 +10569,7 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@2.0.0: {} + chownr@3.0.0: {} ci-info@3.9.0: {} @@ -11548,10 +11548,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -12833,18 +12829,11 @@ snapshots: minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: + minizlib@3.1.0: dependencies: - minipass: 3.3.6 - yallist: 4.0.0 + minipass: 7.1.2 mkdirp@1.0.4: {} @@ -14636,14 +14625,13 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tar@6.2.1: + tar@7.5.4: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 terser@5.43.1: dependencies: @@ -15058,6 +15046,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yaml@1.10.2: {} yaml@2.8.1: {} From 0eb598c817157fa32291ec74d4619d5a81ef7bf0 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 20 Jan 2026 16:45:02 -0800 Subject: [PATCH 037/107] implement website share functionality using share table - Add support for multiple share URLs per website with server-generated slugs - Create shares API endpoint for listing and creating website shares - Add SharesTable, ShareEditButton, ShareDeleteButton components - Move share management to website settings, remove header share button - Remove shareId from website update API (now uses separate share table) Co-Authored-By: Claude Opus 4.5 --- .../websites/[websiteId]/WebsiteHeader.tsx | 31 ++--- .../settings/ShareDeleteButton.tsx | 57 +++++++++ .../[websiteId]/settings/ShareEditButton.tsx | 16 +++ .../[websiteId]/settings/ShareEditForm.tsx | 94 +++++++++++++++ .../[websiteId]/settings/SharesTable.tsx | 46 +++++++ .../[websiteId]/settings/WebsiteSettings.tsx | 5 +- .../[websiteId]/settings/WebsiteShareForm.tsx | 112 +++++------------- src/app/api/share/route.ts | 5 +- src/app/api/websites/[websiteId]/route.ts | 18 +-- .../api/websites/[websiteId]/shares/route.ts | 74 ++++++++++++ src/components/hooks/index.ts | 1 + .../hooks/queries/useWebsiteSharesQuery.ts | 20 ++++ src/queries/prisma/share.ts | 22 +++- 13 files changed, 374 insertions(+), 127 deletions(-) create mode 100644 src/app/(main)/websites/[websiteId]/settings/ShareDeleteButton.tsx create mode 100644 src/app/(main)/websites/[websiteId]/settings/ShareEditButton.tsx create mode 100644 src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx create mode 100644 src/app/(main)/websites/[websiteId]/settings/SharesTable.tsx create mode 100644 src/app/api/websites/[websiteId]/shares/route.ts create mode 100644 src/components/hooks/queries/useWebsiteSharesQuery.ts diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx index 7dd1d771..e79576dd 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx @@ -1,11 +1,9 @@ import { Icon, Row, Text } from '@umami/react-zen'; -import { WebsiteShareForm } from '@/app/(main)/websites/[websiteId]/settings/WebsiteShareForm'; import { Favicon } from '@/components/common/Favicon'; import { LinkButton } from '@/components/common/LinkButton'; import { PageHeader } from '@/components/common/PageHeader'; import { useMessages, useNavigation, useWebsite } from '@/components/hooks'; -import { Edit, Share } from '@/components/icons'; -import { DialogButton } from '@/components/input/DialogButton'; +import { Edit } from '@/components/icons'; import { ActiveUsers } from '@/components/metrics/ActiveUsers'; export function WebsiteHeader({ showActions }: { showActions?: boolean }) { @@ -29,29 +27,14 @@ export function WebsiteHeader({ showActions }: { showActions?: boolean }) { {showActions && ( - - - - - - - {formatMessage(labels.edit)} - - + + + + + {formatMessage(labels.edit)} + )} ); } - -const ShareButton = ({ websiteId, shareId }) => { - const { formatMessage, labels } = useMessages(); - - return ( - } label={formatMessage(labels.share)} width="800px"> - {({ close }) => { - return ; - }} - - ); -}; diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareDeleteButton.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareDeleteButton.tsx new file mode 100644 index 00000000..35e96df3 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/ShareDeleteButton.tsx @@ -0,0 +1,57 @@ +import { ConfirmationForm } from '@/components/common/ConfirmationForm'; +import { useDeleteQuery, useMessages, useModified } from '@/components/hooks'; +import { Trash } from '@/components/icons'; +import { DialogButton } from '@/components/input/DialogButton'; +import { messages } from '@/components/messages'; + +export function ShareDeleteButton({ + shareId, + slug, + onSave, +}: { + shareId: string; + slug: string; + onSave?: () => void; +}) { + const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages(); + const { mutateAsync, isPending, error } = useDeleteQuery(`/share/id/${shareId}`); + const { touch } = useModified(); + + const handleConfirm = async (close: () => void) => { + await mutateAsync(null, { + onSuccess: () => { + touch('shares'); + onSave?.(); + close(); + }, + }); + }; + + return ( + } + title={formatMessage(labels.confirm)} + variant="quiet" + width="400px" + > + {({ close }) => ( + {slug}, + }} + /> + } + isLoading={isPending} + error={getErrorMessage(error)} + onConfirm={handleConfirm.bind(null, close)} + onClose={close} + buttonLabel={formatMessage(labels.delete)} + buttonVariant="danger" + /> + )} + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareEditButton.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareEditButton.tsx new file mode 100644 index 00000000..df1c2e64 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/ShareEditButton.tsx @@ -0,0 +1,16 @@ +import { useMessages } from '@/components/hooks'; +import { Edit } from '@/components/icons'; +import { DialogButton } from '@/components/input/DialogButton'; +import { ShareEditForm } from './ShareEditForm'; + +export function ShareEditButton({ shareId }: { shareId: string }) { + const { formatMessage, labels } = useMessages(); + + return ( + } title={formatMessage(labels.share)} variant="quiet" width="600px"> + {({ close }) => { + return ; + }} + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx new file mode 100644 index 00000000..b1d7d50a --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx @@ -0,0 +1,94 @@ +import { + Button, + Column, + Form, + FormSubmitButton, + Label, + Loading, + Row, + TextField, +} from '@umami/react-zen'; +import { useEffect, useState } from 'react'; +import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; +import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery'; + +export function ShareEditForm({ + shareId, + onSave, + onClose, +}: { + shareId: string; + onSave?: () => void; + onClose?: () => void; +}) { + const { formatMessage, labels, messages, getErrorMessage } = useMessages(); + const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(`/share/id/${shareId}`); + const { cloudMode } = useConfig(); + const { get } = useApi(); + const { modified } = useModified('shares'); + const [share, setShare] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const getUrl = (slug: string) => { + if (cloudMode) { + return `${process.env.cloudUrl}/share/${slug}`; + } + return `${window?.location.origin}${process.env.basePath || ''}/share/${slug}`; + }; + + useEffect(() => { + const loadShare = async () => { + setIsLoading(true); + try { + const data = await get(`/share/id/${shareId}`); + setShare(data); + } finally { + setIsLoading(false); + } + }; + loadShare(); + }, [shareId, modified]); + + const handleSubmit = async (data: any) => { + await mutateAsync( + { slug: data.slug, parameters: share?.parameters || {} }, + { + onSuccess: async () => { + toast(formatMessage(messages.saved)); + touch('shares'); + onSave?.(); + onClose?.(); + }, + }, + ); + }; + + if (isLoading) { + return ; + } + + const url = getUrl(share?.slug || ''); + + return ( + + + + + + + + {onClose && ( + + )} + {formatMessage(labels.save)} + + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/settings/SharesTable.tsx b/src/app/(main)/websites/[websiteId]/settings/SharesTable.tsx new file mode 100644 index 00000000..05e8b357 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/SharesTable.tsx @@ -0,0 +1,46 @@ +import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen'; +import { DateDistance } from '@/components/common/DateDistance'; +import { ExternalLink } from '@/components/common/ExternalLink'; +import { useConfig, useMessages } from '@/components/hooks'; +import { ShareDeleteButton } from './ShareDeleteButton'; +import { ShareEditButton } from './ShareEditButton'; + +export function SharesTable(props: DataTableProps) { + const { formatMessage, labels } = useMessages(); + const { cloudMode } = useConfig(); + + const getUrl = (slug: string) => { + if (cloudMode) { + return `${process.env.cloudUrl}/share/${slug}`; + } + return `${window?.location.origin}${process.env.basePath || ''}/share/${slug}`; + }; + + return ( + + + {({ slug }: any) => { + const url = getUrl(slug); + return ( + + {url} + + ); + }} + + + {(row: any) => } + + + {({ id, slug }: any) => { + return ( + + + + + ); + }} + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx index 3970cdbd..d39c4531 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx @@ -1,14 +1,11 @@ import { Column } from '@umami/react-zen'; import { Panel } from '@/components/common/Panel'; -import { useWebsite } from '@/components/hooks'; import { WebsiteData } from './WebsiteData'; import { WebsiteEditForm } from './WebsiteEditForm'; import { WebsiteShareForm } from './WebsiteShareForm'; import { WebsiteTrackingCode } from './WebsiteTrackingCode'; export function WebsiteSettings({ websiteId }: { websiteId: string; openExternal?: boolean }) { - const website = useWebsite(); - return ( @@ -18,7 +15,7 @@ export function WebsiteSettings({ websiteId }: { websiteId: string; openExternal - + diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx index 56c6f436..6ac4a404 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx @@ -1,93 +1,43 @@ -import { - Button, - Column, - Form, - FormButtons, - FormSubmitButton, - IconLabel, - Label, - Row, - Switch, - TextField, -} from '@umami/react-zen'; -import { RefreshCcw } from 'lucide-react'; -import { useState } from 'react'; -import { useConfig, useMessages, useUpdateQuery } from '@/components/hooks'; -import { getRandomChars } from '@/lib/generate'; - -const generateId = () => getRandomChars(16); +import { Button, Column, Heading, Row, Text } from '@umami/react-zen'; +import { Plus } from 'lucide-react'; +import { useApi, useMessages, useModified, useWebsiteSharesQuery } from '@/components/hooks'; +import { SharesTable } from './SharesTable'; export interface WebsiteShareFormProps { websiteId: string; - shareId?: string; - onSave?: () => void; - onClose?: () => void; } -export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: WebsiteShareFormProps) { - const { formatMessage, labels, messages, getErrorMessage } = useMessages(); - const [currentId, setCurrentId] = useState(shareId); - const { mutateAsync, error, touch, toast } = useUpdateQuery(`/websites/${websiteId}`); - const { cloudMode } = useConfig(); +export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) { + const { formatMessage, labels, messages } = useMessages(); + const { data, isLoading } = useWebsiteSharesQuery({ websiteId }); + const { post } = useApi(); + const { touch } = useModified(); - const getUrl = (shareId: string) => { - if (cloudMode) { - return `${process.env.cloudUrl}/share/${shareId}`; - } - - return `${window?.location.origin}${process.env.basePath || ''}/share/${shareId}`; + const handleCreate = async () => { + await post(`/websites/${websiteId}/shares`, { parameters: {} }); + touch('shares'); }; - const url = getUrl(currentId); - - const handleGenerate = () => { - setCurrentId(generateId()); - }; - - const handleSwitch = () => { - setCurrentId(currentId ? null : generateId()); - }; - - const handleSave = async () => { - const data = { - shareId: currentId, - }; - await mutateAsync(data, { - onSuccess: async () => { - toast(formatMessage(messages.saved)); - touch(`website:${websiteId}`); - onSave?.(); - onClose?.(); - }, - }); - }; + const shares = data?.data || []; + const hasShares = shares.length > 0; return ( -
- - - {formatMessage(labels.enableShareUrl)} - - {currentId && ( - - - - - - - - - - )} - - - {onClose && } - {formatMessage(labels.save)} - - - -
+ + + {formatMessage(labels.share)} + + + {hasShares ? ( + <> + {formatMessage(messages.shareUrl)} + + + ) : ( + {formatMessage(messages.noDataAvailable)} + )} + ); } diff --git a/src/app/api/share/route.ts b/src/app/api/share/route.ts index 99f5df0e..a772b4ab 100644 --- a/src/app/api/share/route.ts +++ b/src/app/api/share/route.ts @@ -1,5 +1,6 @@ import z from 'zod'; import { uuid } from '@/lib/crypto'; +import { getRandomChars } from '@/lib/generate'; import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { anyObjectParam } from '@/lib/schema'; @@ -10,7 +11,7 @@ export async function POST(request: Request) { const schema = z.object({ entityId: z.uuid(), shareType: z.coerce.number().int(), - slug: z.string().max(100), + slug: z.string().max(100).optional(), parameters: anyObjectParam, }); @@ -30,7 +31,7 @@ export async function POST(request: Request) { id: uuid(), entityId, shareType, - slug, + slug: slug || getRandomChars(16), parameters, }); diff --git a/src/app/api/websites/[websiteId]/route.ts b/src/app/api/websites/[websiteId]/route.ts index b4c0e7e8..59f314d3 100644 --- a/src/app/api/websites/[websiteId]/route.ts +++ b/src/app/api/websites/[websiteId]/route.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; -import { SHARE_ID_REGEX } from '@/lib/constants'; import { parseRequest } from '@/lib/request'; -import { badRequest, json, ok, serverError, unauthorized } from '@/lib/response'; +import { json, ok, unauthorized } from '@/lib/response'; import { canDeleteWebsite, canUpdateWebsite, canViewWebsite } from '@/permissions'; import { deleteWebsite, getWebsite, updateWebsite } from '@/queries/prisma'; @@ -33,7 +32,6 @@ export async function POST( const schema = z.object({ name: z.string().optional(), domain: z.string().optional(), - shareId: z.string().regex(SHARE_ID_REGEX).nullable().optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -43,23 +41,15 @@ export async function POST( } const { websiteId } = await params; - const { name, domain, shareId } = body; + const { name, domain } = body; if (!(await canUpdateWebsite(auth, websiteId))) { return unauthorized(); } - try { - const website = await updateWebsite(websiteId, { name, domain, shareId }); + const website = await updateWebsite(websiteId, { name, domain }); - return Response.json(website); - } catch (e: any) { - if (e.message.toLowerCase().includes('unique constraint') && e.message.includes('share_id')) { - return badRequest({ message: 'That share ID is already taken.' }); - } - - return serverError(e); - } + return Response.json(website); } export async function DELETE( diff --git a/src/app/api/websites/[websiteId]/shares/route.ts b/src/app/api/websites/[websiteId]/shares/route.ts new file mode 100644 index 00000000..db079d49 --- /dev/null +++ b/src/app/api/websites/[websiteId]/shares/route.ts @@ -0,0 +1,74 @@ +import { z } from 'zod'; +import { ENTITY_TYPE } from '@/lib/constants'; +import { uuid } from '@/lib/crypto'; +import { getRandomChars } from '@/lib/generate'; +import { parseRequest } from '@/lib/request'; +import { json, unauthorized } from '@/lib/response'; +import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema'; +import { canUpdateWebsite, canViewWebsite } from '@/permissions'; +import { createShare, getSharesByEntityId } from '@/queries/prisma'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + ...filterParams, + ...pagingParams, + }); + + const { auth, query, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { websiteId } = await params; + const { page, pageSize, search } = query; + + if (!(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const data = await getSharesByEntityId(websiteId, { + page, + pageSize, + search, + }); + + return json(data); +} + +export async function POST( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + parameters: anyObjectParam.optional(), + }); + + const { auth, body, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { websiteId } = await params; + const { parameters = {} } = body; + + if (!(await canUpdateWebsite(auth, websiteId))) { + return unauthorized(); + } + + const slug = getRandomChars(16); + + const share = await createShare({ + id: uuid(), + entityId: websiteId, + shareType: ENTITY_TYPE.website, + slug, + parameters, + }); + + return json(share); +} diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index e8e5c135..f47f11f0 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -51,6 +51,7 @@ export * from './queries/useWebsiteSegmentsQuery'; export * from './queries/useWebsiteSessionQuery'; export * from './queries/useWebsiteSessionStatsQuery'; export * from './queries/useWebsiteSessionsQuery'; +export * from './queries/useWebsiteSharesQuery'; export * from './queries/useWebsiteStatsQuery'; export * from './queries/useWebsitesQuery'; export * from './queries/useWebsiteValuesQuery'; diff --git a/src/components/hooks/queries/useWebsiteSharesQuery.ts b/src/components/hooks/queries/useWebsiteSharesQuery.ts new file mode 100644 index 00000000..298e4d26 --- /dev/null +++ b/src/components/hooks/queries/useWebsiteSharesQuery.ts @@ -0,0 +1,20 @@ +import type { ReactQueryOptions } from '@/lib/types'; +import { useApi } from '../useApi'; +import { useModified } from '../useModified'; +import { usePagedQuery } from '../usePagedQuery'; + +export function useWebsiteSharesQuery( + { websiteId }: { websiteId: string }, + options?: ReactQueryOptions, +) { + const { modified } = useModified('shares'); + const { get } = useApi(); + + return usePagedQuery({ + queryKey: ['websiteShares', { websiteId, modified }], + queryFn: pageParams => { + return get(`/websites/${websiteId}/shares`, pageParams); + }, + ...options, + }); +} diff --git a/src/queries/prisma/share.ts b/src/queries/prisma/share.ts index e37dc95b..53246ffb 100644 --- a/src/queries/prisma/share.ts +++ b/src/queries/prisma/share.ts @@ -1,14 +1,15 @@ import type { Prisma } from '@/generated/prisma/client'; import prisma from '@/lib/prisma'; +import type { QueryFilters } from '@/lib/types'; export async function findShare(criteria: Prisma.ShareFindUniqueArgs) { return prisma.client.share.findUnique(criteria); } -export async function getShare(entityId: string) { +export async function getShare(shareId: string) { return findShare({ where: { - id: entityId, + id: shareId, }, }); } @@ -21,6 +22,23 @@ export async function getShareByCode(slug: string) { }); } +export async function getSharesByEntityId(entityId: string, filters?: QueryFilters) { + const { pagedQuery } = prisma; + + return pagedQuery( + 'share', + { + where: { + entityId, + }, + orderBy: { + createdAt: 'desc', + }, + }, + filters, + ); +} + export async function createShare( data: Prisma.ShareCreateInput | Prisma.ShareUncheckedCreateInput, ) { From ef3aec09bea636100dd2a2fa801f6cd1357ada6e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 20 Jan 2026 17:22:16 -0800 Subject: [PATCH 038/107] Add display options form for website shares Allow users to select which navigation items to display when creating or editing a share. Options include traffic, behavior, and growth sections with checkboxes for each nav item (excluding segments/cohorts). Co-Authored-By: Claude Opus 4.5 --- .../[websiteId]/settings/ShareCreateForm.tsx | 83 +++++++++++++++++++ .../[websiteId]/settings/ShareEditForm.tsx | 45 ++++++++-- .../[websiteId]/settings/WebsiteShareForm.tsx | 26 +++--- .../[websiteId]/settings/constants.ts | 30 +++++++ 4 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx create mode 100644 src/app/(main)/websites/[websiteId]/settings/constants.ts diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx new file mode 100644 index 00000000..024fc10b --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx @@ -0,0 +1,83 @@ +import { + Button, + Checkbox, + Column, + Form, + FormField, + FormSubmitButton, + Row, + Text, +} from '@umami/react-zen'; +import { useState } from 'react'; +import { useApi, useMessages, useModified } from '@/components/hooks'; +import { SHARE_NAV_ITEMS } from './constants'; + +export interface ShareCreateFormProps { + websiteId: string; + onSave?: () => void; + onClose?: () => void; +} + +export function ShareCreateForm({ websiteId, onSave, onClose }: ShareCreateFormProps) { + const { formatMessage, labels } = useMessages(); + const { post } = useApi(); + const { touch } = useModified(); + const [isPending, setIsPending] = useState(false); + + // Build default values - all enabled by default + const defaultValues: Record = {}; + SHARE_NAV_ITEMS.forEach(section => { + section.items.forEach(item => { + defaultValues[item.id] = true; + }); + }); + + const handleSubmit = async (data: any) => { + setIsPending(true); + try { + const parameters: Record = {}; + SHARE_NAV_ITEMS.forEach(section => { + section.items.forEach(item => { + parameters[item.id] = data[item.id] ?? true; + }); + }); + await post(`/websites/${websiteId}/shares`, { parameters }); + touch('shares'); + onSave?.(); + onClose?.(); + } finally { + setIsPending(false); + } + }; + + return ( +
+ + {SHARE_NAV_ITEMS.map(section => ( + + + {formatMessage((labels as any)[section.section])} + + + {section.items.map(item => ( + + + {formatMessage((labels as any)[item.label])} + + + ))} + + + ))} + + {onClose && ( + + )} + {formatMessage(labels.save)} + + +
+ ); +} diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx index b1d7d50a..5e8f8a74 100644 --- a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx @@ -1,16 +1,20 @@ import { Button, + Checkbox, Column, Form, + FormField, FormSubmitButton, Label, Loading, Row, + Text, TextField, } from '@umami/react-zen'; import { useEffect, useState } from 'react'; import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery'; +import { SHARE_NAV_ITEMS } from './constants'; export function ShareEditForm({ shareId, @@ -50,8 +54,15 @@ export function ShareEditForm({ }, [shareId, modified]); const handleSubmit = async (data: any) => { + const parameters: Record = {}; + SHARE_NAV_ITEMS.forEach(section => { + section.items.forEach(item => { + parameters[item.id] = data[item.id] ?? true; + }); + }); + await mutateAsync( - { slug: data.slug, parameters: share?.parameters || {} }, + { slug: share.slug, parameters }, { onSuccess: async () => { toast(formatMessage(messages.saved)); @@ -69,24 +80,42 @@ export function ShareEditForm({ const url = getUrl(share?.slug || ''); + // Build default values from share parameters + const defaultValues: Record = {}; + SHARE_NAV_ITEMS.forEach(section => { + section.items.forEach(item => { + defaultValues[item.id] = share?.parameters?.[item.id] ?? true; + }); + }); + return ( -
- + + + {SHARE_NAV_ITEMS.map(section => ( + + + {formatMessage((labels as any)[section.section])} + + + {section.items.map(item => ( + + {formatMessage((labels as any)[item.label])} + + ))} + + + ))} {onClose && ( )} - {formatMessage(labels.save)} + {formatMessage(labels.save)} diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx index 6ac4a404..7453b402 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx @@ -1,6 +1,8 @@ -import { Button, Column, Heading, Row, Text } from '@umami/react-zen'; +import { Column, Heading, Row, Text } from '@umami/react-zen'; import { Plus } from 'lucide-react'; -import { useApi, useMessages, useModified, useWebsiteSharesQuery } from '@/components/hooks'; +import { useMessages, useWebsiteSharesQuery } from '@/components/hooks'; +import { DialogButton } from '@/components/input/DialogButton'; +import { ShareCreateForm } from './ShareCreateForm'; import { SharesTable } from './SharesTable'; export interface WebsiteShareFormProps { @@ -10,13 +12,6 @@ export interface WebsiteShareFormProps { export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) { const { formatMessage, labels, messages } = useMessages(); const { data, isLoading } = useWebsiteSharesQuery({ websiteId }); - const { post } = useApi(); - const { touch } = useModified(); - - const handleCreate = async () => { - await post(`/websites/${websiteId}/shares`, { parameters: {} }); - touch('shares'); - }; const shares = data?.data || []; const hasShares = shares.length > 0; @@ -25,10 +20,15 @@ export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) { {formatMessage(labels.share)} - + } + label={formatMessage(labels.add)} + title={formatMessage(labels.share)} + variant="primary" + width="400px" + > + {({ close }) => } + {hasShares ? ( <> diff --git a/src/app/(main)/websites/[websiteId]/settings/constants.ts b/src/app/(main)/websites/[websiteId]/settings/constants.ts new file mode 100644 index 00000000..f4a3df80 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/settings/constants.ts @@ -0,0 +1,30 @@ +export const SHARE_NAV_ITEMS = [ + { + section: 'traffic', + items: [ + { id: 'overview', label: 'overview' }, + { id: 'events', label: 'events' }, + { id: 'sessions', label: 'sessions' }, + { id: 'realtime', label: 'realtime' }, + { id: 'compare', label: 'compare' }, + { id: 'breakdown', label: 'breakdown' }, + ], + }, + { + section: 'behavior', + items: [ + { id: 'goals', label: 'goals' }, + { id: 'funnels', label: 'funnels' }, + { id: 'journeys', label: 'journeys' }, + { id: 'retention', label: 'retention' }, + ], + }, + { + section: 'growth', + items: [ + { id: 'utm', label: 'utm' }, + { id: 'revenue', label: 'revenue' }, + { id: 'attribution', label: 'attribution' }, + ], + }, +]; From f2c49845d03e31a0e4321b221dbc509febda7fd5 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 20 Jan 2026 18:12:33 -0800 Subject: [PATCH 039/107] Add filtered navigation to share pages - Update share API to return websiteId and parameters - Create ShareNav component that filters nav items based on parameters - Update SharePage to include navigation sidebar and route to correct page - Support all website pages: overview, events, sessions, realtime, compare, breakdown, goals, funnels, journeys, retention, utm, revenue, attribution Co-Authored-By: Claude Opus 4.5 --- src/app/api/share/[slug]/route.ts | 6 +- src/app/share/[...shareId]/ShareNav.tsx | 143 +++++++++++++++++++++++ src/app/share/[...shareId]/SharePage.tsx | 76 ++++++++++-- src/app/share/[...shareId]/page.tsx | 3 +- 4 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 src/app/share/[...shareId]/ShareNav.tsx diff --git a/src/app/api/share/[slug]/route.ts b/src/app/api/share/[slug]/route.ts index 678795e0..ed3271ea 100644 --- a/src/app/api/share/[slug]/route.ts +++ b/src/app/api/share/[slug]/route.ts @@ -12,7 +12,11 @@ export async function GET(_request: Request, { params }: { params: Promise<{ slu return notFound(); } - const data = { shareId: share.id }; + const data = { + shareId: share.id, + websiteId: share.entityId, + parameters: share.parameters, + }; const token = createToken(data, secret()); return json({ ...data, token }); diff --git a/src/app/share/[...shareId]/ShareNav.tsx b/src/app/share/[...shareId]/ShareNav.tsx new file mode 100644 index 00000000..b494046d --- /dev/null +++ b/src/app/share/[...shareId]/ShareNav.tsx @@ -0,0 +1,143 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import { SideMenu } from '@/components/common/SideMenu'; +import { useMessages, useNavigation } from '@/components/hooks'; +import { AlignEndHorizontal, Clock, Eye, Sheet, Tag, User } from '@/components/icons'; +import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg'; + +export function ShareNav({ + shareId, + parameters, + onItemClick, +}: { + shareId: string; + parameters: Record; + onItemClick?: () => void; +}) { + const { formatMessage, labels } = useMessages(); + const { pathname } = useNavigation(); + + const renderPath = (path: string) => `/share/${shareId}${path}`; + + const allItems = [ + { + section: 'traffic', + label: formatMessage(labels.traffic), + items: [ + { + id: 'overview', + label: formatMessage(labels.overview), + icon: , + path: renderPath(''), + }, + { + id: 'events', + label: formatMessage(labels.events), + icon: , + path: renderPath('/events'), + }, + { + id: 'sessions', + label: formatMessage(labels.sessions), + icon: , + path: renderPath('/sessions'), + }, + { + id: 'realtime', + label: formatMessage(labels.realtime), + icon: , + path: renderPath('/realtime'), + }, + { + id: 'compare', + label: formatMessage(labels.compare), + icon: , + path: renderPath('/compare'), + }, + { + id: 'breakdown', + label: formatMessage(labels.breakdown), + icon: , + path: renderPath('/breakdown'), + }, + ], + }, + { + section: 'behavior', + label: formatMessage(labels.behavior), + items: [ + { + id: 'goals', + label: formatMessage(labels.goals), + icon: , + path: renderPath('/goals'), + }, + { + id: 'funnels', + label: formatMessage(labels.funnels), + icon: , + path: renderPath('/funnels'), + }, + { + id: 'journeys', + label: formatMessage(labels.journeys), + icon: , + path: renderPath('/journeys'), + }, + { + id: 'retention', + label: formatMessage(labels.retention), + icon: , + path: renderPath('/retention'), + }, + ], + }, + { + section: 'growth', + label: formatMessage(labels.growth), + items: [ + { + id: 'utm', + label: formatMessage(labels.utm), + icon: , + path: renderPath('/utm'), + }, + { + id: 'revenue', + label: formatMessage(labels.revenue), + icon: , + path: renderPath('/revenue'), + }, + { + id: 'attribution', + label: formatMessage(labels.attribution), + icon: , + path: renderPath('/attribution'), + }, + ], + }, + ]; + + // Filter items based on parameters + const items = allItems + .map(section => ({ + label: section.label, + items: section.items.filter(item => parameters[item.id] !== false), + })) + .filter(section => section.items.length > 0); + + const selectedKey = items + .flatMap(e => e.items) + .find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id; + + return ( + + + + ); +} diff --git a/src/app/share/[...shareId]/SharePage.tsx b/src/app/share/[...shareId]/SharePage.tsx index 7ed06673..3e1cedc0 100644 --- a/src/app/share/[...shareId]/SharePage.tsx +++ b/src/app/share/[...shareId]/SharePage.tsx @@ -1,6 +1,18 @@ 'use client'; -import { Column, useTheme } from '@umami/react-zen'; +import { Column, Grid, useTheme } from '@umami/react-zen'; import { useEffect } from 'react'; +import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage'; +import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage'; +import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage'; +import { GoalsPage } from '@/app/(main)/websites/[websiteId]/(reports)/goals/GoalsPage'; +import { JourneysPage } from '@/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage'; +import { RetentionPage } from '@/app/(main)/websites/[websiteId]/(reports)/retention/RetentionPage'; +import { RevenuePage } from '@/app/(main)/websites/[websiteId]/(reports)/revenue/RevenuePage'; +import { UTMPage } from '@/app/(main)/websites/[websiteId]/(reports)/utm/UTMPage'; +import { ComparePage } from '@/app/(main)/websites/[websiteId]/compare/ComparePage'; +import { EventsPage } from '@/app/(main)/websites/[websiteId]/events/EventsPage'; +import { RealtimePage } from '@/app/(main)/websites/[websiteId]/realtime/RealtimePage'; +import { SessionsPage } from '@/app/(main)/websites/[websiteId]/sessions/SessionsPage'; import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader'; import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage'; import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider'; @@ -8,8 +20,26 @@ import { PageBody } from '@/components/common/PageBody'; import { useShareTokenQuery } from '@/components/hooks'; import { Footer } from './Footer'; import { Header } from './Header'; +import { ShareNav } from './ShareNav'; -export function SharePage({ shareId }) { +const PAGE_COMPONENTS: Record> = { + '': WebsitePage, + overview: WebsitePage, + events: EventsPage, + sessions: SessionsPage, + realtime: RealtimePage, + compare: ComparePage, + breakdown: BreakdownPage, + goals: GoalsPage, + funnels: FunnelsPage, + journeys: JourneysPage, + retention: RetentionPage, + utm: UTMPage, + revenue: RevenuePage, + attribution: AttributionPage, +}; + +export function SharePage({ shareId, path = '' }: { shareId: string; path?: string }) { const { shareToken, isLoading } = useShareTokenQuery(shareId); const { setTheme } = useTheme(); @@ -26,16 +56,42 @@ export function SharePage({ shareId }) { return null; } + const { websiteId, parameters = {} } = shareToken; + + // Check if the requested path is allowed + const pageKey = path || ''; + const isAllowed = pageKey === '' || pageKey === 'overview' || parameters[pageKey] !== false; + + if (!isAllowed) { + return null; + } + + const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage; + return ( - -
- - - - -