From 9b099408c2099ad5a8048e021df387aa78dd08ae Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 7 May 2026 13:24:45 -0700 Subject: [PATCH] add share page options enable filtering and enforce theme --- public/intl/messages/ar-SA.json | 1 + public/intl/messages/be-BY.json | 1 + public/intl/messages/bg-BG.json | 1 + public/intl/messages/bn-BD.json | 1 + public/intl/messages/bs-BA.json | 1 + public/intl/messages/ca-ES.json | 1 + public/intl/messages/cs-CZ.json | 1 + public/intl/messages/da-DK.json | 1 + public/intl/messages/de-CH.json | 1 + public/intl/messages/de-DE.json | 1 + public/intl/messages/el-GR.json | 1 + public/intl/messages/en-GB.json | 1 + public/intl/messages/en-US.json | 1 + public/intl/messages/es-ES.json | 1 + public/intl/messages/fa-IR.json | 1 + public/intl/messages/fi-FI.json | 1 + public/intl/messages/fo-FO.json | 1 + public/intl/messages/fr-FR.json | 1 + public/intl/messages/ga-ES.json | 1 + public/intl/messages/he-IL.json | 1 + public/intl/messages/hi-IN.json | 1 + public/intl/messages/hr-HR.json | 1 + public/intl/messages/hu-HU.json | 1 + public/intl/messages/id-ID.json | 1 + public/intl/messages/it-IT.json | 1 + public/intl/messages/ja-JP.json | 1 + public/intl/messages/km-KH.json | 1 + public/intl/messages/ko-KR.json | 1 + public/intl/messages/lt-LT.json | 1 + public/intl/messages/mn-MN.json | 1 + public/intl/messages/ms-MY.json | 1 + public/intl/messages/my-MM.json | 1 + public/intl/messages/nb-NO.json | 1 + public/intl/messages/nl-NL.json | 1 + public/intl/messages/pl-PL.json | 1 + public/intl/messages/pt-BR.json | 1 + public/intl/messages/pt-PT.json | 1 + public/intl/messages/ro-RO.json | 1 + public/intl/messages/ru-RU.json | 1 + public/intl/messages/si-LK.json | 1 + public/intl/messages/sk-SK.json | 1 + public/intl/messages/sl-SI.json | 1 + public/intl/messages/sv-SE.json | 1 + public/intl/messages/ta-IN.json | 1 + public/intl/messages/th-TH.json | 1 + public/intl/messages/tr-TR.json | 1 + public/intl/messages/uk-UA.json | 1 + public/intl/messages/ur-PK.json | 1 + public/intl/messages/uz-UZ.json | 1 + public/intl/messages/vi-VN.json | 1 + public/intl/messages/zh-CN.json | 1 + public/intl/messages/zh-TW.json | 1 + .../boards/[boardId]/BoardShareCreateForm.tsx | 68 ++++++++++++----- .../boards/[boardId]/BoardSharesTable.tsx | 4 +- .../(main)/links/[linkId]/LinkControls.tsx | 9 ++- .../(main)/pixels/[pixelId]/PixelControls.tsx | 9 ++- .../settings/preferences/ThemeSetting.tsx | 19 +---- src/app/(main)/websites/WebsitesTable.tsx | 8 ++ .../websites/[websiteId]/WebsiteControls.tsx | 11 ++- .../[websiteId]/settings/ShareEditForm.tsx | 31 +++++++- src/app/api/boards/[boardId]/shares/route.ts | 8 +- src/app/api/links/[linkId]/shares/route.ts | 8 +- src/app/api/pixels/[pixelId]/shares/route.ts | 8 +- src/app/share/ShareProvider.tsx | 4 +- src/app/share/[slug]/[[...path]]/ShareNav.tsx | 14 +++- .../share/[slug]/[[...path]]/SharePage.tsx | 15 ++-- src/components/common/DataGrid.tsx | 72 +++++++++--------- src/components/hooks/useFilterParameters.ts | 23 +++--- src/components/hooks/useFilters.ts | 47 ++++++------ src/components/input/ThemeModeSelector.tsx | 36 +++++++++ src/components/messages.ts | 1 + .../share/SimpleShareCreateForm.tsx | 75 ++++++++++++++----- src/components/share/SimpleShareEditForm.tsx | 71 ++++++++++++------ src/lib/share.ts | 20 +++++ src/lib/types.ts | 9 +++ 75 files changed, 450 insertions(+), 172 deletions(-) create mode 100644 src/components/input/ThemeModeSelector.tsx create mode 100644 src/lib/share.ts diff --git a/public/intl/messages/ar-SA.json b/public/intl/messages/ar-SA.json index dfae6b16d..3d242fa3a 100644 --- a/public/intl/messages/ar-SA.json +++ b/public/intl/messages/ar-SA.json @@ -124,6 +124,7 @@ "filter-combined": "مُجمّعة", "filter-raw": "خام", "filters": "التصفيات", + "filters-enabled": "الفلاتر مفعّلة", "first-click": "النقرة الأولى", "first-seen": "أول ظهور", "funnel": "قمع", diff --git a/public/intl/messages/be-BY.json b/public/intl/messages/be-BY.json index 4c1d35461..23ee71517 100644 --- a/public/intl/messages/be-BY.json +++ b/public/intl/messages/be-BY.json @@ -124,6 +124,7 @@ "filter-combined": "Камбініраваны", "filter-raw": "Сырыя", "filters": "Фільтры", + "filters-enabled": "Фільтры ўключаны", "first-click": "Першы клік", "first-seen": "Першы раз убачана", "funnel": "Варонка", diff --git a/public/intl/messages/bg-BG.json b/public/intl/messages/bg-BG.json index fa58a6878..171f576e4 100644 --- a/public/intl/messages/bg-BG.json +++ b/public/intl/messages/bg-BG.json @@ -124,6 +124,7 @@ "filter-combined": "Комбиниран", "filter-raw": "Суров", "filters": "Филтри", + "filters-enabled": "Филтрите са активирани", "first-click": "Първо кликване", "first-seen": "Първо видяно", "funnel": "Фуния", diff --git a/public/intl/messages/bn-BD.json b/public/intl/messages/bn-BD.json index 85c33ea21..9c083a8f5 100644 --- a/public/intl/messages/bn-BD.json +++ b/public/intl/messages/bn-BD.json @@ -124,6 +124,7 @@ "filter-combined": "সম্মিলিত", "filter-raw": "অপরিশোধিত", "filters": "ফিল্টারসমূহ", + "filters-enabled": "ফিল্টার সক্রিয়", "first-click": "প্রথম ক্লিক", "first-seen": "প্রথম দেখা", "funnel": "ফানেল", diff --git a/public/intl/messages/bs-BA.json b/public/intl/messages/bs-BA.json index a11f610bd..931956515 100644 --- a/public/intl/messages/bs-BA.json +++ b/public/intl/messages/bs-BA.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinovano", "filter-raw": "Sirovo", "filters": "Filtri", + "filters-enabled": "Filteri omogućeni", "first-click": "Prvi klik", "first-seen": "Prvi put viđeno", "funnel": "Lijevak", diff --git a/public/intl/messages/ca-ES.json b/public/intl/messages/ca-ES.json index 0a804c0e1..5c7b62b47 100644 --- a/public/intl/messages/ca-ES.json +++ b/public/intl/messages/ca-ES.json @@ -125,6 +125,7 @@ "filter-combined": "Combinat", "filter-raw": "En cru", "filters": "Filtres", + "filters-enabled": "Filtres activats", "first-click": "Primer clic", "first-seen": "Vist per primer cop", "funnel": "Embut", diff --git a/public/intl/messages/cs-CZ.json b/public/intl/messages/cs-CZ.json index 764dfd2ac..e3789ff28 100644 --- a/public/intl/messages/cs-CZ.json +++ b/public/intl/messages/cs-CZ.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinace", "filter-raw": "Nezpracované", "filters": "Filtry", + "filters-enabled": "Filtry povoleny", "first-click": "První kliknutí", "first-seen": "Poprvé viděno", "funnel": "Trychtýř", diff --git a/public/intl/messages/da-DK.json b/public/intl/messages/da-DK.json index 2f005c7f6..8d3ed3cda 100644 --- a/public/intl/messages/da-DK.json +++ b/public/intl/messages/da-DK.json @@ -125,6 +125,7 @@ "filter-combined": "Kombineret", "filter-raw": "Rå", "filters": "Filtre", + "filters-enabled": "Filtre aktiveret", "first-click": "Første klik", "first-seen": "Først set", "funnel": "Tragt", diff --git a/public/intl/messages/de-CH.json b/public/intl/messages/de-CH.json index 50fedf16c..71cce4823 100644 --- a/public/intl/messages/de-CH.json +++ b/public/intl/messages/de-CH.json @@ -125,6 +125,7 @@ "filter-combined": "Kombiniert", "filter-raw": "Rohdate", "filters": "Filter", + "filters-enabled": "Filter aktiviert", "first-click": "Erste Klick", "first-seen": "Erstmal gse", "funnel": "Tunnel", diff --git a/public/intl/messages/de-DE.json b/public/intl/messages/de-DE.json index cb422375a..907d3768c 100644 --- a/public/intl/messages/de-DE.json +++ b/public/intl/messages/de-DE.json @@ -125,6 +125,7 @@ "filter-combined": "Kombiniert", "filter-raw": "Rohdaten", "filters": "Filter", + "filters-enabled": "Filter aktiviert", "first-click": "Erster Klick", "first-seen": "Erstmalig gesehen", "funnel": "Trichter", diff --git a/public/intl/messages/el-GR.json b/public/intl/messages/el-GR.json index 199becdc4..64a004dd9 100644 --- a/public/intl/messages/el-GR.json +++ b/public/intl/messages/el-GR.json @@ -124,6 +124,7 @@ "filter-combined": "Σε συνδυασμό", "filter-raw": "Ακατέργαστο", "filters": "Φίλτρα", + "filters-enabled": "Φίλτρα ενεργοποιημένα", "first-click": "Πρώτο κλικ", "first-seen": "Πρώτη εμφάνιση", "funnel": "Χωνί", diff --git a/public/intl/messages/en-GB.json b/public/intl/messages/en-GB.json index 9de60575c..243b6e9a5 100644 --- a/public/intl/messages/en-GB.json +++ b/public/intl/messages/en-GB.json @@ -125,6 +125,7 @@ "filter-combined": "Combined", "filter-raw": "Raw", "filters": "Filters", + "filters-enabled": "Filters enabled", "first-click": "First click", "first-seen": "First seen", "funnel": "Funnel", diff --git a/public/intl/messages/en-US.json b/public/intl/messages/en-US.json index 6614c6500..45328163f 100644 --- a/public/intl/messages/en-US.json +++ b/public/intl/messages/en-US.json @@ -125,6 +125,7 @@ "filter-combined": "Combined", "filter-raw": "Raw", "filters": "Filters", + "filters-enabled": "Filters enabled", "property-filter": "Property Filter", "activity-by-property": "Activity by property", "first-click": "First click", diff --git a/public/intl/messages/es-ES.json b/public/intl/messages/es-ES.json index 31861c0b9..33dbcea29 100644 --- a/public/intl/messages/es-ES.json +++ b/public/intl/messages/es-ES.json @@ -125,6 +125,7 @@ "filter-combined": "Combinado", "filter-raw": "En crudo", "filters": "Filtros", + "filters-enabled": "Filtros activados", "first-click": "Primer clic", "first-seen": "Primera vez visto", "funnel": "Embudo", diff --git a/public/intl/messages/fa-IR.json b/public/intl/messages/fa-IR.json index cd7352ac0..5ef288bfe 100644 --- a/public/intl/messages/fa-IR.json +++ b/public/intl/messages/fa-IR.json @@ -124,6 +124,7 @@ "filter-combined": "ترکیب شده", "filter-raw": "خام", "filters": "فیلترها", + "filters-enabled": "فیلترها فعال", "first-click": "اولین کلیک", "first-seen": "اولین بار دیده شده", "funnel": "فانل", diff --git a/public/intl/messages/fi-FI.json b/public/intl/messages/fi-FI.json index 049906bf3..2fa0135e8 100644 --- a/public/intl/messages/fi-FI.json +++ b/public/intl/messages/fi-FI.json @@ -125,6 +125,7 @@ "filter-combined": "Yhdistetty", "filter-raw": "Käsittelemätön", "filters": "Suodattimet", + "filters-enabled": "Suodattimet käytössä", "first-click": "Ensimmäinen klikkaus", "first-seen": "Ensimmäinen havainto", "funnel": "Suppilo", diff --git a/public/intl/messages/fo-FO.json b/public/intl/messages/fo-FO.json index e01a37717..758148dee 100644 --- a/public/intl/messages/fo-FO.json +++ b/public/intl/messages/fo-FO.json @@ -125,6 +125,7 @@ "filter-combined": "Samansett", "filter-raw": "Óviðgjørt", "filters": "Síur", + "filters-enabled": "Síur virknaðar", "first-click": "Fyrsta trýst", "first-seen": "Fyrst sæddur", "funnel": "Traktari", diff --git a/public/intl/messages/fr-FR.json b/public/intl/messages/fr-FR.json index 982370683..a02786858 100644 --- a/public/intl/messages/fr-FR.json +++ b/public/intl/messages/fr-FR.json @@ -126,6 +126,7 @@ "filter-combined": "Combiné", "filter-raw": "Brut", "filters": "Filtres", + "filters-enabled": "Filtres activés", "first-click": "Premier clic", "first-seen": "Vu pour la première fois", "funnel": "Entonnoir", diff --git a/public/intl/messages/ga-ES.json b/public/intl/messages/ga-ES.json index a9e180864..0ac9bdf82 100644 --- a/public/intl/messages/ga-ES.json +++ b/public/intl/messages/ga-ES.json @@ -125,6 +125,7 @@ "filter-combined": "Combinado", "filter-raw": "Crú", "filters": "Filtros", + "filters-enabled": "Filtros activados", "first-click": "Primeiro clic", "first-seen": "Primeira visita", "funnel": "Funil", diff --git a/public/intl/messages/he-IL.json b/public/intl/messages/he-IL.json index f5ed72c2f..dda365f0a 100644 --- a/public/intl/messages/he-IL.json +++ b/public/intl/messages/he-IL.json @@ -124,6 +124,7 @@ "filter-combined": "משותף", "filter-raw": "גולמי", "filters": "מסננים", + "filters-enabled": "מסננים מופעלים", "first-click": "קליק ראשון", "first-seen": "נראה לראשונה", "funnel": "משפך", diff --git a/public/intl/messages/hi-IN.json b/public/intl/messages/hi-IN.json index a1b7d396f..d2123d382 100644 --- a/public/intl/messages/hi-IN.json +++ b/public/intl/messages/hi-IN.json @@ -124,6 +124,7 @@ "filter-combined": "संयुक्त", "filter-raw": "रॉ", "filters": "फ़िल्टर", + "filters-enabled": "फ़िल्टर सक्षम", "first-click": "पहला क्लिक", "first-seen": "पहली बार देखा गया", "funnel": "फनल", diff --git a/public/intl/messages/hr-HR.json b/public/intl/messages/hr-HR.json index 963b86a50..a5c022162 100644 --- a/public/intl/messages/hr-HR.json +++ b/public/intl/messages/hr-HR.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinirano", "filter-raw": "Neobrađeno", "filters": "Filteri", + "filters-enabled": "Filtri omogućeni", "first-click": "Prvi klik", "first-seen": "Prvi put viđeno", "funnel": "Lijevak", diff --git a/public/intl/messages/hu-HU.json b/public/intl/messages/hu-HU.json index aea98d3b2..d72587e01 100644 --- a/public/intl/messages/hu-HU.json +++ b/public/intl/messages/hu-HU.json @@ -125,6 +125,7 @@ "filter-combined": "Összevont", "filter-raw": "Nyers", "filters": "Szűrők", + "filters-enabled": "Szűrők engedélyezve", "first-click": "Első kattintás", "first-seen": "Első megtekintés", "funnel": "Tölcsér", diff --git a/public/intl/messages/id-ID.json b/public/intl/messages/id-ID.json index 1bc6b433c..64e68bbbe 100644 --- a/public/intl/messages/id-ID.json +++ b/public/intl/messages/id-ID.json @@ -125,6 +125,7 @@ "filter-combined": "Gabungan", "filter-raw": "Mentah", "filters": "Filter", + "filters-enabled": "Filter diaktifkan", "first-click": "Klik pertama", "first-seen": "Pertama kali dilihat", "funnel": "Corong", diff --git a/public/intl/messages/it-IT.json b/public/intl/messages/it-IT.json index a4589faa7..0ad631dcd 100644 --- a/public/intl/messages/it-IT.json +++ b/public/intl/messages/it-IT.json @@ -125,6 +125,7 @@ "filter-combined": "Aggregati", "filter-raw": "Grezzo", "filters": "Filtri", + "filters-enabled": "Filtri abilitati", "first-click": "Primo clic", "first-seen": "Prima visualizzazione", "funnel": "Funnel", diff --git a/public/intl/messages/ja-JP.json b/public/intl/messages/ja-JP.json index bcf304f8e..243cf1abe 100644 --- a/public/intl/messages/ja-JP.json +++ b/public/intl/messages/ja-JP.json @@ -124,6 +124,7 @@ "filter-combined": "結合", "filter-raw": "RAW", "filters": "フィルター", + "filters-enabled": "フィルター有効", "first-click": "最初のクリック", "first-seen": "初回ログイン", "funnel": "ファネル", diff --git a/public/intl/messages/km-KH.json b/public/intl/messages/km-KH.json index 9fb936623..210a411ae 100644 --- a/public/intl/messages/km-KH.json +++ b/public/intl/messages/km-KH.json @@ -124,6 +124,7 @@ "filter-combined": "រួមបញ្ចូលគ្នា", "filter-raw": "ដើម", "filters": "ចម្រោះ", + "filters-enabled": "ការច្រោះបានបើក", "first-click": "ចុចដំបូង", "first-seen": "ឃើញដំបូង", "funnel": "ផ្លូវបង្ហាញ", diff --git a/public/intl/messages/ko-KR.json b/public/intl/messages/ko-KR.json index 579b9b625..9aaffeb82 100644 --- a/public/intl/messages/ko-KR.json +++ b/public/intl/messages/ko-KR.json @@ -124,6 +124,7 @@ "filter-combined": "합쳐 보기", "filter-raw": "전체 보기", "filters": "필터", + "filters-enabled": "필터 활성화됨", "first-click": "첫 클릭", "first-seen": "첫 접속", "funnel": "퍼널", diff --git a/public/intl/messages/lt-LT.json b/public/intl/messages/lt-LT.json index c0b2aaa0c..cdb4c8191 100644 --- a/public/intl/messages/lt-LT.json +++ b/public/intl/messages/lt-LT.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinuoti", "filter-raw": "Neapdoroti", "filters": "Filtrai", + "filters-enabled": "Filtrai įjungti", "first-click": "Pirmas paspaudimas", "first-seen": "Pirmą kartą matyta", "funnel": "Piltuvas", diff --git a/public/intl/messages/mn-MN.json b/public/intl/messages/mn-MN.json index a9841a278..a58e96947 100644 --- a/public/intl/messages/mn-MN.json +++ b/public/intl/messages/mn-MN.json @@ -124,6 +124,7 @@ "filter-combined": "Нэгтгэсэн", "filter-raw": "Түүхий", "filters": "Шүүлтүүр", + "filters-enabled": "Шүүлтүүр идэвхжүүлэгдсэн", "first-click": "Эхний даралт", "first-seen": "Анх харсан", "funnel": "Цутгал", diff --git a/public/intl/messages/ms-MY.json b/public/intl/messages/ms-MY.json index d34510f54..56ed4f7ac 100644 --- a/public/intl/messages/ms-MY.json +++ b/public/intl/messages/ms-MY.json @@ -125,6 +125,7 @@ "filter-combined": "Digabungkan", "filter-raw": "Mentah", "filters": "Tapis", + "filters-enabled": "Penapis diaktifkan", "first-click": "Klik pertama", "first-seen": "Pertama dilihat", "funnel": "Corong", diff --git a/public/intl/messages/my-MM.json b/public/intl/messages/my-MM.json index 74bea7fb6..b8fc9b7b7 100644 --- a/public/intl/messages/my-MM.json +++ b/public/intl/messages/my-MM.json @@ -124,6 +124,7 @@ "filter-combined": "ပေါင်းစပ်ပြီး", "filter-raw": "အရှိအတိုင်း", "filters": "Filter များ", + "filters-enabled": "စစ်ထုတ်မှုများ ဖွင့်ထားသည်", "first-click": "ပထမဆုံးနှိပ်ချက်", "first-seen": "ပထမဆုံးတွေ့ရှိချိန်", "funnel": "ဖန်နယ်", diff --git a/public/intl/messages/nb-NO.json b/public/intl/messages/nb-NO.json index 699500727..279336971 100644 --- a/public/intl/messages/nb-NO.json +++ b/public/intl/messages/nb-NO.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinert", "filter-raw": "Rå", "filters": "Filter", + "filters-enabled": "Filtre aktivert", "first-click": "Første klikk", "first-seen": "Først sett", "funnel": "Trakt", diff --git a/public/intl/messages/nl-NL.json b/public/intl/messages/nl-NL.json index 0749ac70e..446f84ff4 100644 --- a/public/intl/messages/nl-NL.json +++ b/public/intl/messages/nl-NL.json @@ -125,6 +125,7 @@ "filter-combined": "Gecombineerd", "filter-raw": "Ruw", "filters": "Filters", + "filters-enabled": "Filters ingeschakeld", "first-click": "Eerste klik", "first-seen": "Eerst gezien", "funnel": "Trechter", diff --git a/public/intl/messages/pl-PL.json b/public/intl/messages/pl-PL.json index ff3051a38..aff7aa1da 100644 --- a/public/intl/messages/pl-PL.json +++ b/public/intl/messages/pl-PL.json @@ -125,6 +125,7 @@ "filter-combined": "Połączone", "filter-raw": "Surowe dane", "filters": "Filtry", + "filters-enabled": "Filtry włączone", "first-click": "Pierwsze kliknięcie", "first-seen": "Pierwsza wizyta", "funnel": "Lejek", diff --git a/public/intl/messages/pt-BR.json b/public/intl/messages/pt-BR.json index 9ee595584..698c190fd 100644 --- a/public/intl/messages/pt-BR.json +++ b/public/intl/messages/pt-BR.json @@ -125,6 +125,7 @@ "filter-combined": "Combinado", "filter-raw": "Bruto", "filters": "Filtros", + "filters-enabled": "Filtros ativados", "first-click": "Primeiro clique", "first-seen": "Visto pela primeira vez", "funnel": "Funil", diff --git a/public/intl/messages/pt-PT.json b/public/intl/messages/pt-PT.json index 625b60d01..7e24883cf 100644 --- a/public/intl/messages/pt-PT.json +++ b/public/intl/messages/pt-PT.json @@ -125,6 +125,7 @@ "filter-combined": "Combinado", "filter-raw": "Dados brutos", "filters": "Filtros", + "filters-enabled": "Filtros ativados", "first-click": "Primeiro clique", "first-seen": "Primeira visualização", "funnel": "Funil", diff --git a/public/intl/messages/ro-RO.json b/public/intl/messages/ro-RO.json index b9f010d09..84ee2bc69 100644 --- a/public/intl/messages/ro-RO.json +++ b/public/intl/messages/ro-RO.json @@ -125,6 +125,7 @@ "filter-combined": "Combinat", "filter-raw": "Brut", "filters": "Filtre", + "filters-enabled": "Filtre activate", "first-click": "Primul click", "first-seen": "Văzut pentru prima dată", "funnel": "Parcursul utilizatorului", diff --git a/public/intl/messages/ru-RU.json b/public/intl/messages/ru-RU.json index 5bf41593c..7348fc648 100644 --- a/public/intl/messages/ru-RU.json +++ b/public/intl/messages/ru-RU.json @@ -124,6 +124,7 @@ "filter-combined": "Объединенные", "filter-raw": "Сырые данные", "filters": "Фильтры", + "filters-enabled": "Фильтры включены", "first-click": "Первый клик", "first-seen": "Первый вход", "funnel": "Воронка", diff --git a/public/intl/messages/si-LK.json b/public/intl/messages/si-LK.json index 570687e4b..78a8aa3e1 100644 --- a/public/intl/messages/si-LK.json +++ b/public/intl/messages/si-LK.json @@ -124,6 +124,7 @@ "filter-combined": "ඒකාබද්ධ", "filter-raw": "අමු", "filters": "පෙරහන්", + "filters-enabled": "පෙරහන් සක්‍රීය කර ඇත", "first-click": "පළමු ක්ලික්", "first-seen": "මුලින් දුටු", "funnel": "පුනීලය", diff --git a/public/intl/messages/sk-SK.json b/public/intl/messages/sk-SK.json index d55ea416d..1f429dfa2 100644 --- a/public/intl/messages/sk-SK.json +++ b/public/intl/messages/sk-SK.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinácie", "filter-raw": "Nezpracované", "filters": "Filtre", + "filters-enabled": "Filtre povolené", "first-click": "Prvé kliknutie", "first-seen": "Prvýkrát videné", "funnel": "Lievik", diff --git a/public/intl/messages/sl-SI.json b/public/intl/messages/sl-SI.json index 7e4573297..f8fc8407f 100644 --- a/public/intl/messages/sl-SI.json +++ b/public/intl/messages/sl-SI.json @@ -125,6 +125,7 @@ "filter-combined": "Skupaj", "filter-raw": "Neobdelano", "filters": "Filtri", + "filters-enabled": "Filtri omogočeni", "first-click": "Prvi klik", "first-seen": "Prvič viden", "funnel": "Prodajni lijak", diff --git a/public/intl/messages/sv-SE.json b/public/intl/messages/sv-SE.json index cc175b334..b600465ff 100644 --- a/public/intl/messages/sv-SE.json +++ b/public/intl/messages/sv-SE.json @@ -125,6 +125,7 @@ "filter-combined": "Kombinerade", "filter-raw": "Rådata", "filters": "Filter", + "filters-enabled": "Filter aktiverade", "first-click": "Första klicket", "first-seen": "Först sedd", "funnel": "Tratt", diff --git a/public/intl/messages/ta-IN.json b/public/intl/messages/ta-IN.json index 5092f810d..c605796c7 100644 --- a/public/intl/messages/ta-IN.json +++ b/public/intl/messages/ta-IN.json @@ -124,6 +124,7 @@ "filter-combined": "ஒருங்கிணைந்த", "filter-raw": "மூல", "filters": "வடிகட்டிகள்", + "filters-enabled": "வடிகட்டிகள் இயக்கப்பட்டன", "first-click": "முதல் கிளிக்", "first-seen": "முதலில் பார்த்தது", "funnel": "புனல்", diff --git a/public/intl/messages/th-TH.json b/public/intl/messages/th-TH.json index 275152e4b..ce4734ff8 100644 --- a/public/intl/messages/th-TH.json +++ b/public/intl/messages/th-TH.json @@ -124,6 +124,7 @@ "filter-combined": "ข้อมูลรวม", "filter-raw": "ข้อมูลดิบ", "filters": "ตัวกรอง", + "filters-enabled": "เปิดใช้งานตัวกรอง", "first-click": "คลิกแรก", "first-seen": "เห็นครั้งแรก", "funnel": "ช่องทางขาย", diff --git a/public/intl/messages/tr-TR.json b/public/intl/messages/tr-TR.json index 7c915c393..0725081cb 100644 --- a/public/intl/messages/tr-TR.json +++ b/public/intl/messages/tr-TR.json @@ -125,6 +125,7 @@ "filter-combined": "Birleşik filtre", "filter-raw": "Ham filtre", "filters": "Filtreler", + "filters-enabled": "Filtreler etkin", "first-click": "İlk tıklama", "first-seen": "İlk görülme", "funnel": "Huni", diff --git a/public/intl/messages/uk-UA.json b/public/intl/messages/uk-UA.json index 9d298a3f3..e91c3a8be 100644 --- a/public/intl/messages/uk-UA.json +++ b/public/intl/messages/uk-UA.json @@ -124,6 +124,7 @@ "filter-combined": "Об'єднані", "filter-raw": "Сирі дані", "filters": "Фільтри", + "filters-enabled": "Фільтри увімкнено", "first-click": "Перший клік", "first-seen": "Перший візит", "funnel": "Воронка", diff --git a/public/intl/messages/ur-PK.json b/public/intl/messages/ur-PK.json index 016af6ee0..3e59d20a9 100644 --- a/public/intl/messages/ur-PK.json +++ b/public/intl/messages/ur-PK.json @@ -124,6 +124,7 @@ "filter-combined": "مشترکہ", "filter-raw": "خام", "filters": "فلٹرز", + "filters-enabled": "فلٹرز فعال", "first-click": "پہلا کلک", "first-seen": "پہلی بار دیکھا گیا", "funnel": "فنل", diff --git a/public/intl/messages/uz-UZ.json b/public/intl/messages/uz-UZ.json index 7d61b3301..9b54f139c 100644 --- a/public/intl/messages/uz-UZ.json +++ b/public/intl/messages/uz-UZ.json @@ -125,6 +125,7 @@ "filter-combined": "Birlashtirilgan", "filter-raw": "Xom", "filters": "Filtrlar", + "filters-enabled": "Filtrlar yoqilgan", "first-click": "Birinchi bosish", "first-seen": "Birinchi koʻrilgan", "funnel": "Voronka", diff --git a/public/intl/messages/vi-VN.json b/public/intl/messages/vi-VN.json index eec7e013b..c2d649f7b 100644 --- a/public/intl/messages/vi-VN.json +++ b/public/intl/messages/vi-VN.json @@ -124,6 +124,7 @@ "filter-combined": "Kết hợp lọc", "filter-raw": "Lọc thô", "filters": "Bộ lọc", + "filters-enabled": "Bộ lọc đã bật", "first-click": "Nhấp đầu tiên", "first-seen": "Lần đầu tiên nhìn thấy", "funnel": "Phễu", diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index e552cf158..946e21ff5 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -124,6 +124,7 @@ "filter-combined": "合并", "filter-raw": "原始", "filters": "筛选", + "filters-enabled": "过滤器已启用", "first-click": "首次点击", "first-seen": "首次出现", "funnel": "分析", diff --git a/public/intl/messages/zh-TW.json b/public/intl/messages/zh-TW.json index 1f644123c..8e65e271d 100644 --- a/public/intl/messages/zh-TW.json +++ b/public/intl/messages/zh-TW.json @@ -124,6 +124,7 @@ "filter-combined": "組合", "filter-raw": "原始", "filters": "篩選條件", + "filters-enabled": "篩選器已啟用", "first-click": "首次點擊", "first-seen": "首次造訪", "funnel": "漏斗分析", diff --git a/src/app/(main)/boards/[boardId]/BoardShareCreateForm.tsx b/src/app/(main)/boards/[boardId]/BoardShareCreateForm.tsx index 1842edcc9..3598bfe2f 100644 --- a/src/app/(main)/boards/[boardId]/BoardShareCreateForm.tsx +++ b/src/app/(main)/boards/[boardId]/BoardShareCreateForm.tsx @@ -1,6 +1,16 @@ -import { Button, Column, Form, FormField, FormSubmitButton, Row, TextField } from '@umami/react-zen'; +import { + Button, + Checkbox, + Column, + Form, + FormField, + FormSubmitButton, + Row, + TextField, +} from '@umami/react-zen'; import { useState } from 'react'; import { useApi, useMessages, useModified } from '@/components/hooks'; +import { ThemeModeSelector } from '@/components/input/ThemeModeSelector'; export function BoardShareCreateForm({ boardId, @@ -15,15 +25,19 @@ export function BoardShareCreateForm({ const { touch } = useModified(); const { t, labels, getErrorMessage } = useMessages(); const [isPending, setIsPending] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); - const handleSubmit = async (data: { name: string }) => { + const handleSubmit = async (data: { name: string; allowFilter?: boolean; theme?: string }) => { setIsPending(true); setError(null); try { await post(`/boards/${boardId}/shares`, { name: data.name, + parameters: { + allowFilter: data.allowFilter ?? true, + theme: data.theme === 'system' ? undefined : data.theme, + }, }); touch('shares'); @@ -36,22 +50,38 @@ export function BoardShareCreateForm({ }; return ( -
- - - - - - {onCancel && ( - - )} - - {t(labels.add)} - - - + + {({ watch, setValue }) => ( + + + + + + {t(labels.filters)} + + + setValue('theme', value, { shouldDirty: true })} + /> + + + {onCancel && ( + + )} + + {t(labels.add)} + + + + )}
); } diff --git a/src/app/(main)/boards/[boardId]/BoardSharesTable.tsx b/src/app/(main)/boards/[boardId]/BoardSharesTable.tsx index 3f4e81531..c3b30c297 100644 --- a/src/app/(main)/boards/[boardId]/BoardSharesTable.tsx +++ b/src/app/(main)/boards/[boardId]/BoardSharesTable.tsx @@ -3,6 +3,7 @@ import { CopyButton } from '@/components/common/CopyButton'; import { ExternalLink } from '@/components/common/ExternalLink'; import { useConfig, useMessages, useMobile } from '@/components/hooks'; import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen'; +import { SimpleShareEditButton } from '@/components/share/SimpleShareEditButton'; export function BoardSharesTable(props: DataTableProps) { const { t, labels } = useMessages(); @@ -36,9 +37,10 @@ export function BoardSharesTable(props: DataTableProps) { ); }} - + {({ id, slug }: any) => ( + )} diff --git a/src/app/(main)/links/[linkId]/LinkControls.tsx b/src/app/(main)/links/[linkId]/LinkControls.tsx index 1d1147a82..ad09457e8 100644 --- a/src/app/(main)/links/[linkId]/LinkControls.tsx +++ b/src/app/(main)/links/[linkId]/LinkControls.tsx @@ -1,9 +1,11 @@ import { Column, Row } from '@umami/react-zen'; +import { useShare } from '@/components/hooks'; import { ExportButton } from '@/components/input/ExportButton'; import { FilterBar } from '@/components/input/FilterBar'; import { MonthFilter } from '@/components/input/MonthFilter'; import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter'; import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton'; +import { allowShareFilter } from '@/lib/share'; export function LinkControls({ linkId: websiteId, @@ -18,15 +20,18 @@ export function LinkControls({ allowMonthFilter?: boolean; allowDownload?: boolean; }) { + const share = useShare(); + const showFilter = allowFilter && allowShareFilter(share?.parameters); + return ( - {allowFilter ? :
} + {showFilter ? :
} {allowDateFilter && } {allowDownload && } {allowMonthFilter && } - {allowFilter && } + {showFilter && } ); } diff --git a/src/app/(main)/pixels/[pixelId]/PixelControls.tsx b/src/app/(main)/pixels/[pixelId]/PixelControls.tsx index 55dcd5764..539b0adca 100644 --- a/src/app/(main)/pixels/[pixelId]/PixelControls.tsx +++ b/src/app/(main)/pixels/[pixelId]/PixelControls.tsx @@ -1,9 +1,11 @@ import { Column, Row } from '@umami/react-zen'; +import { useShare } from '@/components/hooks'; import { ExportButton } from '@/components/input/ExportButton'; import { FilterBar } from '@/components/input/FilterBar'; import { MonthFilter } from '@/components/input/MonthFilter'; import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter'; import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton'; +import { allowShareFilter } from '@/lib/share'; export function PixelControls({ pixelId: websiteId, @@ -18,15 +20,18 @@ export function PixelControls({ allowMonthFilter?: boolean; allowDownload?: boolean; }) { + const share = useShare(); + const showFilter = allowFilter && allowShareFilter(share?.parameters); + return ( - {allowFilter ? :
} + {showFilter ? :
} {allowDateFilter && } {allowDownload && } {allowMonthFilter && } - {allowFilter && } + {showFilter && } ); } diff --git a/src/app/(main)/settings/preferences/ThemeSetting.tsx b/src/app/(main)/settings/preferences/ThemeSetting.tsx index 03bd6a6e4..091a78e2e 100644 --- a/src/app/(main)/settings/preferences/ThemeSetting.tsx +++ b/src/app/(main)/settings/preferences/ThemeSetting.tsx @@ -1,21 +1,8 @@ -import { Button, Icon, Row, useTheme } from '@umami/react-zen'; -import { Moon, Sun } from '@/components/icons'; +import { useTheme } from '@umami/react-zen'; +import { ThemeModeSelector } from '@/components/input/ThemeModeSelector'; export function ThemeSetting() { const { theme, setTheme } = useTheme(); - return ( - - - - - ); + return setTheme(value as 'light' | 'dark')} />; } diff --git a/src/app/(main)/websites/WebsitesTable.tsx b/src/app/(main)/websites/WebsitesTable.tsx index bfa13e4ae..e1ed4a7d1 100644 --- a/src/app/(main)/websites/WebsitesTable.tsx +++ b/src/app/(main)/websites/WebsitesTable.tsx @@ -1,5 +1,6 @@ import { DataColumn, DataTable, type DataTableProps, Icon } from '@umami/react-zen'; import type { ReactNode } from 'react'; +import { DateDistance } from '@/components/common/DateDistance'; import { LinkButton } from '@/components/common/LinkButton'; import { SortableLabel } from '@/components/common/SortableLabel'; import { useMessages, useNavigation } from '@/components/hooks'; @@ -22,6 +23,13 @@ export function WebsitesTable({ showActions, renderLink, ...props }: WebsitesTab {renderLink} } /> + } + width="200px" + > + {(row: any) => } + {showActions && ( {(row: any) => { diff --git a/src/app/(main)/websites/[websiteId]/WebsiteControls.tsx b/src/app/(main)/websites/[websiteId]/WebsiteControls.tsx index bab23bc5b..a46489a13 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteControls.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteControls.tsx @@ -1,9 +1,11 @@ import { Column, Grid, Row } from '@umami/react-zen'; +import { useShare } from '@/components/hooks'; import { ExportButton } from '@/components/input/ExportButton'; import { FilterBar } from '@/components/input/FilterBar'; import { MonthFilter } from '@/components/input/MonthFilter'; import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter'; import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton'; +import { allowShareFilter } from '@/lib/share'; export function WebsiteControls({ websiteId, @@ -22,11 +24,16 @@ export function WebsiteControls({ allowDownload?: boolean; allowCompare?: boolean; }) { + const share = useShare(); + const showFilter = allowFilter && allowShareFilter(share?.parameters); + return ( - {allowFilter && } + {showFilter && ( + + )} {allowDateFilter && ( @@ -36,7 +43,7 @@ export function WebsiteControls({ {allowMonthFilter && } - {allowFilter && } + {showFilter && } ); } diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx index 161d35733..3233a8585 100644 --- a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx @@ -7,13 +7,16 @@ import { FormSubmitButton, Grid, Label, + ListSeparator, Loading, Row, + Switch, Text, TextField, } from '@umami/react-zen'; import { useEffect, useState } from 'react'; import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; +import { ThemeModeSelector } from '@/components/input/ThemeModeSelector'; import { SHARE_NAV_ITEMS } from './constants'; export function ShareEditForm({ @@ -59,12 +62,14 @@ export function ShareEditForm({ }, [shareId, modified]); const handleSubmit = async (data: any) => { - const parameters: Record = {}; + const parameters: Record = {}; SHARE_NAV_ITEMS.forEach(section => { section.items.forEach(item => { parameters[item.id] = data[item.id] ?? false; }); }); + parameters.allowFilter = data.allowFilter ?? true; + parameters.theme = data.theme === 'system' ? undefined : data.theme; setIsPending(true); setError(null); @@ -101,6 +106,8 @@ export function ShareEditForm({ // Build default values from share parameters const defaultValues: Record = { name: share?.name || '', + allowFilter: share?.parameters?.allowFilter ?? true, + theme: share?.parameters?.theme || 'system', }; SHARE_NAV_ITEMS.forEach(section => { section.items.forEach(item => { @@ -114,7 +121,7 @@ export function ShareEditForm({ return (
- {({ watch }) => { + {({ watch, setValue }) => { const values = watch(); const hasSelection = allItemIds.some(id => values[id]); @@ -129,7 +136,25 @@ export function ShareEditForm({ - + + + setValue('allowFilter', value, { shouldDirty: true })} + > + {t(labels.filtersEnabled)} + + + + setValue('theme', value, { shouldDirty: true })} + /> + + + + {SHARE_NAV_ITEMS.map(section => ( {t((labels as any)[section.section])} diff --git a/src/app/api/boards/[boardId]/shares/route.ts b/src/app/api/boards/[boardId]/shares/route.ts index f8adeff10..46e28e522 100644 --- a/src/app/api/boards/[boardId]/shares/route.ts +++ b/src/app/api/boards/[boardId]/shares/route.ts @@ -4,7 +4,7 @@ import { uuid } from '@/lib/crypto'; import { getRandomChars } from '@/lib/generate'; import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { filterParams, pagingParams } from '@/lib/schema'; +import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema'; import { canUpdateBoard, canViewBoard } from '@/permissions'; import { createShare, getSharesByEntityId } from '@/queries/prisma'; @@ -45,6 +45,7 @@ export async function POST( ) { const schema = z.object({ name: z.string().max(200), + parameters: anyObjectParam.optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -54,7 +55,8 @@ export async function POST( } const { boardId } = await params; - const { name } = body; + const { name, parameters } = body; + const shareParameters = parameters ?? {}; if (!(await canUpdateBoard(auth, boardId))) { return unauthorized(); @@ -66,7 +68,7 @@ export async function POST( shareType: ENTITY_TYPE.board, name, slug: getRandomChars(16), - parameters: {}, + parameters: shareParameters, }); return json(share); diff --git a/src/app/api/links/[linkId]/shares/route.ts b/src/app/api/links/[linkId]/shares/route.ts index 0ed8516e6..f596bad85 100644 --- a/src/app/api/links/[linkId]/shares/route.ts +++ b/src/app/api/links/[linkId]/shares/route.ts @@ -4,7 +4,7 @@ import { uuid } from '@/lib/crypto'; import { getRandomChars } from '@/lib/generate'; import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { filterParams, pagingParams } from '@/lib/schema'; +import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema'; import { canUpdateLink, canViewLink } from '@/permissions'; import { createShare, getSharesByEntityId } from '@/queries/prisma'; @@ -45,6 +45,7 @@ export async function POST( ) { const schema = z.object({ name: z.string().max(200), + parameters: anyObjectParam.optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -54,7 +55,8 @@ export async function POST( } const { linkId } = await params; - const { name } = body; + const { name, parameters } = body; + const shareParameters = parameters ?? {}; if (!(await canUpdateLink(auth, linkId))) { return unauthorized(); @@ -66,7 +68,7 @@ export async function POST( shareType: ENTITY_TYPE.link, name, slug: getRandomChars(16), - parameters: {}, + parameters: shareParameters, }); return json(share); diff --git a/src/app/api/pixels/[pixelId]/shares/route.ts b/src/app/api/pixels/[pixelId]/shares/route.ts index 8b688cd7c..a82b8ff7e 100644 --- a/src/app/api/pixels/[pixelId]/shares/route.ts +++ b/src/app/api/pixels/[pixelId]/shares/route.ts @@ -4,7 +4,7 @@ import { uuid } from '@/lib/crypto'; import { getRandomChars } from '@/lib/generate'; import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { filterParams, pagingParams } from '@/lib/schema'; +import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema'; import { canUpdatePixel, canViewPixel } from '@/permissions'; import { createShare, getSharesByEntityId } from '@/queries/prisma'; @@ -45,6 +45,7 @@ export async function POST( ) { const schema = z.object({ name: z.string().max(200), + parameters: anyObjectParam.optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -54,7 +55,8 @@ export async function POST( } const { pixelId } = await params; - const { name } = body; + const { name, parameters } = body; + const shareParameters = parameters ?? {}; if (!(await canUpdatePixel(auth, pixelId))) { return unauthorized(); @@ -66,7 +68,7 @@ export async function POST( shareType: ENTITY_TYPE.pixel, name, slug: getRandomChars(16), - parameters: {}, + parameters: shareParameters, }); return json(share); diff --git a/src/app/share/ShareProvider.tsx b/src/app/share/ShareProvider.tsx index ab01690eb..806e1347a 100644 --- a/src/app/share/ShareProvider.tsx +++ b/src/app/share/ShareProvider.tsx @@ -1,7 +1,7 @@ 'use client'; import { useShareTokenQuery } from '@/components/hooks'; import { ENTITY_TYPE } from '@/lib/constants'; -import type { WhiteLabel } from '@/lib/types'; +import type { ShareParameters, WhiteLabel } from '@/lib/types'; import { Loading } from '@umami/react-zen'; import { usePathname, useRouter } from 'next/navigation'; import { createContext, type ReactNode, useEffect } from 'react'; @@ -15,7 +15,7 @@ export interface ShareData { boardId?: string; pixelId?: string; linkId?: string; - parameters: any; + parameters: ShareParameters; token: string; whiteLabel?: WhiteLabel; } diff --git a/src/app/share/[slug]/[[...path]]/ShareNav.tsx b/src/app/share/[slug]/[[...path]]/ShareNav.tsx index 9bc238ad4..66899d256 100644 --- a/src/app/share/[slug]/[[...path]]/ShareNav.tsx +++ b/src/app/share/[slug]/[[...path]]/ShareNav.tsx @@ -3,6 +3,7 @@ import { useMessages, useNavigation, useShare } from '@/components/hooks'; import { AlignEndHorizontal, Clock, Eye, PanelLeft, Sheet, Tag, User } from '@/components/icons'; import { LanguageButton } from '@/components/input/LanguageButton'; import { PreferencesButton } from '@/components/input/PreferencesButton'; +import { allowShareFilter, excludeShareFilterParam, getShareTheme } from '@/lib/share'; import { Funnel, Gauge, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg'; import { buildPath } from '@/lib/url'; import { @@ -32,12 +33,19 @@ export function ShareNav({ const { t, labels } = useMessages(); const { pathname, query } = useNavigation(); const { slug, parameters } = share; + const allowFilter = allowShareFilter(parameters); + const shareTheme = getShareTheme(parameters); const renderPath = (path: string) => buildPath(`/share/${slug}${path}`, { - ...query, + ...Object.fromEntries( + Object.entries(query).filter(([key]) => { + return allowFilter || !excludeShareFilterParam(key); + }), + ), event: undefined, compare: undefined, + theme: undefined, view: undefined, unit: undefined, excludeBounce: undefined, @@ -202,13 +210,13 @@ export function ShareNav({ > {collapsed ? ( - + {!shareTheme && } ) : ( - + {!shareTheme && } diff --git a/src/app/share/[slug]/[[...path]]/SharePage.tsx b/src/app/share/[slug]/[[...path]]/SharePage.tsx index 122ad0a0b..d6660bd8a 100644 --- a/src/app/share/[slug]/[[...path]]/SharePage.tsx +++ b/src/app/share/[slug]/[[...path]]/SharePage.tsx @@ -22,6 +22,7 @@ import { PageBody } from '@/components/common/PageBody'; import { useShare } from '@/components/hooks'; import { MobileMenuButton } from '@/components/input/MobileMenuButton'; import { ENTITY_TYPE } from '@/lib/constants'; +import { getShareTheme } from '@/lib/share'; import { Column, Grid, Row, useTheme } from '@umami/react-zen'; import { usePathname, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -68,20 +69,20 @@ export function SharePage() { setNavCollapsed(value); }; const share = useShare(); - const { setTheme } = useTheme(); + const { initTheme } = useTheme(); const router = useRouter(); const pathname = usePathname(); const path = getSharePath(pathname); const { slug, websiteId, boardId, pixelId, linkId, parameters = {}, shareType } = share; + const shareTheme = getShareTheme(parameters); useEffect(() => { - const url = new URL(window?.location?.href); - const theme = url.searchParams.get('theme'); + initTheme(shareTheme, 'system'); - if (theme === 'light' || theme === 'dark') { - setTheme(theme); - } - }, [setTheme]); + return () => { + initTheme(undefined, 'system'); + }; + }, [shareTheme, initTheme]); // Check if the requested path is allowed const pageKey = path || ''; diff --git a/src/components/common/DataGrid.tsx b/src/components/common/DataGrid.tsx index 9a601a129..58978c35d 100644 --- a/src/components/common/DataGrid.tsx +++ b/src/components/common/DataGrid.tsx @@ -62,47 +62,47 @@ export function DataGrid({ const child = data ? (typeof children === 'function' ? children(data) : children) : null; return ( - - {allowSearch && ( - - - {renderActions?.()} - - )} - - {data && ( - <> + + + {allowSearch && ( + + + {renderActions?.()} + + )} + + {data && ( {isValidElement(child) ? cloneElement(child as ReactElement, { displayMode }) : child} - {showPager && ( - - - - )} - - )} - + )} + + + {showPager && ( + + + + )} ); } diff --git a/src/components/hooks/useFilterParameters.ts b/src/components/hooks/useFilterParameters.ts index 24211c386..b5900d465 100644 --- a/src/components/hooks/useFilterParameters.ts +++ b/src/components/hooks/useFilterParameters.ts @@ -1,27 +1,32 @@ import { useMemo } from 'react'; import { FILTER_COLUMNS } from '@/lib/constants'; +import { useShare } from './context/useShare'; import { useNavigation } from './useNavigation'; export function useFilterParameters({ includePagination = true }: { includePagination?: boolean } = {}) { const { query } = useNavigation(); + const share = useShare(); + const allowFilter = share?.parameters?.allowFilter !== false; return useMemo(() => { const filterParams: Record = {}; - for (const key of Object.keys(query)) { - const baseName = key.replace(/\d+$/, ''); - if (FILTER_COLUMNS[baseName]) { - filterParams[key] = query[key]; + if (allowFilter) { + for (const key of Object.keys(query)) { + const baseName = key.replace(/\d+$/, ''); + if (FILTER_COLUMNS[baseName]) { + filterParams[key] = query[key]; + } } } const params = { ...filterParams, search: query.search, - segment: query.segment, - cohort: query.cohort, - excludeBounce: query.excludeBounce, - match: query.match, + segment: allowFilter ? query.segment : undefined, + cohort: allowFilter ? query.cohort : undefined, + excludeBounce: allowFilter ? query.excludeBounce : undefined, + match: allowFilter ? query.match : undefined, } as Record; if (includePagination) { @@ -30,5 +35,5 @@ export function useFilterParameters({ includePagination = true }: { includePagin } return params; - }, [query, includePagination]); + }, [allowFilter, includePagination, query]); } diff --git a/src/components/hooks/useFilters.ts b/src/components/hooks/useFilters.ts index c50625deb..94e2f8936 100644 --- a/src/components/hooks/useFilters.ts +++ b/src/components/hooks/useFilters.ts @@ -1,5 +1,6 @@ import { FILTER_COLUMNS, OPERATORS } from '@/lib/constants'; import { safeDecodeURIComponent } from '@/lib/url'; +import { useShare } from './context/useShare'; import { useFields } from './useFields'; import { useMessages } from './useMessages'; import { useNavigation } from './useNavigation'; @@ -10,6 +11,8 @@ export function useFilters() { const { query } = useNavigation(); const { fields } = useFields(); const operatorLabels = useOperatorLabels(); + const share = useShare(); + const allowFilter = share?.parameters?.allowFilter !== false; const operators = [ { name: 'eq', type: 'string', label: t(labels.is) }, @@ -56,30 +59,32 @@ export function useFilters() { uuid: [OPERATORS.equals], }; - const filters = Object.keys(query).reduce((arr, key) => { - const baseName = key.replace(/\d+$/, ''); - if (FILTER_COLUMNS[baseName]) { - let operator = 'eq'; - let value = safeDecodeURIComponent(query[key]); - const label = fields.find(({ name }) => name === baseName)?.label; + const filters = allowFilter + ? Object.keys(query).reduce((arr, key) => { + const baseName = key.replace(/\d+$/, ''); + if (FILTER_COLUMNS[baseName]) { + let operator = 'eq'; + let value = safeDecodeURIComponent(query[key]); + const label = fields.find(({ name }) => name === baseName)?.label; - const match = value.match(/^([a-z]+)\.(.*)/); + const match = value.match(/^([a-z]+)\.(.*)/); - if (match) { - operator = match[1]; - value = match[2]; - } + if (match) { + operator = match[1]; + value = match[2]; + } - return arr.concat({ - name: key, - type: baseName, - operator, - value, - label, - }); - } - return arr; - }, []); + return arr.concat({ + name: key, + type: baseName, + operator, + value, + label, + }); + } + return arr; + }, []) + : []; const getFilters = (type: string) => { return ( diff --git a/src/components/input/ThemeModeSelector.tsx b/src/components/input/ThemeModeSelector.tsx new file mode 100644 index 000000000..ad4d6a920 --- /dev/null +++ b/src/components/input/ThemeModeSelector.tsx @@ -0,0 +1,36 @@ +import type { ReactNode } from 'react'; +import { Button, Icon, Row } from '@umami/react-zen'; +import { Monitor, Moon, Sun } from '@/components/icons'; + +export type ThemeMode = 'light' | 'dark' | 'system'; + +export function ThemeModeSelector({ + value, + onChange, + includeSystem = false, +}: { + value?: ThemeMode; + onChange: (value: ThemeMode) => void; + includeSystem?: boolean; +}) { + const options: { id: ThemeMode; icon: ReactNode; label: string }[] = [ + { id: 'light', icon: , label: 'Light' }, + ...(includeSystem ? [{ id: 'system' as const, icon: , label: 'System' }] : []), + { id: 'dark', icon: , label: 'Dark' }, + ]; + + return ( + + {options.map(({ id, icon, label }) => ( + + ))} + + ); +} diff --git a/src/components/messages.ts b/src/components/messages.ts index 72ce903cf..84ce2054d 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -190,6 +190,7 @@ export const labels: Record = { type: 'label.type', filter: 'label.filter', filters: 'label.filters', + filtersEnabled: 'label.filters-enabled', propertyFilter: 'label.property-filter', activityByProperty: 'label.activity-by-property', breakdown: 'label.breakdown', diff --git a/src/components/share/SimpleShareCreateForm.tsx b/src/components/share/SimpleShareCreateForm.tsx index 9ab850bcb..28ff801cc 100644 --- a/src/components/share/SimpleShareCreateForm.tsx +++ b/src/components/share/SimpleShareCreateForm.tsx @@ -1,6 +1,16 @@ -import { Button, Column, Form, FormField, FormSubmitButton, Row, TextField } from '@umami/react-zen'; +import { + Button, + Column, + Form, + FormField, + FormSubmitButton, + Row, + Switch, + TextField, +} from '@umami/react-zen'; import { useState } from 'react'; import { useApi, useMessages, useModified } from '@/components/hooks'; +import { ThemeModeSelector } from '@/components/input/ThemeModeSelector'; export function SimpleShareCreateForm({ createPath, @@ -15,15 +25,19 @@ export function SimpleShareCreateForm({ const { touch } = useModified(); const { t, labels, getErrorMessage } = useMessages(); const [isPending, setIsPending] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); - const handleSubmit = async (data: { name: string }) => { + const handleSubmit = async (data: { name: string; allowFilter?: boolean; theme?: string }) => { setIsPending(true); setError(null); try { await post(createPath, { name: data.name, + parameters: { + allowFilter: data.allowFilter ?? true, + theme: data.theme === 'system' ? undefined : data.theme, + }, }); touch('shares'); @@ -36,22 +50,45 @@ export function SimpleShareCreateForm({ }; return ( - - - - - - - {onCancel && ( - - )} - - {t(labels.add)} - - - + + {({ watch, setValue }) => ( + + + + + + + setValue('allowFilter', value, { shouldDirty: true })} + > + {t(labels.filtersEnabled)} + + + + setValue('theme', value, { shouldDirty: true })} + /> + + + + {onCancel && ( + + )} + + {t(labels.add)} + + + + )} ); } diff --git a/src/components/share/SimpleShareEditForm.tsx b/src/components/share/SimpleShareEditForm.tsx index 3c83fed52..6bdcf9300 100644 --- a/src/components/share/SimpleShareEditForm.tsx +++ b/src/components/share/SimpleShareEditForm.tsx @@ -7,10 +7,12 @@ import { Label, Loading, Row, + Switch, TextField, } from '@umami/react-zen'; import { useEffect, useState } from 'react'; import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; +import { ThemeModeSelector } from '@/components/input/ThemeModeSelector'; export function SimpleShareEditForm({ shareId, @@ -49,7 +51,7 @@ export function SimpleShareEditForm({ loadShare(); }, [get, modified, shareId]); - const handleSubmit = async (data: { name: string }) => { + const handleSubmit = async (data: { name: string; allowFilter?: boolean; theme?: string }) => { setIsPending(true); setError(null); @@ -57,7 +59,11 @@ export function SimpleShareEditForm({ await post(`/share/id/${shareId}`, { name: data.name, slug: share.slug, - parameters: share.parameters || {}, + parameters: { + ...(share.parameters || {}), + allowFilter: data.allowFilter ?? true, + theme: data.theme === 'system' ? undefined : data.theme, + }, }); touch('shares'); @@ -78,27 +84,50 @@ export function SimpleShareEditForm({
- - - - + {({ watch, setValue }) => ( + + + + + + + + + + + setValue('allowFilter', value, { shouldDirty: true })} + > + {t(labels.filtersEnabled)} + + + + setValue('theme', value, { shouldDirty: true })} + /> + + + + {onClose && ( + + )} + + {t(labels.save)} + + - - - - - {onClose && ( - - )} - - {t(labels.save)} - - - + )} ); } diff --git a/src/lib/share.ts b/src/lib/share.ts new file mode 100644 index 000000000..4fc20366c --- /dev/null +++ b/src/lib/share.ts @@ -0,0 +1,20 @@ +import { FILTER_COLUMNS } from './constants'; +import type { ShareParameters, ShareTheme } from './types'; + +const FILTER_QUERY_PARAMS = new Set(['cohort', 'excludeBounce', 'match', 'segment']); + +export function allowShareFilter(parameters?: ShareParameters | null) { + return parameters?.allowFilter !== false; +} + +export function getShareTheme(parameters?: ShareParameters | null): ShareTheme | undefined { + return parameters?.theme === 'light' || parameters?.theme === 'dark' + ? parameters.theme + : undefined; +} + +export function excludeShareFilterParam(key: string) { + const baseName = key.replace(/\d+$/, ''); + + return baseName in FILTER_COLUMNS || FILTER_QUERY_PARAMS.has(key); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 6c8bb92ff..d2e20712b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -27,9 +27,18 @@ export interface Auth { pixelIds?: string[]; linkId?: string; linkIds?: string[]; + parameters?: ShareParameters; }; } +export type ShareTheme = 'light' | 'dark'; + +export interface ShareParameters { + allowFilter?: boolean; + theme?: ShareTheme; + [key: string]: boolean | ShareTheme | undefined; +} + export interface PropertyFilter { propertyName: string; dataType: number;