From c98f324c22858e1cdd13d450e877b5927eb0581a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 11 Jul 2025 22:55:30 -0700 Subject: [PATCH] Settings refactor. --- db/postgresql/schema.prisma | 36 +- next.config.ts | 13 +- package.json | 2 +- pnpm-lock.yaml | 430 +++++++++++++++++- .../admin/teams/[teamId]/AdminTeamPage.tsx | 2 +- .../admin/users/[userId]/UserProvider.tsx | 19 +- src/app/(main)/settings/SettingsLayout.tsx | 5 + .../DateRangeSetting.tsx | 0 .../LanguageSetting.tsx | 0 .../preferences/PreferenceSettings.tsx | 39 ++ .../settings/preferences/PreferencesPage.tsx | 16 + .../{profile => preferences}/ThemeSetting.tsx | 7 +- .../TimezoneSetting.tsx | 0 src/app/(main)/settings/preferences/page.tsx | 10 + .../settings/profile/PasswordChangeButton.tsx | 17 +- .../settings/profile/ProfileSettings.tsx | 24 - src/app/(main)/settings/teams/TeamAddForm.tsx | 2 +- .../(main)/settings/teams/TeamJoinForm.tsx | 2 +- .../(main)/settings/teams/TeamsDataTable.tsx | 5 +- src/app/(main)/settings/teams/TeamsTable.tsx | 11 +- .../teams/[teamId]}/TeamDeleteForm.tsx | 0 .../teams/[teamId]}/TeamDetails.tsx | 10 +- .../teams/[teamId]}/TeamEditForm.tsx | 41 +- .../teams/[teamId]}/TeamManage.tsx | 0 .../teams/[teamId]}/TeamMemberEditButton.tsx | 18 +- .../teams/[teamId]}/TeamMemberEditForm.tsx | 8 +- .../[teamId]}/TeamMemberRemoveButton.tsx | 10 +- .../teams/[teamId]}/TeamMembersDataTable.tsx | 2 +- .../teams/[teamId]}/TeamMembersPage.tsx | 0 .../teams/[teamId]}/TeamMembersTable.tsx | 44 +- .../teams/[teamId]/TeamSettingsPage.tsx | 11 + .../[teamId]}/TeamWebsiteRemoveButton.tsx | 0 .../teams/[teamId]}/TeamWebsitesDataTable.tsx | 2 +- .../teams/[teamId]}/TeamWebsitesTable.tsx | 9 +- .../team => settings/teams/[teamId]}/page.tsx | 6 +- .../settings/websites/WebsiteAddButton.tsx | 17 +- .../settings/websites/WebsitesTable.tsx | 15 +- .../websites/[websiteId]/WebsiteEditForm.tsx | 2 +- .../websites/[websiteId]/WebsiteSettings.tsx | 10 +- .../(main)/teams/[teamId]/TeamProvider.tsx | 15 +- src/app/(main)/teams/[teamId]/layout.tsx | 7 +- .../[teamId]/settings/TeamSettingsLayout.tsx | 49 -- .../teams/[teamId]/settings/members/page.tsx | 12 - .../teams/[teamId]/settings/team/TeamPage.tsx | 6 - .../settings/websites/TeamWebsitesPage.tsx | 29 -- .../settings/websites/[websiteId]/page.tsx | 5 - .../teams/[teamId]/settings/websites/page.tsx | 12 - .../websites/[websiteId]/WebsiteProvider.tsx | 17 +- src/app/api/teams/[teamId]/users/route.ts | 9 +- src/components/common/ConfirmationForm.tsx | 11 +- src/components/hooks/queries/useTeamQuery.ts | 11 +- src/components/hooks/queries/useUserQuery.ts | 10 +- .../hooks/queries/useWebsiteQuery.ts | 9 +- src/components/input/FilterBar.tsx | 2 +- src/components/input/MenuButton.tsx | 4 +- src/components/messages.ts | 1 + 56 files changed, 706 insertions(+), 348 deletions(-) rename src/app/(main)/settings/{profile => preferences}/DateRangeSetting.tsx (100%) rename src/app/(main)/settings/{profile => preferences}/LanguageSetting.tsx (100%) create mode 100644 src/app/(main)/settings/preferences/PreferenceSettings.tsx create mode 100644 src/app/(main)/settings/preferences/PreferencesPage.tsx rename src/app/(main)/settings/{profile => preferences}/ThemeSetting.tsx (61%) rename src/app/(main)/settings/{profile => preferences}/TimezoneSetting.tsx (100%) create mode 100644 src/app/(main)/settings/preferences/page.tsx rename src/app/(main)/{teams/[teamId]/settings/team => settings/teams/[teamId]}/TeamDeleteForm.tsx (100%) rename src/app/(main)/{teams/[teamId]/settings/team => settings/teams/[teamId]}/TeamDetails.tsx (88%) rename src/app/(main)/{teams/[teamId]/settings/team => settings/teams/[teamId]}/TeamEditForm.tsx (69%) rename src/app/(main)/{teams/[teamId]/settings/team => settings/teams/[teamId]}/TeamManage.tsx (100%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMemberEditButton.tsx (83%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMemberEditForm.tsx (94%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMemberRemoveButton.tsx (87%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMembersDataTable.tsx (90%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMembersPage.tsx (100%) rename src/app/(main)/{teams/[teamId]/settings/members => settings/teams/[teamId]}/TeamMembersTable.tsx (50%) create mode 100644 src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx rename src/app/(main)/{teams/[teamId]/settings/websites => settings/teams/[teamId]}/TeamWebsiteRemoveButton.tsx (100%) rename src/app/(main)/{teams/[teamId]/settings/websites => settings/teams/[teamId]}/TeamWebsitesDataTable.tsx (91%) rename src/app/(main)/{teams/[teamId]/settings/websites => settings/teams/[teamId]}/TeamWebsitesTable.tsx (85%) rename src/app/(main)/{teams/[teamId]/settings/team => settings/teams/[teamId]}/page.tsx (62%) delete mode 100644 src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx delete mode 100644 src/app/(main)/teams/[teamId]/settings/members/page.tsx delete mode 100644 src/app/(main)/teams/[teamId]/settings/team/TeamPage.tsx delete mode 100644 src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesPage.tsx delete mode 100644 src/app/(main)/teams/[teamId]/settings/websites/[websiteId]/page.tsx delete mode 100644 src/app/(main)/teams/[teamId]/settings/websites/page.tsx diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 39dac240..cfcbf0ef 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -1,5 +1,5 @@ generator client { - provider = "prisma-client" + provider = "prisma-client-js" previewFeatures = ["queryCompiler", "driverAdapters"] output = "../src/generated/prisma" moduleFormat = "esm" @@ -46,7 +46,7 @@ model Session { websiteEvent WebsiteEvent[] sessionData SessionData[] - revenue Revenue[] + revenue Revenue[] @@index([createdAt]) @@index([websiteId]) @@ -224,7 +224,7 @@ model Report { type String @db.VarChar(200) name String @db.VarChar(200) description String @db.VarChar(500) - parameters Json + parameters Json createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) @@ -239,13 +239,13 @@ model Report { } model Segment { - id String @id() @unique() @map("segment_id") @db.Uuid - websiteId String @map("website_id") @db.Uuid - type String @db.VarChar(200) - name String @db.VarChar(200) - parameters Json - createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) - updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) + id String @id() @unique() @map("segment_id") @db.Uuid + websiteId String @map("website_id") @db.Uuid + type String @db.VarChar(200) + name String @db.VarChar(200) + parameters Json + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6) website Website @relation(fields: [websiteId], references: [id]) @@ -254,14 +254,14 @@ model Segment { } model Revenue { - id String @id() @unique() @map("revenue_id") @db.Uuid - websiteId String @map("website_id") @db.Uuid - sessionId String @map("session_id") @db.Uuid - eventId String @map("event_id") @db.Uuid - eventName String @map("event_name") @db.VarChar(50) - currency String @db.VarChar(100) - revenue Decimal? @db.Decimal(19, 4) - createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + id String @id() @unique() @map("revenue_id") @db.Uuid + websiteId String @map("website_id") @db.Uuid + sessionId String @map("session_id") @db.Uuid + eventId String @map("event_id") @db.Uuid + eventName String @map("event_name") @db.VarChar(50) + currency String @db.VarChar(100) + revenue Decimal? @db.Decimal(19, 4) + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) website Website @relation(fields: [websiteId], references: [id]) session Session @relation(fields: [sessionId], references: [id]) diff --git a/next.config.ts b/next.config.ts index 4e0561b7..f2c07aff 100644 --- a/next.config.ts +++ b/next.config.ts @@ -125,7 +125,7 @@ if (collectApiEndpoint) { const redirects = [ { source: '/settings', - destination: '/settings/profile', + destination: '/settings/preferences', permanent: true, }, { @@ -133,11 +133,6 @@ const redirects = [ destination: '/teams/:id/websites', permanent: true, }, - { - source: '/teams/:id/settings', - destination: '/teams/:id/settings/team', - permanent: true, - }, { source: '/admin', destination: '/admin/users', @@ -173,12 +168,6 @@ if (cloudMode && cloudUrl) { permanent: false, }); - redirects.push({ - source: '/teams/:id/settings/:path*', - destination: `${cloudUrl}/teams/:id/settings/:path*`, - permanent: false, - }); - if (disableLogin) { redirects.push({ source: '/login', diff --git a/package.json b/package.json index 02d0d23f..878176db 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@react-spring/web": "^9.7.3", "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.80.10", - "@umami/react-zen": "^0.144.0", + "@umami/react-zen": "^0.145.0", "@umami/redis-client": "^0.27.0", "bcryptjs": "^2.4.3", "chalk": "^4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6929ce71..9443ea0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.80.10 version: 5.80.10(react@19.1.0) '@umami/react-zen': - specifier: ^0.144.0 - version: 0.144.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) + specifier: ^0.145.0 + version: 0.145.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) '@umami/redis-client': specifier: ^0.27.0 version: 0.27.0 @@ -355,6 +355,45 @@ importers: specifier: ^5.5.3 version: 5.8.3 + dist: + dependencies: + '@tanstack/react-query': + specifier: ^4.33.0 + version: 4.40.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: + specifier: ^2.3.1 + version: 2.5.1 + colord: + specifier: ^2.9.2 + version: 2.9.3 + date-fns-tz: + specifier: ^1.1.4 + version: 1.3.8(date-fns@2.30.0) + immer: + specifier: ^9.0.12 + version: 9.0.21 + moment-timezone: + specifier: ^0.5.35 + version: 0.5.48 + next: + specifier: ^13.4.0 + version: 13.5.11(@babel/core@7.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-basics: + specifier: ^0.36.0 + version: 0.36.0(next@13.5.11(@babel/core@7.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-intl: + specifier: ^5.24.7 + version: 5.25.1(react@18.3.1)(typescript@4.9.5) + zustand: + specifier: ^4.3.8 + version: 4.5.7(@types/react@19.1.8)(immer@9.0.21)(react@18.3.1) + packages: '@ampproject/remapping@2.3.0': @@ -1056,6 +1095,9 @@ packages: '@formatjs/ecma402-abstract@2.3.4': resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} + '@formatjs/fast-memoize@1.2.1': + resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==} + '@formatjs/fast-memoize@2.2.3': resolution: {integrity: sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==} @@ -1080,9 +1122,15 @@ packages: '@formatjs/icu-skeleton-parser@1.8.8': resolution: {integrity: sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==} + '@formatjs/intl-displaynames@5.4.3': + resolution: {integrity: sha512-4r12A3mS5dp5hnSaQCWBuBNfi9Amgx2dzhU4lTFfhSxgb5DOAiAbMpg6+7gpWZgl4ahsj3l2r/iHIjdmdXOE2Q==} + '@formatjs/intl-displaynames@6.8.5': resolution: {integrity: sha512-85b+GdAKCsleS6cqVxf/Aw/uBd+20EM0wDpgaxzHo3RIR3bxF4xCJqH/Grbzx8CXurTgDDZHPdPdwJC+May41w==} + '@formatjs/intl-listformat@6.5.3': + resolution: {integrity: sha512-ozpz515F/+3CU+HnLi5DYPsLa6JoCfBggBSSg/8nOB5LYSFW9+ZgNQJxJ8tdhKYeODT+4qVHX27EeJLoxLGLNg==} + '@formatjs/intl-listformat@7.7.5': resolution: {integrity: sha512-Wzes10SMNeYgnxYiKsda4rnHP3Q3II4XT2tZyOgnH5fWuHDtIkceuWlRQNsvrI3uiwP4hLqp2XdQTCsfkhXulg==} @@ -1106,6 +1154,14 @@ packages: typescript: optional: true + '@formatjs/intl@2.2.1': + resolution: {integrity: sha512-vgvyUOOrzqVaOFYzTf2d3+ToSkH2JpR7x/4U1RyoHQLmvEaTQvXJ7A2qm1Iy3brGNXC/+/7bUlc3lpH+h/LOJA==} + peerDependencies: + typescript: ^4.5 + peerDependenciesMeta: + typescript: + optional: true + '@formatjs/ts-transformer@2.13.0': resolution: {integrity: sha512-mu7sHXZk1NWZrQ3eUqugpSYo8x5/tXkrI4uIbFqCEC0eNgQaIcoKgVeDFgDAcgG+cEme2atAUYSFF+DFWC4org==} peerDependencies: @@ -1381,54 +1437,111 @@ packages: resolution: {integrity: sha512-SCF2UPT+mDrfO3DDeUb7eTRqHycBEx4aJ8vACm17Nyn2b2ueaLlS5u/2V42SSZF/F6LydI7R78fv3xPW7HHdWw==} engines: {node: '>=18.0.0'} + '@next/env@13.5.11': + resolution: {integrity: sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==} + '@next/env@15.3.4': resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==} '@next/eslint-plugin-next@14.2.30': resolution: {integrity: sha512-mvVsMIutMxQ4NGZEMZ1kiBNc+la8Xmlk30bKUmCPQz2eFkmsLv54Mha8QZarMaCtSPkkFA1TMD+FIZk0l/PpzA==} + '@next/swc-darwin-arm64@13.5.9': + resolution: {integrity: sha512-pVyd8/1y1l5atQRvOaLOvfbmRwefxLhqQOzYo/M7FQ5eaRwA1+wuCn7t39VwEgDd7Aw1+AIWwd+MURXUeXhwDw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-arm64@15.3.4': resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@next/swc-darwin-x64@13.5.9': + resolution: {integrity: sha512-DwdeJqP7v8wmoyTWPbPVodTwCybBZa02xjSJ6YQFIFZFZ7dFgrieKW4Eo0GoIcOJq5+JxkQyejmI+8zwDp3pwA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-darwin-x64@15.3.4': resolution: {integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@next/swc-linux-arm64-gnu@13.5.9': + resolution: {integrity: sha512-wdQsKsIsGSNdFojvjW3Ozrh8Q00+GqL3wTaMjDkQxVtRbAqfFBtrLPO0IuWChVUP2UeuQcHpVeUvu0YgOP00+g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-gnu@15.3.4': resolution: {integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@13.5.9': + resolution: {integrity: sha512-6VpS+bodQqzOeCwGxoimlRoosiWlSc0C224I7SQWJZoyJuT1ChNCo+45QQH+/GtbR/s7nhaUqmiHdzZC9TXnXA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@15.3.4': resolution: {integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-x64-gnu@13.5.9': + resolution: {integrity: sha512-XxG3yj61WDd28NA8gFASIR+2viQaYZEFQagEodhI/R49gXWnYhiflTeeEmCn7Vgnxa/OfK81h1gvhUZ66lozpw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-gnu@15.3.4': resolution: {integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@13.5.9': + resolution: {integrity: sha512-/dnscWqfO3+U8asd+Fc6dwL2l9AZDl7eKtPNKW8mKLh4Y4wOpjJiamhe8Dx+D+Oq0GYVjuW0WwjIxYWVozt2bA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@15.3.4': resolution: {integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-win32-arm64-msvc@13.5.9': + resolution: {integrity: sha512-T/iPnyurOK5a4HRUcxAlss8uzoEf5h9tkd+W2dSWAfzxv8WLKlUgbfk+DH43JY3Gc2xK5URLuXrxDZ2mGfk/jw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-arm64-msvc@15.3.4': resolution: {integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@next/swc-win32-ia32-msvc@13.5.9': + resolution: {integrity: sha512-BLiPKJomaPrTAb7ykjA0LPcuuNMLDVK177Z1xe0nAem33+9FIayU4k/OWrtSn9SAJW/U60+1hoey5z+KCHdRLQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@13.5.9': + resolution: {integrity: sha512-/72/dZfjXXNY/u+n8gqZDjI6rxKMpYsgBBYNZKWOQw0BpBF7WCnPflRy3ZtvQ2+IYI3ZH2bPyj7K+6a6wNk90Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@next/swc-win32-x64-msvc@15.3.4': resolution: {integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==} engines: {node: '>= 10'} @@ -2316,9 +2429,27 @@ packages: '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@swc/helpers@0.5.2': + resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} + + '@tanstack/query-core@4.40.0': + resolution: {integrity: sha512-7MJTtZkCSuehMC7IxMOCGsLvHS3jHx4WjveSrGsG1Nc1UQLjaFwwkpLA2LmPfvOAxnH4mszMOBFD6LlZE+aB+Q==} + '@tanstack/query-core@5.80.10': resolution: {integrity: sha512-mUNQOtzxkjL6jLbyChZoSBP6A5gQDVRUiPvW+/zw/9ftOAz+H754zCj3D8PwnzPKyHzGkQ9JbH48ukhym9LK1Q==} + '@tanstack/react-query@4.40.1': + resolution: {integrity: sha512-mgD07S5N8e5v81CArKDWrHE4LM7HxZ9k/KLeD3+NUD9WimGZgKIqojUZf/rXkfAMYZU9p0Chzj2jOXm7xpgHHQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + '@tanstack/react-query@5.80.10': resolution: {integrity: sha512-6zM098J8sLy9oU60XAdzUlAH4wVzoMVsWUWiiE/Iz4fd67PplxeyL4sw/MPcVJJVhbwGGXCsHn9GrQt2mlAzig==} peerDependencies: @@ -2547,8 +2678,8 @@ packages: resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@umami/react-zen@0.144.0': - resolution: {integrity: sha512-RpPid2hdXAzbWKlbQWdSCwm6GZMHVs1f6VsVumq7Who/B8wxX0RVBwehHn3/ERwDw+c6ThRzlAZvOO6QLv+tQw==} + '@umami/react-zen@0.145.0': + resolution: {integrity: sha512-bbbdgWDwW0MIXfSc3JJPDOcmATZpwDsbrDdj37LEvP6rdtUgwAarnATjJa9l3133LGhhEAdYitZqwrDfHHPI5g==} '@umami/redis-client@0.27.0': resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==} @@ -2988,12 +3119,12 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} - caniuse-lite@1.0.30001722: - resolution: {integrity: sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==} - caniuse-lite@1.0.30001726: resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -3424,8 +3555,8 @@ packages: resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} engines: {node: '>=10'} - decimal.js@10.5.0: - resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} dedent@1.6.0: resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} @@ -4055,6 +4186,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} @@ -4291,6 +4425,9 @@ packages: intl-messageformat@10.7.7: resolution: {integrity: sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==} + intl-messageformat@9.13.0: + resolution: {integrity: sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==} + ipaddr.js@2.2.0: resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} engines: {node: '>= 10'} @@ -5082,6 +5219,12 @@ packages: resolution: {integrity: sha512-DXO4L9W+08T+A7h5+xdT32l7IMot8z7WOH+7C1Maol571PnktQ8un7Ni4CyPFp4H+vht/FDA5/tpjRvWMFQDMw==} engines: {node: '>=10', npm: '>=6'} + moment-timezone@0.5.48: + resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -5101,6 +5244,28 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-basics@0.36.0: + resolution: {integrity: sha512-Nwou8pCjFuoD/ZxUw9iKC7hhZeWbo/ng0ze74yck3W89MNc/CepwCDziflAHY5XcmIVNmpXOCu9OfmzTdVRPWQ==} + peerDependencies: + next: ^13.4.0 + react: ^18.2.0 + react-dom: ^18.2.0 + + next@13.5.11: + resolution: {integrity: sha512-WUPJ6WbAX9tdC86kGTu92qkrRdgRqVrY++nwM+shmWQwmyxt4zhZfR59moXSI4N8GDYCBY3lIAqhzjDd4rTC8Q==} + engines: {node: '>=16.14.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + next@15.3.4: resolution: {integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -5936,6 +6101,11 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -5952,8 +6122,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-hook-form@7.58.1: - resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==} + react-hook-form@7.60.0: + resolution: {integrity: sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -5963,6 +6133,15 @@ packages: peerDependencies: react: '*' + react-intl@5.25.1: + resolution: {integrity: sha512-pkjdQDvpJROoXLMltkP/5mZb0/XqrqLoPGKUCfbdkP8m6U9xbK40K51Wu+a4aQqTEvEK5lHBk0fWzUV72SJ3Hg==} + peerDependencies: + react: ^16.3.0 || 17 || 18 + typescript: ^4.5 + peerDependenciesMeta: + typescript: + optional: true + react-intl@6.8.9: resolution: {integrity: sha512-TUfj5E7lyUDvz/GtovC9OMh441kBr08rtIbgh3p0R8iF3hVY+V2W9Am7rb8BpJ/29BH1utJOqOOhmvEVh3GfZg==} peerDependencies: @@ -6018,6 +6197,10 @@ 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@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -6204,6 +6387,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -6463,6 +6649,19 @@ packages: style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -6839,6 +7038,10 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + watchpack@2.4.0: + resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + engines: {node: '>=10.13.0'} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -6954,6 +7157,21 @@ packages: zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zustand@5.0.5: resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==} engines: {node: '>=12.20.0'} @@ -7624,7 +7842,11 @@ snapshots: dependencies: '@formatjs/fast-memoize': 2.2.7 '@formatjs/intl-localematcher': 0.6.1 - decimal.js: 10.5.0 + decimal.js: 10.6.0 + tslib: 2.8.1 + + '@formatjs/fast-memoize@1.2.1': + dependencies: tslib: 2.8.1 '@formatjs/fast-memoize@2.2.3': @@ -7668,12 +7890,24 @@ snapshots: '@formatjs/ecma402-abstract': 2.2.4 tslib: 2.8.1 + '@formatjs/intl-displaynames@5.4.3': + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/intl-localematcher': 0.2.25 + tslib: 2.8.1 + '@formatjs/intl-displaynames@6.8.5': dependencies: '@formatjs/ecma402-abstract': 2.2.4 '@formatjs/intl-localematcher': 0.5.8 tslib: 2.8.1 + '@formatjs/intl-listformat@6.5.3': + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/intl-localematcher': 0.2.25 + tslib: 2.8.1 + '@formatjs/intl-listformat@7.7.5': dependencies: '@formatjs/ecma402-abstract': 2.2.4 @@ -7709,6 +7943,18 @@ snapshots: optionalDependencies: typescript: 5.8.3 + '@formatjs/intl@2.2.1(typescript@4.9.5)': + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/fast-memoize': 1.2.1 + '@formatjs/icu-messageformat-parser': 2.1.0 + '@formatjs/intl-displaynames': 5.4.3 + '@formatjs/intl-listformat': 6.5.3 + intl-messageformat: 9.13.0 + tslib: 2.8.1 + optionalDependencies: + typescript: 4.9.5 + '@formatjs/ts-transformer@2.13.0(ts-jest@29.4.0(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3))': dependencies: intl-messageformat-parser: 6.1.2 @@ -8070,33 +8316,62 @@ snapshots: '@netlify/plugin-nextjs@5.11.3': {} + '@next/env@13.5.11': {} + '@next/env@15.3.4': {} '@next/eslint-plugin-next@14.2.30': dependencies: glob: 10.3.10 + '@next/swc-darwin-arm64@13.5.9': + optional: true + '@next/swc-darwin-arm64@15.3.4': optional: true + '@next/swc-darwin-x64@13.5.9': + optional: true + '@next/swc-darwin-x64@15.3.4': optional: true + '@next/swc-linux-arm64-gnu@13.5.9': + optional: true + '@next/swc-linux-arm64-gnu@15.3.4': optional: true + '@next/swc-linux-arm64-musl@13.5.9': + optional: true + '@next/swc-linux-arm64-musl@15.3.4': optional: true + '@next/swc-linux-x64-gnu@13.5.9': + optional: true + '@next/swc-linux-x64-gnu@15.3.4': optional: true + '@next/swc-linux-x64-musl@13.5.9': + optional: true + '@next/swc-linux-x64-musl@15.3.4': optional: true + '@next/swc-win32-arm64-msvc@13.5.9': + optional: true + '@next/swc-win32-arm64-msvc@15.3.4': optional: true + '@next/swc-win32-ia32-msvc@13.5.9': + optional: true + + '@next/swc-win32-x64-msvc@13.5.9': + optional: true + '@next/swc-win32-x64-msvc@15.3.4': optional: true @@ -9453,8 +9728,22 @@ snapshots: dependencies: tslib: 2.8.1 + '@swc/helpers@0.5.2': + dependencies: + tslib: 2.8.1 + + '@tanstack/query-core@4.40.0': {} + '@tanstack/query-core@5.80.10': {} + '@tanstack/react-query@4.40.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-core': 4.40.0 + react: 18.3.1 + use-sync-external-store: 1.5.0(react@18.3.1) + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + '@tanstack/react-query@5.80.10(react@19.1.0)': dependencies: '@tanstack/query-core': 5.80.10 @@ -9736,7 +10025,7 @@ snapshots: '@typescript-eslint/types': 8.34.1 eslint-visitor-keys: 4.2.1 - '@umami/react-zen@0.144.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': + '@umami/react-zen@0.145.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.6 '@internationalized/date': 3.8.2 @@ -9750,7 +10039,7 @@ snapshots: react: 19.1.0 react-aria-components: 1.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dom: 19.1.0(react@19.1.0) - react-hook-form: 7.58.1(react@19.1.0) + react-hook-form: 7.60.0(react@19.1.0) react-icons: 5.5.0(react@19.1.0) thenby: 1.3.4 zustand: 5.0.5(@types/react@19.1.8)(immer@9.0.21)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) @@ -10245,16 +10534,16 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.24.5 - caniuse-lite: 1.0.30001722 + caniuse-lite: 1.0.30001727 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 caniuse-lite@1.0.30001718: {} - caniuse-lite@1.0.30001722: {} - caniuse-lite@1.0.30001726: {} + caniuse-lite@1.0.30001727: {} + caseless@0.12.0: {} chalk@2.4.2: @@ -10726,7 +11015,7 @@ snapshots: decamelize@5.0.1: {} - decimal.js@10.5.0: {} + decimal.js@10.6.0: {} dedent@1.6.0: {} @@ -11559,6 +11848,8 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} + glob@10.3.10: dependencies: foreground-child: 3.3.1 @@ -11788,6 +12079,13 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.9.4 tslib: 2.8.1 + intl-messageformat@9.13.0: + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/fast-memoize': 1.2.1 + '@formatjs/icu-messageformat-parser': 2.1.0 + tslib: 2.8.1 + ipaddr.js@2.2.0: {} is-array-buffer@3.0.5: @@ -12764,6 +13062,12 @@ snapshots: mmdb-lib@2.2.1: {} + moment-timezone@0.5.48: + dependencies: + moment: 2.30.1 + + moment@2.30.1: {} + ms@2.1.2: {} ms@2.1.3: {} @@ -12774,6 +13078,40 @@ snapshots: natural-compare@1.4.0: {} + next-basics@0.36.0(next@13.5.11(@babel/core@7.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + bcryptjs: 2.4.3 + jsonwebtoken: 9.0.2 + next: 13.5.11(@babel/core@7.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + pure-rand: 6.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next@13.5.11(@babel/core@7.27.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 13.5.11 + '@swc/helpers': 0.5.2 + busboy: 1.6.0 + caniuse-lite: 1.0.30001727 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.27.1)(react@18.3.1) + watchpack: 2.4.0 + optionalDependencies: + '@next/swc-darwin-arm64': 13.5.9 + '@next/swc-darwin-x64': 13.5.9 + '@next/swc-linux-arm64-gnu': 13.5.9 + '@next/swc-linux-arm64-musl': 13.5.9 + '@next/swc-linux-x64-gnu': 13.5.9 + '@next/swc-linux-x64-musl': 13.5.9 + '@next/swc-win32-arm64-msvc': 13.5.9 + '@next/swc-win32-ia32-msvc': 13.5.9 + '@next/swc-win32-x64-msvc': 13.5.9 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@15.3.4(@babel/core@7.27.1)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.3.4 @@ -13659,6 +13997,12 @@ snapshots: react-hook-form: 7.56.4(react@19.1.0) react-window: 1.8.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -13673,7 +14017,7 @@ snapshots: dependencies: react: 19.1.0 - react-hook-form@7.58.1(react@19.1.0): + react-hook-form@7.60.0(react@19.1.0): dependencies: react: 19.1.0 @@ -13681,6 +14025,22 @@ snapshots: dependencies: react: 19.1.0 + react-intl@5.25.1(react@18.3.1)(typescript@4.9.5): + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/icu-messageformat-parser': 2.1.0 + '@formatjs/intl': 2.2.1(typescript@4.9.5) + '@formatjs/intl-displaynames': 5.4.3 + '@formatjs/intl-listformat': 6.5.3 + '@types/hoist-non-react-statics': 3.3.6 + '@types/react': 18.3.23 + hoist-non-react-statics: 3.3.2 + intl-messageformat: 9.13.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + typescript: 4.9.5 + react-intl@6.8.9(react@19.1.0)(typescript@5.8.3): dependencies: '@formatjs/ecma402-abstract': 2.2.4 @@ -13763,6 +14123,10 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + react@19.1.0: {} read-babelrc-up@1.1.0: @@ -13996,6 +14360,10 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scheduler@0.26.0: {} schema-utils@2.7.1: @@ -14314,6 +14682,13 @@ snapshots: style-search@0.1.0: {} + styled-jsx@5.1.1(@babel/core@7.27.1)(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + '@babel/core': 7.27.1 + styled-jsx@5.1.6(@babel/core@7.27.1)(react@19.1.0): dependencies: client-only: 0.0.1 @@ -14695,6 +15070,10 @@ snapshots: dependencies: react: 19.1.0 + use-sync-external-store@1.5.0(react@18.3.1): + dependencies: + react: 18.3.1 + use-sync-external-store@1.5.0(react@19.1.0): dependencies: react: 19.1.0 @@ -14740,6 +15119,11 @@ snapshots: dependencies: makeerror: 1.0.12 + watchpack@2.4.0: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + web-streams-polyfill@3.3.3: {} which-boxed-primitive@1.1.1: @@ -14881,6 +15265,14 @@ snapshots: zod@3.25.67: {} + zustand@4.5.7(@types/react@19.1.8)(immer@9.0.21)(react@18.3.1): + dependencies: + use-sync-external-store: 1.5.0(react@18.3.1) + optionalDependencies: + '@types/react': 19.1.8 + immer: 9.0.21 + react: 18.3.1 + zustand@5.0.5(@types/react@19.1.8)(immer@9.0.21)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): optionalDependencies: '@types/react': 19.1.8 diff --git a/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx b/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx index 31134388..7f680a91 100644 --- a/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx +++ b/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx @@ -1,5 +1,5 @@ 'use client'; -import { TeamDetails } from '@/app/(main)/teams/[teamId]/settings/team/TeamDetails'; +import { TeamDetails } from '@/app/(main)/settings/teams/[teamId]/TeamDetails'; import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider'; export function AdminTeamPage({ teamId }: { teamId: string }) { diff --git a/src/app/(main)/admin/users/[userId]/UserProvider.tsx b/src/app/(main)/admin/users/[userId]/UserProvider.tsx index 22b1b955..39df7cfc 100644 --- a/src/app/(main)/admin/users/[userId]/UserProvider.tsx +++ b/src/app/(main)/admin/users/[userId]/UserProvider.tsx @@ -1,22 +1,19 @@ -import { createContext, ReactNode, useEffect } from 'react'; +import { createContext, ReactNode } from 'react'; import { Loading } from '@umami/react-zen'; -import { useModified, useUserQuery } from '@/components/hooks'; +import { useUserQuery } from '@/components/hooks'; export const UserContext = createContext(null); export function UserProvider({ userId, children }: { userId: string; children: ReactNode }) { - const { modified } = useModified(`user:${userId}`); - const { data: user, isFetching, isLoading, refetch } = useUserQuery(userId); - - useEffect(() => { - if (modified) { - refetch(); - } - }, [modified]); + const { data: user, isFetching, isLoading } = useUserQuery(userId); if (isFetching && isLoading) { return ; } - return {children}; + if (!user) { + return null; + } + + return {children}; } diff --git a/src/app/(main)/settings/SettingsLayout.tsx b/src/app/(main)/settings/SettingsLayout.tsx index 4110820e..cfc901d4 100644 --- a/src/app/(main)/settings/SettingsLayout.tsx +++ b/src/app/(main)/settings/SettingsLayout.tsx @@ -13,6 +13,11 @@ export function SettingsLayout({ children }: { children: ReactNode }) { const { pathname } = useNavigation(); const items = [ + { + id: 'preferences', + label: formatMessage(labels.preferences), + url: '/settings/preferences', + }, { id: 'profile', label: formatMessage(labels.profile), diff --git a/src/app/(main)/settings/profile/DateRangeSetting.tsx b/src/app/(main)/settings/preferences/DateRangeSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/DateRangeSetting.tsx rename to src/app/(main)/settings/preferences/DateRangeSetting.tsx diff --git a/src/app/(main)/settings/profile/LanguageSetting.tsx b/src/app/(main)/settings/preferences/LanguageSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/LanguageSetting.tsx rename to src/app/(main)/settings/preferences/LanguageSetting.tsx diff --git a/src/app/(main)/settings/preferences/PreferenceSettings.tsx b/src/app/(main)/settings/preferences/PreferenceSettings.tsx new file mode 100644 index 00000000..c956ebad --- /dev/null +++ b/src/app/(main)/settings/preferences/PreferenceSettings.tsx @@ -0,0 +1,39 @@ +import { Column, Label } from '@umami/react-zen'; +import { useLoginQuery, useMessages } from '@/components/hooks'; +import { TimezoneSetting } from './TimezoneSetting'; +import { DateRangeSetting } from './DateRangeSetting'; +import { LanguageSetting } from './LanguageSetting'; +import { ThemeSetting } from './ThemeSetting'; + +export function PreferenceSettings() { + const { user } = useLoginQuery(); + const { formatMessage, labels } = useMessages(); + + if (!user) { + return null; + } + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/(main)/settings/preferences/PreferencesPage.tsx b/src/app/(main)/settings/preferences/PreferencesPage.tsx new file mode 100644 index 00000000..62d7f882 --- /dev/null +++ b/src/app/(main)/settings/preferences/PreferencesPage.tsx @@ -0,0 +1,16 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import { useMessages } from '@/components/hooks'; +import { SectionHeader } from '@/components/common/SectionHeader'; +import { PreferenceSettings } from './PreferenceSettings'; + +export function PreferencesPage() { + const { formatMessage, labels } = useMessages(); + + return ( + + + + + ); +} diff --git a/src/app/(main)/settings/profile/ThemeSetting.tsx b/src/app/(main)/settings/preferences/ThemeSetting.tsx similarity index 61% rename from src/app/(main)/settings/profile/ThemeSetting.tsx rename to src/app/(main)/settings/preferences/ThemeSetting.tsx index bc9b90b6..d0c4fb28 100644 --- a/src/app/(main)/settings/profile/ThemeSetting.tsx +++ b/src/app/(main)/settings/preferences/ThemeSetting.tsx @@ -6,15 +6,12 @@ export function ThemeSetting() { return ( - - - {({ close }) => } + {({ close }) => ( + + + + )} diff --git a/src/app/(main)/settings/profile/ProfileSettings.tsx b/src/app/(main)/settings/profile/ProfileSettings.tsx index 40471c75..72ae7d91 100644 --- a/src/app/(main)/settings/profile/ProfileSettings.tsx +++ b/src/app/(main)/settings/profile/ProfileSettings.tsx @@ -1,10 +1,6 @@ import { Row, Column, Label } from '@umami/react-zen'; import { useLoginQuery, useMessages } from '@/components/hooks'; import { ROLES } from '@/lib/constants'; -import { TimezoneSetting } from './TimezoneSetting'; -import { DateRangeSetting } from './DateRangeSetting'; -import { LanguageSetting } from './LanguageSetting'; -import { ThemeSetting } from './ThemeSetting'; import { PasswordChangeButton } from './PasswordChangeButton'; export function ProfileSettings() { @@ -52,26 +48,6 @@ export function ProfileSettings() { )} - - - - - - - - - - - - - - - - - - - - ); } diff --git a/src/app/(main)/settings/teams/TeamAddForm.tsx b/src/app/(main)/settings/teams/TeamAddForm.tsx index 80fd0c62..2c1746f7 100644 --- a/src/app/(main)/settings/teams/TeamAddForm.tsx +++ b/src/app/(main)/settings/teams/TeamAddForm.tsx @@ -25,7 +25,7 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: }; return ( -
+ diff --git a/src/app/(main)/settings/teams/TeamJoinForm.tsx b/src/app/(main)/settings/teams/TeamJoinForm.tsx index 91ec5aaf..047b28ad 100644 --- a/src/app/(main)/settings/teams/TeamJoinForm.tsx +++ b/src/app/(main)/settings/teams/TeamJoinForm.tsx @@ -25,7 +25,7 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: }; return ( - + children}> + {({ data }) => { return ; }} diff --git a/src/app/(main)/settings/teams/TeamsTable.tsx b/src/app/(main)/settings/teams/TeamsTable.tsx index 39870ec8..bfe6b332 100644 --- a/src/app/(main)/settings/teams/TeamsTable.tsx +++ b/src/app/(main)/settings/teams/TeamsTable.tsx @@ -1,8 +1,9 @@ import { DataColumn, DataTable, Icon, MenuItem, Text, Row } from '@umami/react-zen'; import { useMessages } from '@/components/hooks'; -import { Arrow, Edit } from '@/components/icons'; +import { Eye, Edit } from '@/components/icons'; import { ROLES } from '@/lib/constants'; import { MenuButton } from '@/components/input/MenuButton'; +import Link from 'next/link'; export function TeamsTable({ data = [], @@ -16,7 +17,9 @@ export function TeamsTable({ return ( - + + {(row: any) => {row.name}} + {(row: any) => row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username} @@ -36,12 +39,12 @@ export function TeamsTable({ - + {formatMessage(labels.view)} - + diff --git a/src/app/(main)/teams/[teamId]/settings/team/TeamDeleteForm.tsx b/src/app/(main)/settings/teams/[teamId]/TeamDeleteForm.tsx similarity index 100% rename from src/app/(main)/teams/[teamId]/settings/team/TeamDeleteForm.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamDeleteForm.tsx diff --git a/src/app/(main)/teams/[teamId]/settings/team/TeamDetails.tsx b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx similarity index 88% rename from src/app/(main)/teams/[teamId]/settings/team/TeamDetails.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx index d7782956..635aba55 100644 --- a/src/app/(main)/teams/[teamId]/settings/team/TeamDetails.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx @@ -8,8 +8,8 @@ import { Users } from '@/components/icons'; import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton'; import { TeamManage } from './TeamManage'; import { TeamEditForm } from './TeamEditForm'; -import { TeamWebsitesDataTable } from '@/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable'; -import { TeamMembersDataTable } from '@/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable'; +import { TeamWebsitesDataTable } from './TeamWebsitesDataTable'; +import { TeamMembersDataTable } from './TeamMembersDataTable'; export function TeamDetails({ teamId }: { teamId: string }) { const team = useContext(TeamContext); @@ -22,10 +22,12 @@ export function TeamDetails({ teamId }: { teamId: string }) { user.role !== ROLES.viewOnly; const canEdit = - !!team?.teamUser?.find( + user.isAdmin || + (!!team?.teamUser?.find( ({ userId, role }) => (role === ROLES.teamOwner || role === ROLES.teamManager) && userId === user.id, - ) && user.role !== ROLES.viewOnly; + ) && + user.role !== ROLES.viewOnly); return ( diff --git a/src/app/(main)/teams/[teamId]/settings/team/TeamEditForm.tsx b/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx similarity index 69% rename from src/app/(main)/teams/[teamId]/settings/team/TeamEditForm.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx index fad603bb..4fde3fd3 100644 --- a/src/app/(main)/teams/[teamId]/settings/team/TeamEditForm.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamEditForm.tsx @@ -6,7 +6,6 @@ import { TextField, Button, useToast, - Text, } from '@umami/react-zen'; import { getRandomChars } from '@/lib/crypto'; import { useContext } from 'react'; @@ -15,28 +14,38 @@ import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; const generateId = () => `team_${getRandomChars(16)}`; -export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?: boolean }) { +export function TeamEditForm({ + teamId, + allowEdit, + onSave, +}: { + teamId: string; + allowEdit?: boolean; + onSave?: () => void; +}) { const team = useContext(TeamContext); const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); + const { toast } = useToast(); + const { touch } = useModified(); + const { mutate, error } = useMutation({ mutationFn: (data: any) => post(`/teams/${teamId}`, data), }); - const { toast } = useToast(); - const { touch } = useModified(); - const cloudMode = !!process.env.cloudMode; const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { touch('teams'); + touch(`teams:${teamId}`); toast(formatMessage(messages.saved)); + onSave?.(); }, }); }; return ( - + {({ setValue }) => { return ( <> @@ -48,22 +57,16 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit? label={formatMessage(labels.name)} rules={{ required: formatMessage(labels.required) }} > - {allowEdit ? : {team?.name}} + + + + - {!cloudMode && allowEdit && ( - - - - )} {allowEdit && ( - {allowEdit && ( - - )} + {formatMessage(labels.save)} )} diff --git a/src/app/(main)/teams/[teamId]/settings/team/TeamManage.tsx b/src/app/(main)/settings/teams/[teamId]/TeamManage.tsx similarity index 100% rename from src/app/(main)/teams/[teamId]/settings/team/TeamManage.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamManage.tsx diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMemberEditButton.tsx similarity index 83% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMemberEditButton.tsx index 62279bdd..308e3439 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditButton.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMemberEditButton.tsx @@ -1,14 +1,5 @@ import { useMessages, useModified } from '@/components/hooks'; -import { - Row, - Pressable, - Icon, - Modal, - DialogTrigger, - Dialog, - Text, - useToast, -} from '@umami/react-zen'; +import { Row, Button, Icon, Modal, DialogTrigger, Dialog, useToast } from '@umami/react-zen'; import { TeamMemberEditForm } from './TeamMemberEditForm'; import { Edit } from '@/components/icons'; @@ -28,21 +19,20 @@ export function TeamMemberEditButton({ const { touch } = useModified(); const handleSave = () => { - toast(formatMessage(messages.saved)); touch('teams:members'); + toast(formatMessage(messages.saved)); onSave?.(); }; return ( - + {({ close }) => ( diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditForm.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMemberEditForm.tsx similarity index 94% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditForm.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMemberEditForm.tsx index c5501221..72618f83 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberEditForm.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMemberEditForm.tsx @@ -39,7 +39,7 @@ export function TeamMemberEditForm({ }; return ( - + - - {formatMessage(labels.save)} - + + {formatMessage(labels.save)} + ); diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberRemoveButton.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx similarity index 87% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMemberRemoveButton.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx index 508ddc6e..ae25c8be 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMemberRemoveButton.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMemberRemoveButton.tsx @@ -1,8 +1,8 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm'; import { useApi, useMessages, useModified } from '@/components/hooks'; import { messages } from '@/components/messages'; -import { Close } from '@/components/icons'; -import { Button, Icon, Modal, DialogTrigger, Dialog, Text } from '@umami/react-zen'; +import { Trash } from '@/components/icons'; +import { Button, Icon, Modal, DialogTrigger, Dialog } from '@umami/react-zen'; export function TeamMemberRemoveButton({ teamId, @@ -35,11 +35,10 @@ export function TeamMemberRemoveButton({ return ( - @@ -53,6 +52,7 @@ export function TeamMemberRemoveButton({ onConfirm={handleConfirm.bind(null, close)} onClose={close} buttonLabel={formatMessage(labels.remove)} + buttonVariant="danger" /> )} diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersDataTable.tsx similarity index 90% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMembersDataTable.tsx index 25ccbab3..d5f99310 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersDataTable.tsx @@ -12,7 +12,7 @@ export function TeamMembersDataTable({ const queryResult = useTeamMembersQuery(teamId); return ( - + {({ data }) => } ); diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersPage.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx similarity index 100% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMembersPage.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx diff --git a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx similarity index 50% rename from src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx index 34f3ac3b..f84f58a9 100644 --- a/src/app/(main)/teams/[teamId]/settings/members/TeamMembersTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx @@ -1,9 +1,8 @@ -import { DataColumn, DataTable, MenuItem } from '@umami/react-zen'; -import { useMessages, useLoginQuery } from '@/components/hooks'; +import { DataColumn, DataTable, Row } from '@umami/react-zen'; +import { useMessages } from '@/components/hooks'; import { ROLES } from '@/lib/constants'; import { TeamMemberRemoveButton } from './TeamMemberRemoveButton'; import { TeamMemberEditButton } from './TeamMemberEditButton'; -import { MenuButton } from '@/components/input/MenuButton'; export function TeamMembersTable({ data = [], @@ -15,7 +14,6 @@ export function TeamMembersTable({ allowEdit: boolean; }) { const { formatMessage, labels } = useMessages(); - const { user } = useLoginQuery(); const roles = { [ROLES.teamOwner]: formatMessage(labels.teamOwner), @@ -32,28 +30,22 @@ export function TeamMembersTable({ {(row: any) => roles[row?.role]} - - {(row: any) => { - return ( - allowEdit && - row?.role !== ROLES.teamOwner && - user?.id !== row?.user?.id && ( - - - - - - - - - ) - ); - }} - + {allowEdit && ( + + {(row: any) => { + return ( + + + + + ); + }} + + )} ); } diff --git a/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx b/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx new file mode 100644 index 00000000..88ec9b40 --- /dev/null +++ b/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx @@ -0,0 +1,11 @@ +'use client'; +import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider'; +import { TeamDetails } from '@/app/(main)/settings/teams/[teamId]/TeamDetails'; + +export function TeamSettingsPage({ teamId }: { teamId: string }) { + return ( + + + + ); +} diff --git a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsiteRemoveButton.tsx b/src/app/(main)/settings/teams/[teamId]/TeamWebsiteRemoveButton.tsx similarity index 100% rename from src/app/(main)/teams/[teamId]/settings/websites/TeamWebsiteRemoveButton.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamWebsiteRemoveButton.tsx diff --git a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamWebsitesDataTable.tsx similarity index 91% rename from src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamWebsitesDataTable.tsx index 1cb65eac..6a2e4f45 100644 --- a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamWebsitesDataTable.tsx @@ -12,7 +12,7 @@ export function TeamWebsitesDataTable({ const queryResult = useTeamWebsitesQuery(teamId); return ( - + {({ data }) => } ); diff --git a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamWebsitesTable.tsx similarity index 85% rename from src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx rename to src/app/(main)/settings/teams/[teamId]/TeamWebsitesTable.tsx index 59926335..f04f1f85 100644 --- a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/TeamWebsitesTable.tsx @@ -1,7 +1,8 @@ import { DataColumn, DataTable, Icon, MenuItem, Text, Row } from '@umami/react-zen'; import { useLoginQuery, useMessages } from '@/components/hooks'; -import { Arrow, Edit } from '@/components/icons'; +import { Eye, Edit } from '@/components/icons'; import { MenuButton } from '@/components/input/MenuButton'; +import Link from 'next/link'; export function TeamWebsitesTable({ teamId, @@ -17,7 +18,9 @@ export function TeamWebsitesTable({ return ( - + + {(row: any) => {row.name}} + {(row: any) => row?.createUser?.username} @@ -31,7 +34,7 @@ export function TeamWebsitesTable({ - + {formatMessage(labels.view)} diff --git a/src/app/(main)/teams/[teamId]/settings/team/page.tsx b/src/app/(main)/settings/teams/[teamId]/page.tsx similarity index 62% rename from src/app/(main)/teams/[teamId]/settings/team/page.tsx rename to src/app/(main)/settings/teams/[teamId]/page.tsx index 4767b1c4..5a9aaacb 100644 --- a/src/app/(main)/teams/[teamId]/settings/team/page.tsx +++ b/src/app/(main)/settings/teams/[teamId]/page.tsx @@ -1,12 +1,12 @@ import { Metadata } from 'next'; -import { TeamPage } from './TeamPage'; +import { TeamSettingsPage } from './TeamSettingsPage'; export default async function ({ params }: { params: Promise<{ teamId: string }> }) { const { teamId } = await params; - return ; + return ; } export const metadata: Metadata = { - title: 'Teams Details', + title: 'Teams', }; diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.tsx b/src/app/(main)/settings/websites/WebsiteAddButton.tsx index 8996c5b9..74852a14 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -1,5 +1,14 @@ import { useMessages, useModified } from '@/components/hooks'; -import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen'; +import { + Button, + Icon, + Modal, + Dialog, + DialogTrigger, + Text, + Column, + useToast, +} from '@umami/react-zen'; import { Plus } from '@/components/icons'; import { WebsiteAddForm } from './WebsiteAddForm'; @@ -24,7 +33,11 @@ export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: - {({ close }) => } + {({ close }) => ( + + + + )} diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx index f4cc7ebf..2d285c75 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -6,7 +6,7 @@ import { Eye, SquarePen } from '@/components/icons'; import Link from 'next/link'; export interface WebsitesTableProps { - data: any[]; + data: Record[]; showActions?: boolean; allowEdit?: boolean; allowView?: boolean; @@ -22,7 +22,8 @@ export function WebsitesTable({ children, }: WebsitesTableProps) { const { formatMessage, labels } = useMessages(); - const { renderUrl } = useNavigation(); + const { renderUrl, pathname } = useNavigation(); + const isSettings = pathname.includes('/settings'); if (!data?.length) { return children; @@ -31,7 +32,11 @@ export function WebsitesTable({ return ( - {(row: any) => {row.name}} + {(row: any) => ( + + {row.name} + + )} {showActions && ( @@ -41,7 +46,7 @@ export function WebsitesTable({ return ( - {allowEdit && ( + {allowView && ( @@ -51,7 +56,7 @@ export function WebsitesTable({ )} - {allowView && ( + {allowEdit && ( diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteEditForm.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteEditForm.tsx index 8e9c6d7c..94197748 100644 --- a/src/app/(main)/settings/websites/[websiteId]/WebsiteEditForm.tsx +++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteEditForm.tsx @@ -33,7 +33,7 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa }; return ( -
+ diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteSettings.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteSettings.tsx index a7d19745..e36cf0ae 100644 --- a/src/app/(main)/settings/websites/[websiteId]/WebsiteSettings.tsx +++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteSettings.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { Icon, Tabs, TabList, Tab, TabPanel, Text } from '@umami/react-zen'; import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider'; import { useMessages } from '@/components/hooks'; -import { Globe, Arrow } from '@/components/icons'; +import { Globe, Eye } from '@/components/icons'; import { SectionHeader } from '@/components/common/SectionHeader'; import { WebsiteShareForm } from './WebsiteShareForm'; import { WebsiteTrackingCode } from './WebsiteTrackingCode'; @@ -25,7 +25,7 @@ export function WebsiteSettings({ }> - + {formatMessage(labels.view)} @@ -35,7 +35,7 @@ export function WebsiteSettings({ {formatMessage(labels.details)} {formatMessage(labels.trackingCode)} {formatMessage(labels.shareUrl)} - {formatMessage(labels.data)} + {formatMessage(labels.manage)} @@ -44,9 +44,9 @@ export function WebsiteSettings({ - + - + diff --git a/src/app/(main)/teams/[teamId]/TeamProvider.tsx b/src/app/(main)/teams/[teamId]/TeamProvider.tsx index 178b73d6..a0161ff0 100644 --- a/src/app/(main)/teams/[teamId]/TeamProvider.tsx +++ b/src/app/(main)/teams/[teamId]/TeamProvider.tsx @@ -1,25 +1,18 @@ 'use client'; -import { createContext, ReactNode, useEffect } from 'react'; -import { useTeamQuery, useModified } from '@/components/hooks'; +import { createContext, ReactNode } from 'react'; +import { useTeamQuery } from '@/components/hooks'; import { Loading } from '@umami/react-zen'; export const TeamContext = createContext(null); export function TeamProvider({ teamId, children }: { teamId?: string; children: ReactNode }) { - const { modified } = useModified(`teams`); - const { data: team, isLoading, isFetching, refetch } = useTeamQuery(teamId); - - useEffect(() => { - if (teamId && modified) { - refetch(); - } - }, [teamId, modified]); + const { data: team, isLoading, isFetching } = useTeamQuery(teamId); if (isFetching && isLoading) { return ; } - if (teamId && !team) { + if (!team) { return null; } diff --git a/src/app/(main)/teams/[teamId]/layout.tsx b/src/app/(main)/teams/[teamId]/layout.tsx index 11814eca..1dfdd243 100644 --- a/src/app/(main)/teams/[teamId]/layout.tsx +++ b/src/app/(main)/teams/[teamId]/layout.tsx @@ -1,6 +1,5 @@ import { TeamProvider } from './TeamProvider'; import { Metadata } from 'next'; -import { TeamSettingsLayout } from './settings/TeamSettingsLayout'; export default async function ({ children, @@ -11,11 +10,7 @@ export default async function ({ }) { const { teamId } = await params; - return ( - - {children} - - ); + return {children}; } export const metadata: Metadata = { diff --git a/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx b/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx deleted file mode 100644 index 6e4c33cc..00000000 --- a/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; -import { ReactNode } from 'react'; -import { useMessages, useNavigation } from '@/components/hooks'; -import { Grid, Column } from '@umami/react-zen'; -import { SideMenu } from '@/components/common/SideMenu'; -import { Panel } from '@/components/common/Panel'; -import { PageHeader } from '@/components/common/PageHeader'; -import { PageBody } from '@/components/common/PageBody'; - -export function TeamSettingsLayout({ children }: { children: ReactNode }) { - const { formatMessage, labels } = useMessages(); - const { pathname, teamId } = useNavigation(); - - const items = [ - { - id: 'team', - label: formatMessage(labels.team), - url: `/teams/${teamId}/settings/team`, - }, - { - id: 'websites', - label: formatMessage(labels.websites), - url: `/teams/${teamId}/settings/websites`, - }, - { - id: 'members', - label: formatMessage(labels.members), - url: `/teams/${teamId}/settings/members`, - }, - ].filter(n => n); - - const value = items.find(({ url }) => pathname.includes(url))?.id; - - return ( - - - - - - - - - {children} - - - - - ); -} diff --git a/src/app/(main)/teams/[teamId]/settings/members/page.tsx b/src/app/(main)/teams/[teamId]/settings/members/page.tsx deleted file mode 100644 index f6957843..00000000 --- a/src/app/(main)/teams/[teamId]/settings/members/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Metadata } from 'next'; -import { TeamMembersPage } from './TeamMembersPage'; - -export default async function ({ params }: { params: Promise<{ teamId: string }> }) { - const { teamId } = await params; - - return ; -} - -export const metadata: Metadata = { - title: 'Team Members', -}; diff --git a/src/app/(main)/teams/[teamId]/settings/team/TeamPage.tsx b/src/app/(main)/teams/[teamId]/settings/team/TeamPage.tsx deleted file mode 100644 index a35e6d72..00000000 --- a/src/app/(main)/teams/[teamId]/settings/team/TeamPage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -'use client'; -import { TeamDetails } from './TeamDetails'; - -export function TeamPage({ teamId }: { teamId: string }) { - return ; -} diff --git a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesPage.tsx b/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesPage.tsx deleted file mode 100644 index d878b941..00000000 --- a/src/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesPage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; -import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider'; -import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton'; -import { useLoginQuery, useMessages } from '@/components/hooks'; -import { SectionHeader } from '@/components/common/SectionHeader'; -import { TeamWebsitesDataTable } from './TeamWebsitesDataTable'; -import { ROLES } from '@/lib/constants'; -import { useContext } from 'react'; -import { Column } from '@umami/react-zen'; - -export function TeamWebsitesPage({ teamId }: { teamId: string }) { - const team = useContext(TeamContext); - const { formatMessage, labels } = useMessages(); - const { user } = useLoginQuery(); - - const canEdit = - !!team?.teamUser?.find( - ({ userId, role }) => userId === user.id && role !== ROLES.teamViewOnly, - ) && user.role !== ROLES.viewOnly; - - return ( - - - {canEdit && } - - - - ); -} diff --git a/src/app/(main)/teams/[teamId]/settings/websites/[websiteId]/page.tsx b/src/app/(main)/teams/[teamId]/settings/websites/[websiteId]/page.tsx deleted file mode 100644 index a18f8a2e..00000000 --- a/src/app/(main)/teams/[teamId]/settings/websites/[websiteId]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import Page from '@/app/(main)/settings/websites/[websiteId]/page'; - -export default function ({ params }) { - return ; -} diff --git a/src/app/(main)/teams/[teamId]/settings/websites/page.tsx b/src/app/(main)/teams/[teamId]/settings/websites/page.tsx deleted file mode 100644 index e34c35d5..00000000 --- a/src/app/(main)/teams/[teamId]/settings/websites/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { TeamWebsitesPage } from './TeamWebsitesPage'; -import { Metadata } from 'next'; - -export default async function ({ params }: { params: Promise<{ teamId: string }> }) { - const { teamId } = await params; - - return ; -} - -export const metadata: Metadata = { - title: 'Teams Websites', -}; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx b/src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx index ea0a8bbe..16aa55fb 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx @@ -1,6 +1,6 @@ 'use client'; -import { createContext, ReactNode, useEffect } from 'react'; -import { useModified, useWebsiteQuery } from '@/components/hooks'; +import { createContext, ReactNode } from 'react'; +import { useWebsiteQuery } from '@/components/hooks'; import { Loading } from '@umami/react-zen'; import { Website } from '@/generated/prisma/client'; @@ -13,18 +13,15 @@ export function WebsiteProvider({ websiteId: string; children: ReactNode; }) { - const { modified } = useModified(`website:${websiteId}`); - const { data: website, isFetching, isLoading, refetch } = useWebsiteQuery(websiteId); - - useEffect(() => { - if (modified) { - refetch(); - } - }, [modified]); + const { data: website, isFetching, isLoading } = useWebsiteQuery(websiteId); if (isFetching && isLoading) { return ; } + if (!website) { + return null; + } + return {children}; } diff --git a/src/app/api/teams/[teamId]/users/route.ts b/src/app/api/teams/[teamId]/users/route.ts index 376351ea..cf1b4b9b 100644 --- a/src/app/api/teams/[teamId]/users/route.ts +++ b/src/app/api/teams/[teamId]/users/route.ts @@ -1,13 +1,14 @@ import { z } from 'zod'; import { unauthorized, json, badRequest } from '@/lib/response'; import { canAddUserToTeam, canViewTeam } from '@/lib/auth'; -import { parseRequest } from '@/lib/request'; -import { pagingParams, teamRoleParam } from '@/lib/schema'; +import { getQueryFilters, parseRequest } from '@/lib/request'; +import { pagingParams, teamRoleParam, searchParams } from '@/lib/schema'; import { createTeamUser, getTeamUser, getTeamUsers } from '@/queries'; export async function GET(request: Request, { params }: { params: Promise<{ teamId: string }> }) { const schema = z.object({ ...pagingParams, + ...searchParams, }); const { auth, query, error } = await parseRequest(request, schema); @@ -22,6 +23,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ team return unauthorized('You must be the owner of this team.'); } + const filters = getQueryFilters(query); + const users = await getTeamUsers( { where: { @@ -39,7 +42,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team }, }, }, - query, + filters, ); return json(users); diff --git a/src/components/common/ConfirmationForm.tsx b/src/components/common/ConfirmationForm.tsx index 39861f61..38c8634d 100644 --- a/src/components/common/ConfirmationForm.tsx +++ b/src/components/common/ConfirmationForm.tsx @@ -25,10 +25,17 @@ export function ConfirmationForm({ return ( - {message} + + {message} + - + {buttonLabel || formatMessage(labels.ok)} diff --git a/src/components/hooks/queries/useTeamQuery.ts b/src/components/hooks/queries/useTeamQuery.ts index 563e16d5..f471172b 100644 --- a/src/components/hooks/queries/useTeamQuery.ts +++ b/src/components/hooks/queries/useTeamQuery.ts @@ -1,10 +1,17 @@ import { useApi } from '../useApi'; +import { useModified } from '@/components/hooks'; +import { keepPreviousData } from '@tanstack/react-query'; +import { ReactQueryOptions } from '@/lib/types'; -export function useTeamQuery(teamId: string) { +export function useTeamQuery(teamId: string, options?: ReactQueryOptions) { const { get, useQuery } = useApi(); + const { modified } = useModified(`teams:${teamId}`); + return useQuery({ - queryKey: ['teams', teamId], + queryKey: ['teams', { teamId, modified }], queryFn: () => get(`/teams/${teamId}`), enabled: !!teamId, + placeholderData: keepPreviousData, + ...options, }); } diff --git a/src/components/hooks/queries/useUserQuery.ts b/src/components/hooks/queries/useUserQuery.ts index baaa084b..6fb476d5 100644 --- a/src/components/hooks/queries/useUserQuery.ts +++ b/src/components/hooks/queries/useUserQuery.ts @@ -1,11 +1,17 @@ import { useApi } from '../useApi'; +import { useModified } from '@/components/hooks'; +import { keepPreviousData } from '@tanstack/react-query'; +import { ReactQueryOptions } from '@/lib/types'; -export function useUserQuery(userId: string, options?: Record) { +export function useUserQuery(userId: string, options?: ReactQueryOptions) { const { get, useQuery } = useApi(); + const { modified } = useModified(`user:${userId}`); + return useQuery({ - queryKey: ['users', userId], + queryKey: ['users', { userId, modified }], queryFn: () => get(`/users/${userId}`), enabled: !!userId, + placeholderData: keepPreviousData, ...options, }); } diff --git a/src/components/hooks/queries/useWebsiteQuery.ts b/src/components/hooks/queries/useWebsiteQuery.ts index 55fe4eba..8faaaa26 100644 --- a/src/components/hooks/queries/useWebsiteQuery.ts +++ b/src/components/hooks/queries/useWebsiteQuery.ts @@ -1,12 +1,17 @@ import { useApi } from '../useApi'; +import { useModified } from '@/components/hooks'; +import { keepPreviousData } from '@tanstack/react-query'; +import { ReactQueryOptions } from '@/lib/types'; -export function useWebsiteQuery(websiteId: string, options?: Record) { +export function useWebsiteQuery(websiteId: string, options?: ReactQueryOptions) { const { get, useQuery } = useApi(); + const { modified } = useModified(`website:${websiteId}`); return useQuery({ - queryKey: ['website', { websiteId }], + queryKey: ['website', { websiteId, modified }], queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, + placeholderData: keepPreviousData, ...options, }); } diff --git a/src/components/input/FilterBar.tsx b/src/components/input/FilterBar.tsx index f4f86b65..224b4c77 100644 --- a/src/components/input/FilterBar.tsx +++ b/src/components/input/FilterBar.tsx @@ -62,7 +62,7 @@ export function FilterBar() { })} -