From 3cb7fa34b00bf09e751ab0cef87c14afbfe07970 Mon Sep 17 00:00:00 2001 From: rkoh-rq <49420412+rkoh-rq@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:00:33 +0800 Subject: [PATCH 01/27] fix: quote "event" reserved keyword in journey queries Fixes PostgreSQL syntax error by quoting the "event" column alias. This was causing the journey query to fail. "event" is a reserved keyword in PostgreSQL. Added double quotes to treat it as an identifier rather than a keyword. Changes: - Quote "event" in PostgreSQL - Quote "event" in ClickHouse for consistency --- src/queries/sql/reports/getJourney.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/queries/sql/reports/getJourney.ts b/src/queries/sql/reports/getJourney.ts index e831a96d..9d927cfe 100644 --- a/src/queries/sql/reports/getJourney.ts +++ b/src/queries/sql/reports/getJourney.ts @@ -72,7 +72,7 @@ async function relationalQuery( for (let i = 1; i <= steps; i++) { const endQuery = i < steps ? ',' : ''; selectQuery += `s.e${i},`; - maxQuery += `\nmax(CASE WHEN event_number = ${i} THEN event ELSE NULL END) AS e${i}${endQuery}`; + maxQuery += `\nmax(CASE WHEN event_number = ${i} THEN "event" ELSE NULL END) AS e${i}${endQuery}`; groupByQuery += `s.e${i}${endQuery} `; } @@ -118,7 +118,7 @@ async function relationalQuery( select distinct visit_id, referrer_path, - coalesce(nullIf(event_name, ''), url_path) event, + coalesce(nullIf(event_name, ''), url_path) "event", row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number from website_event where website_id = {{websiteId::uuid}} @@ -182,7 +182,7 @@ async function clickhouseQuery( for (let i = 1; i <= steps; i++) { const endQuery = i < steps ? ',' : ''; selectQuery += `s.e${i},`; - maxQuery += `\nmax(CASE WHEN event_number = ${i} THEN event ELSE NULL END) AS e${i}${endQuery}`; + maxQuery += `\nmax(CASE WHEN event_number = ${i} THEN "event" ELSE NULL END) AS e${i}${endQuery}`; groupByQuery += `s.e${i}${endQuery} `; } @@ -227,7 +227,7 @@ async function clickhouseQuery( WITH events AS ( select distinct visit_id, - coalesce(nullIf(event_name, ''), url_path) event, + coalesce(nullIf(event_name, ''), url_path) "event", row_number() OVER (PARTITION BY visit_id ORDER BY created_at) AS event_number from website_event where website_id = {websiteId:UUID} From 1879c161eeba2591f72b4a9186d48f5836ca04a4 Mon Sep 17 00:00:00 2001 From: metaloozee Date: Sun, 9 Nov 2025 00:22:06 +0530 Subject: [PATCH 02/27] fix: Redirect loop on auth failure --- src/app/(main)/App.tsx | 8 ++------ src/app/logout/LogoutPage.tsx | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 32218d11..7700639d 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -9,18 +9,14 @@ import { MobileNav } from '@/app/(main)/MobileNav'; export function App({ children }) { const { user, isLoading, error } = useLoginQuery(); const config = useConfig(); - const { pathname, router } = useNavigation(); + const { pathname } = useNavigation(); if (isLoading || !config) { return ; } if (error) { - if (process.env.cloudMode) { - window.location.href = '/login'; - } else { - router.push('/login'); - } + window.location.href = '/login'; return null; } diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx index 909f35de..bd471796 100644 --- a/src/app/logout/LogoutPage.tsx +++ b/src/app/logout/LogoutPage.tsx @@ -13,7 +13,7 @@ export function LogoutPage() { async function logout() { await post('/auth/logout'); - router.push('/login'); + window.location.href = '/login'; } removeClientAuthToken(); From bf548c5acae58cc1420d3ae7e79127f3db654e81 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 21:19:38 -0800 Subject: [PATCH 03/27] Fix revenue bigInt but and case insensitive currency --- src/queries/sql/reports/getRevenue.ts | 38 +++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index e13106ce..5771bdef 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -41,6 +41,15 @@ async function relationalQuery( currency, }); + const joinQuery = filterQuery + ? `join website_event + on website_event.website_id = revenue.website_id + and website_event.session_id = revenue.session_id + and website_event.event_id = revenue.event_id + and website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}}` + : ''; + const chart = await rawQuery( ` select @@ -48,17 +57,12 @@ async function relationalQuery( ${getDateSQL('revenue.created_at', unit, timezone)} t, sum(revenue.revenue) y from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} ${cohortQuery} ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency like {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} group by x, t order by t @@ -72,19 +76,14 @@ async function relationalQuery( session.country as name, sum(revenue) value from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} join session on session.website_id = revenue.website_id and session.session_id = revenue.session_id ${cohortQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency = {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} group by session.country `, @@ -98,23 +97,18 @@ async function relationalQuery( count(distinct revenue.event_id) as count, count(distinct revenue.session_id) as unique_count from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} ${cohortQuery} ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency = {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} `, queryParams, ).then(result => result?.[0]); - total.average = total.count > 0 ? total.sum / total.count : 0; + total.average = total.count > 0 ? Number(total.sum) / Number(total.count) : 0; return { chart, country, total }; } From f30724629cb4a6b4c401c78e83085b7671e1fbe4 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 21:37:35 -0800 Subject: [PATCH 04/27] Fix null and string return types from getWebsiteStats --- src/queries/sql/getWebsiteStats.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/queries/sql/getWebsiteStats.ts b/src/queries/sql/getWebsiteStats.ts index 391d22be..4a4bef78 100644 --- a/src/queries/sql/getWebsiteStats.ts +++ b/src/queries/sql/getWebsiteStats.ts @@ -36,11 +36,11 @@ async function relationalQuery( return rawQuery( ` select - sum(t.c) as "pageviews", + cast(coalesce(sum(t.c), 0) as bigint) as "pageviews", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", - sum(case when t.c = 1 then 1 else 0 end) as "bounces", - sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" + coalesce(sum(case when t.c = 1 then 1 else 0 end), 0) as "bounces", + cast(coalesce(sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}), 0) as bigint) as "totaltime" from ( select website_event.session_id, From 9230f3cb7b18203614fe2856d216540916be785c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 22:03:06 -0800 Subject: [PATCH 05/27] manually include basePath --- src/app/(main)/App.tsx | 2 +- src/app/logout/LogoutPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 7700639d..ec08838d 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -16,7 +16,7 @@ export function App({ children }) { } if (error) { - window.location.href = '/login'; + window.location.href = `${process.env.basePath || ''}/login`; return null; } diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx index bd471796..d66d62a9 100644 --- a/src/app/logout/LogoutPage.tsx +++ b/src/app/logout/LogoutPage.tsx @@ -13,7 +13,7 @@ export function LogoutPage() { async function logout() { await post('/auth/logout'); - window.location.href = '/login'; + window.location.href = `${process.env.basePath || ''}/login`; } removeClientAuthToken(); From f3e246c64bf75093664472d0cae32a6067089327 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 23:58:20 -0800 Subject: [PATCH 06/27] fix hasdata queries, add hasData to website events, fix sessionactivity truncation, --- .../[websiteId]/events/EventsTable.tsx | 37 ++++++++++++++++++- .../[websiteId]/sessions/SessionActivity.tsx | 9 +++-- src/queries/sql/events/getEventData.ts | 28 +++++++------- src/queries/sql/events/getWebsiteEvents.ts | 6 ++- .../sql/sessions/getSessionActivity.ts | 4 +- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index e9e3e6a0..ea0edde1 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -1,11 +1,24 @@ -import { DataTable, DataColumn, Row, Text, DataTableProps, IconLabel } from '@umami/react-zen'; +import { + DataTable, + DataColumn, + Row, + Text, + DataTableProps, + IconLabel, + Button, + Dialog, + DialogTrigger, + Icon, + Popover, +} from '@umami/react-zen'; import { useFormat, useMessages, useNavigation } from '@/components/hooks'; import { Avatar } from '@/components/common/Avatar'; import Link from 'next/link'; -import { Eye } from '@/components/icons'; +import { Eye, FileText } from '@/components/icons'; import { Lightning } from '@/components/svg'; import { DateDistance } from '@/components/common/DateDistance'; import { TypeIcon } from '@/components/common/TypeIcon'; +import { EventData } from '@/components/metrics/EventData'; export function EventsTable(props: DataTableProps) { const { formatMessage, labels } = useMessages(); @@ -32,6 +45,7 @@ export function EventsTable(props: DataTableProps) { > {row.eventName || row.urlPath} + {row.hasData > 0 && } ); }} @@ -72,3 +86,22 @@ export function EventsTable(props: DataTableProps) { ); } + +const PropertiesButton = props => { + return ( + + + + + + + + + ); +}; diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx index b9f34e48..7bcf1b76 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx @@ -14,7 +14,7 @@ import { import { LoadingPanel } from '@/components/common/LoadingPanel'; import { Eye, FileText } from '@/components/icons'; import { Lightning } from '@/components/svg'; -import { useMessages, useSessionActivityQuery, useTimezone } from '@/components/hooks'; +import { useMessages, useMobile, useSessionActivityQuery, useTimezone } from '@/components/hooks'; import { EventData } from '@/components/metrics/EventData'; export function SessionActivity({ @@ -36,6 +36,7 @@ export function SessionActivity({ startDate, endDate, ); + const { isMobile } = useMobile(); let lastDay = null; return ( @@ -50,16 +51,16 @@ export function SessionActivity({ {showHeader && {formatTimezoneDate(createdAt, 'PPPP')}} - {formatTimezoneDate(createdAt, 'pp')} + {formatTimezoneDate(createdAt, 'pp')} {eventName ? : } - + {eventName ? formatMessage(labels.triggeredEvent) : formatMessage(labels.viewedPage)} - + {eventName || urlPath} {hasData > 0 && } diff --git a/src/queries/sql/events/getEventData.ts b/src/queries/sql/events/getEventData.ts index 42dc2040..269258a8 100644 --- a/src/queries/sql/events/getEventData.ts +++ b/src/queries/sql/events/getEventData.ts @@ -19,20 +19,20 @@ async function relationalQuery(websiteId: string, eventId: string) { return rawQuery( ` - select website_id as "websiteId", - session_id as "sessionId", - event_id as "eventId", - url_path as "urlPath", - event_name as "eventName", - data_key as "dataKey", - string_value as "stringValue", - number_value as "numberValue", - date_value as "dateValue", - data_type as "dataType", - created_at as "createdAt" + select event_data.website_id as "websiteId", + event_data.website_event_id as "eventId", + website_event.event_name as "eventName", + event_data.data_key as "dataKey", + event_data.string_value as "stringValue", + event_data.number_value as "numberValue", + event_data.date_value as "dateValue", + event_data.data_type as "dataType", + event_data.created_at as "createdAt" from event_data - website_id = {{websiteId::uuid}} - event_id = {{eventId::uuid}} + join website_event on website_event.event_id = event_data.website_event_id + and website_event.website_id = {{websiteId::uuid}} + where event_data.website_id = {{websiteId::uuid}} + and event_data.website_event_id = {{eventId::uuid}} `, { websiteId, eventId }, FUNCTION_NAME, @@ -45,9 +45,7 @@ async function clickhouseQuery(websiteId: string, eventId: string): Promise Date: Mon, 10 Nov 2025 01:07:11 -0800 Subject: [PATCH 07/27] fix realtime logs for mobile --- .../[websiteId]/realtime/RealtimeLog.tsx | 38 ++++++++++++++----- .../[websiteId]/realtime/RealtimePage.tsx | 5 ++- src/components/metrics/ListTable.tsx | 6 +-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx index 9ae19bf8..3dec340f 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx @@ -9,6 +9,7 @@ import { useCountryNames, useLocale, useMessages, + useMobile, useNavigation, useTimezone, useWebsite, @@ -40,6 +41,7 @@ export function RealtimeLog({ data }: { data: any }) { const { countryNames } = useCountryNames(locale); const [filter, setFilter] = useState(TYPE_ALL); const { updateParams } = useNavigation(); + const { isPhone } = useMobile(); const buttons = [ { @@ -123,12 +125,18 @@ export function RealtimeLog({ data }: { data: any }) { const row = logs[index]; return ( - - - - {getTime(row)} + + + + + + + {getTime(row)} + - {getDetail(row)} + + {getDetail(row)} + ); @@ -168,10 +176,22 @@ export function RealtimeLog({ data }: { data: any }) { return ( {formatMessage(labels.activity)} - - - - + {isPhone ? ( + <> + + + + + + + + ) : ( + + + + + )} + {logs?.length === 0 && } {logs?.length > 0 && ( diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx index 7f9ab608..0f9fa358 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx @@ -6,7 +6,7 @@ import { PageBody } from '@/components/common/PageBody'; import { Panel } from '@/components/common/Panel'; import { RealtimeChart } from '@/components/metrics/RealtimeChart'; import { WorldMap } from '@/components/metrics/WorldMap'; -import { useRealtimeQuery } from '@/components/hooks'; +import { useMobile, useRealtimeQuery } from '@/components/hooks'; import { RealtimeLog } from './RealtimeLog'; import { RealtimeHeader } from './RealtimeHeader'; import { RealtimePaths } from './RealtimePaths'; @@ -16,6 +16,7 @@ import { percentFilter } from '@/lib/filters'; export function RealtimePage({ websiteId }: { websiteId: string }) { const { data, isLoading, error } = useRealtimeQuery(websiteId); + const { isMobile } = useMobile(); if (isLoading || error) { return ; @@ -48,7 +49,7 @@ export function RealtimePage({ websiteId }: { websiteId: string }) { - + diff --git a/src/components/metrics/ListTable.tsx b/src/components/metrics/ListTable.tsx index 303556b0..e76e0174 100644 --- a/src/components/metrics/ListTable.tsx +++ b/src/components/metrics/ListTable.tsx @@ -57,7 +57,7 @@ export function ListTable({ showPercentage={showPercentage} change={renderChange ? renderChange(row, index) : null} currency={currency} - isMobile={isPhone} + isPhone={isPhone} /> ); }; @@ -101,7 +101,7 @@ const AnimatedRow = ({ animate, showPercentage = true, currency, - isMobile, + isPhone, }) => { const props = useSpring({ width: percent, @@ -120,7 +120,7 @@ const AnimatedRow = ({ gap > - + {label} From 49e1582c288bc5bc71f3f4e0482873b0930c1297 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 15:36:43 -0800 Subject: [PATCH 08/27] implement generateTimeSeries for eventsChart --- src/components/metrics/EventsChart.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index 7301faf4..246772b3 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -1,10 +1,11 @@ -import { useMemo, useState, useEffect } from 'react'; -import { colord } from 'colord'; import { BarChart, BarChartProps } from '@/components/charts/BarChart'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks'; import { renderDateLabels } from '@/lib/charts'; import { CHART_COLORS } from '@/lib/constants'; -import { LoadingPanel } from '@/components/common/LoadingPanel'; +import { generateTimeSeries } from '@/lib/date'; +import { colord } from 'colord'; +import { useCallback, useEffect, useMemo, useState } from 'react'; export interface EventsChartProps extends BarChartProps { websiteId: string; @@ -15,7 +16,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { locale } = useLocale(); + const { locale, dateLocale } = useLocale(); const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId); const [label, setLabel] = useState(focusLabel); @@ -37,7 +38,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { const color = colord(CHART_COLORS[index % CHART_COLORS.length]); return { label: key, - data: map[key], + data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale), lineTension: 0, backgroundColor: color.alpha(0.6).toRgbString(), borderColor: color.alpha(0.7).toRgbString(), @@ -54,6 +55,8 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { } }, [focusLabel]); + const renderXLabel = useCallback(renderDateLabels(unit, locale), [unit, locale]); + return ( {chartData && ( @@ -63,7 +66,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { maxDate={endDate} unit={unit} stacked={true} - renderXLabel={renderDateLabels(unit, locale)} + renderXLabel={renderXLabel} height="400px" /> )} From a1d6204373b13bb4675de3aacc49a650b24318be Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:24:51 -0800 Subject: [PATCH 09/27] add canonicalizeTimezone conversions Co-authored-by: Om Mishra --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 +++++++++++++-- src/lib/constants.ts | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 359bbc1f..16e12314 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc } = useTimezone(); + const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone, + timezone: canonicalizeTimezone(timezone), }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 0e1fe6cd..3770c26b 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG } from '@/lib/constants'; +import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,5 +34,16 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; + const canonicalizeTimezone = (timezone: string): string => { + return TIMEZONE_LEGACY[timezone] ?? timezone; + }; + + return { + timezone, + saveTimezone, + formatTimezoneDate, + toUtc, + fromUtc, + canonicalizeTimezone, + }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 50a25b8d..195fe1be 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,3 +658,24 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; + +export const TIMEZONE_LEGACY: Record = { + 'Asia/Batavia': 'Asia/Jakarta', + 'Asia/Calcutta': 'Asia/Kolkata', + 'Asia/Chongqing': 'Asia/Shanghai', + 'Asia/Harbin': 'Asia/Shanghai', + 'Asia/Jayapura': 'Asia/Pontianak', + 'Asia/Katmandu': 'Asia/Kathmandu', + 'Asia/Macao': 'Asia/Macau', + 'Asia/Rangoon': 'Asia/Yangon', + 'Asia/Saigon': 'Asia/Ho_Chi_Minh', + 'Europe/Kiev': 'Europe/Kyiv', + 'Europe/Zaporozhye': 'Europe/Kyiv', + 'Etc/UTC': 'UTC', + 'US/Arizona': 'America/Phoenix', + 'US/Central': 'America/Chicago', + 'US/Eastern': 'America/New_York', + 'US/Mountain': 'America/Denver', + 'US/Pacific': 'America/Los_Angeles', + 'US/Samoa': 'Pacific/Pago_Pago', +}; From 13ab84d50e7f8bfa4837ba8f7e48e97703b227dd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:26:06 -0800 Subject: [PATCH 10/27] Revert "add canonicalizeTimezone conversions" This reverts commit a1d6204373b13bb4675de3aacc49a650b24318be. --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 ++------------- src/lib/constants.ts | 21 --------------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 16e12314..359bbc1f 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); + const { timezone, toUtc } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone: canonicalizeTimezone(timezone), + timezone, }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 3770c26b..0e1fe6cd 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; +import { TIMEZONE_CONFIG } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,16 +34,5 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - const canonicalizeTimezone = (timezone: string): string => { - return TIMEZONE_LEGACY[timezone] ?? timezone; - }; - - return { - timezone, - saveTimezone, - formatTimezoneDate, - toUtc, - fromUtc, - canonicalizeTimezone, - }; + return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 195fe1be..50a25b8d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,24 +658,3 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; - -export const TIMEZONE_LEGACY: Record = { - 'Asia/Batavia': 'Asia/Jakarta', - 'Asia/Calcutta': 'Asia/Kolkata', - 'Asia/Chongqing': 'Asia/Shanghai', - 'Asia/Harbin': 'Asia/Shanghai', - 'Asia/Jayapura': 'Asia/Pontianak', - 'Asia/Katmandu': 'Asia/Kathmandu', - 'Asia/Macao': 'Asia/Macau', - 'Asia/Rangoon': 'Asia/Yangon', - 'Asia/Saigon': 'Asia/Ho_Chi_Minh', - 'Europe/Kiev': 'Europe/Kyiv', - 'Europe/Zaporozhye': 'Europe/Kyiv', - 'Etc/UTC': 'UTC', - 'US/Arizona': 'America/Phoenix', - 'US/Central': 'America/Chicago', - 'US/Eastern': 'America/New_York', - 'US/Mountain': 'America/Denver', - 'US/Pacific': 'America/Los_Angeles', - 'US/Samoa': 'Pacific/Pago_Pago', -}; From 839bf3898fa1f7bcc607529262788f294c273c7a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:27:45 -0800 Subject: [PATCH 11/27] add canonicalizeTimezone conversions Co-authored-by: Om Mishra --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 +++++++++++++-- src/lib/constants.ts | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 359bbc1f..16e12314 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc } = useTimezone(); + const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone, + timezone: canonicalizeTimezone(timezone), }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 0e1fe6cd..3770c26b 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG } from '@/lib/constants'; +import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,5 +34,16 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; + const canonicalizeTimezone = (timezone: string): string => { + return TIMEZONE_LEGACY[timezone] ?? timezone; + }; + + return { + timezone, + saveTimezone, + formatTimezoneDate, + toUtc, + fromUtc, + canonicalizeTimezone, + }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 50a25b8d..195fe1be 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,3 +658,24 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; + +export const TIMEZONE_LEGACY: Record = { + 'Asia/Batavia': 'Asia/Jakarta', + 'Asia/Calcutta': 'Asia/Kolkata', + 'Asia/Chongqing': 'Asia/Shanghai', + 'Asia/Harbin': 'Asia/Shanghai', + 'Asia/Jayapura': 'Asia/Pontianak', + 'Asia/Katmandu': 'Asia/Kathmandu', + 'Asia/Macao': 'Asia/Macau', + 'Asia/Rangoon': 'Asia/Yangon', + 'Asia/Saigon': 'Asia/Ho_Chi_Minh', + 'Europe/Kiev': 'Europe/Kyiv', + 'Europe/Zaporozhye': 'Europe/Kyiv', + 'Etc/UTC': 'UTC', + 'US/Arizona': 'America/Phoenix', + 'US/Central': 'America/Chicago', + 'US/Eastern': 'America/New_York', + 'US/Mountain': 'America/Denver', + 'US/Pacific': 'America/Los_Angeles', + 'US/Samoa': 'Pacific/Pago_Pago', +}; From 592f7c0ae728d21dbd1c3acd8cc85e87ffdb14f8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 10 Nov 2025 21:08:55 -0800 Subject: [PATCH 12/27] Added check for REDIS_URL. Closes #3677. --- docker-compose.yml | 1 - scripts/check-db.js | 4 ++++ src/tracker/index.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8c8a47a6..348c294c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,6 @@ services: - "3000:3000" environment: DATABASE_URL: postgresql://umami:umami@db:5432/umami - DATABASE_TYPE: postgresql APP_SECRET: replace-me-with-a-random-string depends_on: db: diff --git a/scripts/check-db.js b/scripts/check-db.js index 7c7daaa1..09577699 100644 --- a/scripts/check-db.js +++ b/scripts/check-db.js @@ -36,6 +36,10 @@ async function checkEnv() { } else { success('DATABASE_URL is defined.'); } + + if (process.env.REDIS_URL) { + success('REDIS_URL is defined.'); + } } async function checkConnection() { diff --git a/src/tracker/index.js b/src/tracker/index.js index c4c420b6..18b3ff89 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -45,7 +45,7 @@ if (excludeSearch) u.search = ''; if (excludeHash) u.hash = ''; return u.toString(); - } catch (e) { + } catch { return raw; } }; From 14f3db550bb6705b1c5e4b92409ea0d5eb2ff3a4 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Tue, 11 Nov 2025 10:32:31 +0100 Subject: [PATCH 13/27] Use raw query with on conflict in createSession --- src/app/api/send/route.ts | 1 + src/queries/sql/sessions/createSession.ts | 75 ++++++++++++----------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index f5e00c8a..2c2085bf 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -146,6 +146,7 @@ export async function POST(request: Request) { region, city, distinctId: id, + createdAt, }); } diff --git a/src/queries/sql/sessions/createSession.ts b/src/queries/sql/sessions/createSession.ts index 958754f1..b5106a54 100644 --- a/src/queries/sql/sessions/createSession.ts +++ b/src/queries/sql/sessions/createSession.ts @@ -1,41 +1,44 @@ import { Prisma } from '@/generated/prisma/client'; import prisma from '@/lib/prisma'; -export async function createSession(data: Prisma.SessionCreateInput) { - const { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - } = data; +const FUNCTION_NAME = 'createSession'; - try { - return await prisma.client.session.create({ - data: { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - }, - }); - } catch (e: any) { - if (e.message.toLowerCase().includes('unique constraint')) { - return null; - } - throw e; - } +export async function createSession(data: Prisma.SessionCreateInput) { + const { rawQuery } = prisma; + + await rawQuery( + ` + insert into session ( + session_id, + website_id, + browser, + os, + device, + screen, + language, + country, + region, + city, + distinct_id, + created_at + ) + values ( + {{id}}, + {{websiteId}}, + {{browser}}, + {{os}}, + {{device}}, + {{screen}}, + {{language}}, + {{country}}, + {{region}}, + {{city}}, + {{distinctId}}, + {{createdAt}} + ) + on conflict (session_id) do nothing + `, + data, + FUNCTION_NAME, + ); } From 30781430c5ef51c6d4d79b620ffa4f34293bf9d4 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:13:25 -0800 Subject: [PATCH 14/27] remove timezone from realtime. Closes #3700 --- src/app/api/realtime/[websiteId]/route.ts | 8 +------- src/components/hooks/queries/useRealtimeQuery.ts | 6 ++---- src/lib/clickhouse.ts | 2 +- src/queries/sql/pageviews/getPageviewStats.ts | 2 +- src/queries/sql/sessions/getSessionStats.ts | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/app/api/realtime/[websiteId]/route.ts b/src/app/api/realtime/[websiteId]/route.ts index 054a1241..eaa0bbd8 100644 --- a/src/app/api/realtime/[websiteId]/route.ts +++ b/src/app/api/realtime/[websiteId]/route.ts @@ -1,21 +1,15 @@ import { REALTIME_RANGE } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { timezoneParam } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getRealtimeData } from '@/queries/sql'; import { startOfMinute, subMinutes } from 'date-fns'; -import z from 'zod'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - timezone: timezoneParam, - }); - - const { auth, query, error } = await parseRequest(request, schema); + const { auth, query, error } = await parseRequest(request); if (error) { return error(); diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index 582fe9fa..ed388b03 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,4 +1,3 @@ -import { useTimezone } from '@/components/hooks/useTimezone'; import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; @@ -24,11 +23,10 @@ export interface RealtimeData { export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); - const { timezone } = useTimezone(); const { data, isLoading, error } = useQuery({ - queryKey: ['realtime', { websiteId, timezone }], + queryKey: ['realtime', { websiteId }], queryFn: async () => { - return get(`/realtime/${websiteId}`, { timezone }); + return get(`/realtime/${websiteId}`); }, enabled: !!websiteId, refetchInterval: REALTIME_INTERVAL, diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 5e6e7133..4880e122 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) { if (timezone) { - return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`; + return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`; } return `toDateTime(date_trunc('${unit}', ${field}))`; } diff --git a/src/queries/sql/pageviews/getPageviewStats.ts b/src/queries/sql/pageviews/getPageviewStats.ts index 7dd9ac93..a6619e87 100644 --- a/src/queries/sql/pageviews/getPageviewStats.ts +++ b/src/queries/sql/pageviews/getPageviewStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, diff --git a/src/queries/sql/sessions/getSessionStats.ts b/src/queries/sql/sessions/getSessionStats.ts index 07582d39..ea93b226 100644 --- a/src/queries/sql/sessions/getSessionStats.ts +++ b/src/queries/sql/sessions/getSessionStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, From bf498d92392571fbf025011c5d30627c5d30ff42 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:45:41 -0800 Subject: [PATCH 15/27] add RealtimeData to types --- .../hooks/queries/useRealtimeQuery.ts | 21 +------------------ src/lib/types.ts | 20 ++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index ed388b03..9e20da04 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,25 +1,6 @@ import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; - -export interface RealtimeData { - countries: Record; - events: any[]; - pageviews: any[]; - referrers: Record; - timestamp: number; - series: { - views: any[]; - visitors: any[]; - }; - totals: { - views: number; - visitors: number; - events: number; - countries: number; - }; - urls: Record; - visitors: any[]; -} +import { RealtimeData } from '@/lib/types'; export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); diff --git a/src/lib/types.ts b/src/lib/types.ts index 1237f519..e5d4ecc5 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -116,3 +116,23 @@ export interface PageResult { sortDescending?: boolean; search?: string; } + +export interface RealtimeData { + countries: Record; + events: any[]; + pageviews: any[]; + referrers: Record; + timestamp: number; + series: { + views: any[]; + visitors: any[]; + }; + totals: { + views: number; + visitors: number; + events: number; + countries: number; + }; + urls: Record; + visitors: any[]; +} From 678a2ccdf31d20166f3830c9c267d678b0fbf01b Mon Sep 17 00:00:00 2001 From: Prince EKPINSE Date: Wed, 12 Nov 2025 00:08:36 +0100 Subject: [PATCH 16/27] fix: correct autocomplete attributes to enable password manager autofill --- src/app/login/LoginForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx index c1c2c431..8a859e04 100644 --- a/src/app/login/LoginForm.tsx +++ b/src/app/login/LoginForm.tsx @@ -44,7 +44,7 @@ export function LoginForm() { name="username" rules={{ required: formatMessage(labels.required) }} > - + - + Date: Wed, 12 Nov 2025 00:15:05 +0100 Subject: [PATCH 17/27] fix: enable password manager autofill on login form (#3735) --- src/app/login/LoginForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx index 8a859e04..b151b94e 100644 --- a/src/app/login/LoginForm.tsx +++ b/src/app/login/LoginForm.tsx @@ -46,6 +46,7 @@ export function LoginForm() { > + Date: Wed, 12 Nov 2025 17:51:19 +0800 Subject: [PATCH 18/27] feat(geo): add support for direct .mmdb URL and custom GEO_DATABASE_URL - Support GEO_DATABASE_URL environment variable for custom database URL - Auto-detect .mmdb files and skip decompression - Maintain backward compatibility with tar.gz archives --- scripts/build-geo.js | 90 ++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/scripts/build-geo.js b/scripts/build-geo.js index b6ac42c4..379f6e70 100644 --- a/scripts/build-geo.js +++ b/scripts/build-geo.js @@ -13,12 +13,18 @@ if (process.env.VERCEL && !process.env.BUILD_GEO) { const db = 'GeoLite2-City'; -let url = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`; +// Support custom URL via environment variable +let url = process.env.GEO_DATABASE_URL; -if (process.env.MAXMIND_LICENSE_KEY) { - url = - `https://download.maxmind.com/app/geoip_download` + - `?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`; +// Fallback to default URLs if not provided +if (!url) { + if (process.env.MAXMIND_LICENSE_KEY) { + url = + `https://download.maxmind.com/app/geoip_download` + + `?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`; + } else { + url = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`; + } } const dest = path.resolve(process.cwd(), 'geo'); @@ -27,30 +33,66 @@ if (!fs.existsSync(dest)) { fs.mkdirSync(dest); } -const download = url => +// Check if URL points to a direct .mmdb file (already extracted) +const isDirectMmdb = url.endsWith('.mmdb'); + +// Download handler for compressed tar.gz files +const downloadCompressed = url => new Promise(resolve => { https.get(url, res => { resolve(res.pipe(zlib.createGunzip({})).pipe(tar.t())); }); }); -download(url).then( - res => - new Promise((resolve, reject) => { - res.on('entry', entry => { - if (entry.path.endsWith('.mmdb')) { - const filename = path.join(dest, path.basename(entry.path)); - entry.pipe(fs.createWriteStream(filename)); - - console.log('Saved geo database:', filename); - } - }); - - res.on('error', e => { - reject(e); - }); - res.on('finish', () => { +// Download handler for direct .mmdb files +const downloadDirect = url => + new Promise((resolve, reject) => { + https.get(url, res => { + const filename = path.join(dest, path.basename(url)); + const fileStream = fs.createWriteStream(filename); + + res.pipe(fileStream); + + fileStream.on('finish', () => { + fileStream.close(); + console.log('Saved geo database:', filename); resolve(); }); - }), -); + + fileStream.on('error', e => { + reject(e); + }); + }); + }); + +// Execute download based on file type +if (isDirectMmdb) { + downloadDirect(url).catch(e => { + console.error('Failed to download geo database:', e); + process.exit(1); + }); +} else { + downloadCompressed(url).then( + res => + new Promise((resolve, reject) => { + res.on('entry', entry => { + if (entry.path.endsWith('.mmdb')) { + const filename = path.join(dest, path.basename(entry.path)); + entry.pipe(fs.createWriteStream(filename)); + + console.log('Saved geo database:', filename); + } + }); + + res.on('error', e => { + reject(e); + }); + res.on('finish', () => { + resolve(); + }); + }), + ).catch(e => { + console.error('Failed to download geo database:', e); + process.exit(1); + }); +} From e13362bfec5d4b2e6d33f56f4277bcb0d903036e Mon Sep 17 00:00:00 2001 From: Mintimate Date: Wed, 12 Nov 2025 19:18:44 +0800 Subject: [PATCH 19/27] feat(geo): add redirect support for direct .mmdb downloads --- scripts/build-geo.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/build-geo.js b/scripts/build-geo.js index 379f6e70..f0aedeba 100644 --- a/scripts/build-geo.js +++ b/scripts/build-geo.js @@ -45,10 +45,16 @@ const downloadCompressed = url => }); // Download handler for direct .mmdb files -const downloadDirect = url => +const downloadDirect = (url, originalUrl) => new Promise((resolve, reject) => { https.get(url, res => { - const filename = path.join(dest, path.basename(url)); + // Follow redirects + if (res.statusCode === 301 || res.statusCode === 302) { + downloadDirect(res.headers.location, originalUrl || url).then(resolve).catch(reject); + return; + } + + const filename = path.join(dest, path.basename(originalUrl || url)); const fileStream = fs.createWriteStream(filename); res.pipe(fileStream); From 8a66603d324d4d1f4e5515699ebdd46021981baf Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 12 Nov 2025 16:39:58 -0800 Subject: [PATCH 20/27] Responsive fixes. --- package.json | 2 +- pnpm-lock.yaml | 259 ++++++++++-------- .../(reports)/attribution/Attribution.tsx | 4 +- .../(reports)/attribution/AttributionPage.tsx | 2 +- .../(reports)/breakdown/Breakdown.tsx | 98 ++++--- .../(reports)/breakdown/BreakdownPage.tsx | 9 +- .../(reports)/retention/Retention.tsx | 105 ++++--- .../[websiteId]/(reports)/utm/UTM.tsx | 2 +- .../[websiteId]/ExpandedViewModal.tsx | 1 + .../websites/[websiteId]/WebsiteControls.tsx | 4 +- .../[websiteId]/WebsiteExpandedView.tsx | 16 +- .../websites/[websiteId]/WebsiteHeader.tsx | 8 +- src/components/common/PageHeader.tsx | 14 +- src/components/input/WebsiteDateFilter.tsx | 5 +- src/components/metrics/MetricsBar.tsx | 2 +- src/lib/ip.ts | 2 +- 16 files changed, 302 insertions(+), 231 deletions(-) diff --git a/package.json b/package.json index 4c2f1df8..82b1be07 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@react-spring/web": "^10.0.3", "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.90.5", - "@umami/react-zen": "^0.203.0", + "@umami/react-zen": "^0.206.0", "@umami/redis-client": "^0.29.0", "bcryptjs": "^3.0.2", "chalk": "^5.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ca04cd9..a7e9b49c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.90.5 version: 5.90.5(react@19.2.0) '@umami/react-zen': - specifier: ^0.203.0 - version: 0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + specifier: ^0.206.0 + version: 0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) '@umami/redis-client': specifier: ^0.29.0 version: 0.29.0 @@ -878,6 +878,9 @@ packages: '@emnapi/runtime@1.5.0': resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/runtime@1.7.0': + resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} + '@emnapi/wasi-threads@1.0.4': resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} @@ -1163,8 +1166,8 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-darwin-arm64@0.34.4': - resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==} + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] @@ -1175,8 +1178,8 @@ packages: cpu: [x64] os: [darwin] - '@img/sharp-darwin-x64@0.34.4': - resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==} + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] @@ -1186,8 +1189,8 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.3': - resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==} + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] @@ -1196,8 +1199,8 @@ packages: cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.3': - resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==} + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] @@ -1206,8 +1209,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm64@1.2.3': - resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==} + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] @@ -1216,8 +1219,8 @@ packages: cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.3': - resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==} + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] @@ -1226,18 +1229,23 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.2.3': - resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==} + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.3': - resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==} + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] @@ -1246,8 +1254,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.3': - resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==} + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] @@ -1256,8 +1264,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.3': - resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==} + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] @@ -1266,8 +1274,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.3': - resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==} + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] @@ -1277,8 +1285,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linux-arm64@0.34.4': - resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==} + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] @@ -1289,8 +1297,8 @@ packages: cpu: [arm] os: [linux] - '@img/sharp-linux-arm@0.34.4': - resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==} + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] @@ -1301,20 +1309,26 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-linux-ppc64@0.34.4': - resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==} + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + '@img/sharp-linux-s390x@0.34.3': resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.34.4': - resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==} + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] @@ -1325,8 +1339,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linux-x64@0.34.4': - resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==} + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] @@ -1337,8 +1351,8 @@ packages: cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.4': - resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==} + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] @@ -1349,8 +1363,8 @@ packages: cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.4': - resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==} + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] @@ -1360,8 +1374,8 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-wasm32@0.34.4': - resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==} + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] @@ -1371,8 +1385,8 @@ packages: cpu: [arm64] os: [win32] - '@img/sharp-win32-arm64@0.34.4': - resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==} + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] @@ -1383,8 +1397,8 @@ packages: cpu: [ia32] os: [win32] - '@img/sharp-win32-ia32@0.34.4': - resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==} + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] @@ -1395,8 +1409,8 @@ packages: cpu: [x64] os: [win32] - '@img/sharp-win32-x64@0.34.4': - resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==} + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -2921,8 +2935,8 @@ packages: resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@umami/react-zen@0.203.0': - resolution: {integrity: sha512-lgGUapA0zDbLu63GINaEPndIsT8ry85vE316AWU/EEH3qYDBNscetcBfZFr+DTD/c5eLKy9OxmMwIpbs7k+/UA==} + '@umami/react-zen@0.206.0': + resolution: {integrity: sha512-9XM3Oj1akdyuwkMT1SldrJOyrMACP9TLJApZ/9ocmPuET4B7vpPxRoxv8OpEVlBaDw5nmlJfIvefsNMBLt1OQg==} '@umami/redis-client@0.29.0': resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==} @@ -3393,8 +3407,8 @@ packages: caniuse-lite@1.0.30001741: resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} - caniuse-lite@1.0.30001751: - resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -6540,8 +6554,8 @@ packages: peerDependencies: react: '>=16.13.1' - react-hook-form@7.65.0: - resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==} + react-hook-form@7.66.0: + resolution: {integrity: sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -6846,8 +6860,8 @@ packages: resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.4: - resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@1.2.0: @@ -8184,6 +8198,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.7.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.0.4': dependencies: tslib: 2.8.1 @@ -8443,9 +8462,9 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.0 optional: true - '@img/sharp-darwin-arm64@0.34.4': + '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.3 + '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true '@img/sharp-darwin-x64@0.34.3': @@ -8453,63 +8472,66 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.2.0 optional: true - '@img/sharp-darwin-x64@0.34.4': + '@img/sharp-darwin-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.3 + '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true '@img/sharp-libvips-darwin-arm64@1.2.0': optional: true - '@img/sharp-libvips-darwin-arm64@1.2.3': + '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true '@img/sharp-libvips-darwin-x64@1.2.0': optional: true - '@img/sharp-libvips-darwin-x64@1.2.3': + '@img/sharp-libvips-darwin-x64@1.2.4': optional: true '@img/sharp-libvips-linux-arm64@1.2.0': optional: true - '@img/sharp-libvips-linux-arm64@1.2.3': + '@img/sharp-libvips-linux-arm64@1.2.4': optional: true '@img/sharp-libvips-linux-arm@1.2.0': optional: true - '@img/sharp-libvips-linux-arm@1.2.3': + '@img/sharp-libvips-linux-arm@1.2.4': optional: true '@img/sharp-libvips-linux-ppc64@1.2.0': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.3': + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true '@img/sharp-libvips-linux-s390x@1.2.0': optional: true - '@img/sharp-libvips-linux-s390x@1.2.3': + '@img/sharp-libvips-linux-s390x@1.2.4': optional: true '@img/sharp-libvips-linux-x64@1.2.0': optional: true - '@img/sharp-libvips-linux-x64@1.2.3': + '@img/sharp-libvips-linux-x64@1.2.4': optional: true '@img/sharp-libvips-linuxmusl-arm64@1.2.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.3': + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true '@img/sharp-libvips-linuxmusl-x64@1.2.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.3': + '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true '@img/sharp-linux-arm64@0.34.3': @@ -8517,9 +8539,9 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.0 optional: true - '@img/sharp-linux-arm64@0.34.4': + '@img/sharp-linux-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.3 + '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true '@img/sharp-linux-arm@0.34.3': @@ -8527,9 +8549,9 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.2.0 optional: true - '@img/sharp-linux-arm@0.34.4': + '@img/sharp-linux-arm@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.3 + '@img/sharp-libvips-linux-arm': 1.2.4 optional: true '@img/sharp-linux-ppc64@0.34.3': @@ -8537,9 +8559,14 @@ snapshots: '@img/sharp-libvips-linux-ppc64': 1.2.0 optional: true - '@img/sharp-linux-ppc64@0.34.4': + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.3 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true '@img/sharp-linux-s390x@0.34.3': @@ -8547,9 +8574,9 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.0 optional: true - '@img/sharp-linux-s390x@0.34.4': + '@img/sharp-linux-s390x@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.3 + '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true '@img/sharp-linux-x64@0.34.3': @@ -8557,9 +8584,9 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.0 optional: true - '@img/sharp-linux-x64@0.34.4': + '@img/sharp-linux-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.3 + '@img/sharp-libvips-linux-x64': 1.2.4 optional: true '@img/sharp-linuxmusl-arm64@0.34.3': @@ -8567,9 +8594,9 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 optional: true - '@img/sharp-linuxmusl-arm64@0.34.4': + '@img/sharp-linuxmusl-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true '@img/sharp-linuxmusl-x64@0.34.3': @@ -8577,9 +8604,9 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.0 optional: true - '@img/sharp-linuxmusl-x64@0.34.4': + '@img/sharp-linuxmusl-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.3 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true '@img/sharp-wasm32@0.34.3': @@ -8587,27 +8614,27 @@ snapshots: '@emnapi/runtime': 1.5.0 optional: true - '@img/sharp-wasm32@0.34.4': + '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.5.0 + '@emnapi/runtime': 1.7.0 optional: true '@img/sharp-win32-arm64@0.34.3': optional: true - '@img/sharp-win32-arm64@0.34.4': + '@img/sharp-win32-arm64@0.34.5': optional: true '@img/sharp-win32-ia32@0.34.3': optional: true - '@img/sharp-win32-ia32@0.34.4': + '@img/sharp-win32-ia32@0.34.5': optional: true '@img/sharp-win32-x64@0.34.3': optional: true - '@img/sharp-win32-x64@0.34.4': + '@img/sharp-win32-x64@0.34.5': optional: true '@internationalized/date@3.10.0': @@ -10670,7 +10697,7 @@ snapshots: '@typescript-eslint/types': 8.46.2 eslint-visitor-keys: 4.2.1 - '@umami/react-zen@0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))': + '@umami/react-zen@0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.8 '@internationalized/date': 3.10.0 @@ -10684,7 +10711,7 @@ snapshots: react: 19.2.0 react-aria-components: 1.13.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-dom: 19.2.0(react@19.2.0) - react-hook-form: 7.65.0(react@19.2.0) + react-hook-form: 7.66.0(react@19.2.0) react-icons: 5.5.0(react@19.2.0) thenby: 1.3.4 zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) @@ -11215,7 +11242,7 @@ snapshots: caniuse-lite@1.0.30001741: {} - caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001754: {} caseless@0.12.0: {} @@ -13902,7 +13929,7 @@ snapshots: dependencies: '@next/env': 15.5.6 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001751 + caniuse-lite: 1.0.30001754 postcss: 8.4.31 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) @@ -13917,7 +13944,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.5.6 '@next/swc-win32-x64-msvc': 15.5.6 babel-plugin-react-compiler: 19.1.0-rc.2 - sharp: 0.34.4 + sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -14839,7 +14866,7 @@ snapshots: '@babel/runtime': 7.28.3 react: 19.2.0 - react-hook-form@7.65.0(react@19.2.0): + react-hook-form@7.66.0(react@19.2.0): dependencies: react: 19.2.0 @@ -15266,34 +15293,36 @@ snapshots: '@img/sharp-win32-x64': 0.34.3 optional: true - sharp@0.34.4: + sharp@0.34.5: dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.4 - '@img/sharp-darwin-x64': 0.34.4 - '@img/sharp-libvips-darwin-arm64': 1.2.3 - '@img/sharp-libvips-darwin-x64': 1.2.3 - '@img/sharp-libvips-linux-arm': 1.2.3 - '@img/sharp-libvips-linux-arm64': 1.2.3 - '@img/sharp-libvips-linux-ppc64': 1.2.3 - '@img/sharp-libvips-linux-s390x': 1.2.3 - '@img/sharp-libvips-linux-x64': 1.2.3 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 - '@img/sharp-libvips-linuxmusl-x64': 1.2.3 - '@img/sharp-linux-arm': 0.34.4 - '@img/sharp-linux-arm64': 0.34.4 - '@img/sharp-linux-ppc64': 0.34.4 - '@img/sharp-linux-s390x': 0.34.4 - '@img/sharp-linux-x64': 0.34.4 - '@img/sharp-linuxmusl-arm64': 0.34.4 - '@img/sharp-linuxmusl-x64': 0.34.4 - '@img/sharp-wasm32': 0.34.4 - '@img/sharp-win32-arm64': 0.34.4 - '@img/sharp-win32-ia32': 0.34.4 - '@img/sharp-win32-x64': 0.34.4 + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 optional: true shebang-command@1.2.0: diff --git a/src/app/(main)/websites/[websiteId]/(reports)/attribution/Attribution.tsx b/src/app/(main)/websites/[websiteId]/(reports)/attribution/Attribution.tsx index af947df8..f014bbbe 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/attribution/Attribution.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/attribution/Attribution.tsx @@ -95,7 +95,7 @@ export function Attribution({ })} - + @@ -104,7 +104,7 @@ export function Attribution({ - + diff --git a/src/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage.tsx b/src/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage.tsx index 497913b7..9fe9012b 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage.tsx @@ -17,7 +17,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) { return ( - +