From d1ecd6245749dced2a02be47098549eac747aa6c Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Tue, 13 May 2025 15:09:34 -0700 Subject: [PATCH] [TOOL-4489] Default analytics range to 30 days and save preference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change default range to last 30 days for all analytics views - Add localStorage persistence for user's selected range preference - Add proper error handling with invalid duration ID - Fix separator key warning by adding unique keys 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/@/components/blocks/SidebarLayout.tsx | 2 +- .../components/ProjectSidebarLayout.tsx | 6 +- .../connect/universal-bridge/page.tsx | 1 + .../analytics/date-range-selector.tsx | 2 +- .../components/PayAnalyticsFilter.tsx | 83 +++++++++++++++++-- apps/dashboard/src/lib/time.ts | 4 +- 6 files changed, 89 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx b/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx index d0b5b0fb23b..49e1a69b4ba 100644 --- a/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx +++ b/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx @@ -137,7 +137,7 @@ function RenderSidebarGroup(props: { } if ("separator" in link) { - return ; + return ; } return ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx index cecd6ec482d..861d72d1f6e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/components/ProjectSidebarLayout.tsx @@ -50,9 +50,13 @@ export function ProjectSidebarLayout(props: { tracking: tracking("account-abstraction"), }, { - label: "Universal Bridge", href: `${layoutPath}/connect/universal-bridge`, icon: PayIcon, + label: ( + + Universal Bridge New + + ), tracking: tracking("universal-bridge"), }, { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/universal-bridge/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/universal-bridge/page.tsx index 94920b9f74c..ac2cec604ae 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/universal-bridge/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/connect/universal-bridge/page.tsx @@ -32,6 +32,7 @@ export default async function Page(props: { from: searchParams.from, to: searchParams.to, interval: searchParams.interval, + defaultRange: "last-30", }); return ( diff --git a/apps/dashboard/src/components/analytics/date-range-selector.tsx b/apps/dashboard/src/components/analytics/date-range-selector.tsx index d579ed9b0ae..03389d12f55 100644 --- a/apps/dashboard/src/components/analytics/date-range-selector.tsx +++ b/apps/dashboard/src/components/analytics/date-range-selector.tsx @@ -80,7 +80,7 @@ export function DateRangeSelector(props: { export function getLastNDaysRange(id: DurationId) { const durationInfo = durationPresets.find((preset) => preset.id === id); if (!durationInfo) { - throw new Error("Invalid duration id"); + throw new Error(`Invalid duration id: ${id}`); } const todayDate = new Date(Date.now() + 1000 * 60 * 60 * 24); // add 1 day to avoid timezone issues diff --git a/apps/dashboard/src/components/pay/PayAnalytics/components/PayAnalyticsFilter.tsx b/apps/dashboard/src/components/pay/PayAnalytics/components/PayAnalyticsFilter.tsx index 9d6d2e59655..137d533ee1d 100644 --- a/apps/dashboard/src/components/pay/PayAnalytics/components/PayAnalyticsFilter.tsx +++ b/apps/dashboard/src/components/pay/PayAnalytics/components/PayAnalyticsFilter.tsx @@ -1,7 +1,11 @@ "use client"; import { normalizeTimeISOString } from "@/lib/time"; -import { DateRangeSelector } from "components/analytics/date-range-selector"; +import { useQuery } from "@tanstack/react-query"; +import { + DateRangeSelector, + type DurationId, +} from "components/analytics/date-range-selector"; import { IntervalSelector } from "components/analytics/interval-selector"; import { getUniversalBridgeFiltersFromSearchParams } from "lib/time"; import { @@ -9,28 +13,91 @@ import { useSetResponsiveSearchParams, } from "responsive-rsc"; +const STORAGE_KEY = "thirdweb:ub-analytics-range"; + +type SavedRange = { + rangeType: string; + interval: "day" | "week"; +}; + +type SearchParams = { + from?: string; + to?: string; + interval?: "day" | "week"; +}; + export function PayAnalyticsFilter() { const responsiveSearchParams = useResponsiveSearchParams(); const setResponsiveSearchParams = useSetResponsiveSearchParams(); + // Load saved range from localStorage using useQuery + useQuery({ + queryKey: [ + "savedRange", + responsiveSearchParams.from, + responsiveSearchParams.to, + ], + queryFn: () => { + const savedRangeString = localStorage.getItem(STORAGE_KEY); + if (savedRangeString) { + try { + const savedRange = JSON.parse(savedRangeString) as SavedRange; + // Get the current range based on the saved range type + const { range } = getUniversalBridgeFiltersFromSearchParams({ + from: undefined, + to: undefined, + interval: savedRange.interval, + defaultRange: (savedRange.rangeType || "last-30") as DurationId, + }); + + setResponsiveSearchParams((v) => ({ + ...v, + from: normalizeTimeISOString(range.from), + to: normalizeTimeISOString(range.to), + interval: savedRange.interval, + })); + } catch (e) { + localStorage.removeItem(STORAGE_KEY); + console.error("Failed to parse saved range:", e); + } + } + return null; + }, + enabled: !responsiveSearchParams.from && !responsiveSearchParams.to, + }); + const { range, interval } = getUniversalBridgeFiltersFromSearchParams({ from: responsiveSearchParams.from, to: responsiveSearchParams.to, interval: responsiveSearchParams.interval, + defaultRange: "last-30", }); + const saveToLocalStorage = (params: { + rangeType: string; + interval: "day" | "week"; + }) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(params)); + }; + return (
{ - setResponsiveSearchParams((v) => { - return { + setResponsiveSearchParams((v: SearchParams) => { + const newParams = { ...v, from: normalizeTimeISOString(newRange.from), to: normalizeTimeISOString(newRange.to), }; + // Save to localStorage + saveToLocalStorage({ + rangeType: newRange.type || "last-30", + interval: newParams.interval || "day", + }); + return newParams; }); }} /> @@ -38,11 +105,17 @@ export function PayAnalyticsFilter() { { - setResponsiveSearchParams((v) => { - return { + setResponsiveSearchParams((v: SearchParams) => { + const newParams = { ...v, interval: newInterval, }; + // Save to localStorage + saveToLocalStorage({ + rangeType: range.type || "last-30", + interval: newInterval, + }); + return newParams; }); }} /> diff --git a/apps/dashboard/src/lib/time.ts b/apps/dashboard/src/lib/time.ts index 900b9e99204..6b06f0e1395 100644 --- a/apps/dashboard/src/lib/time.ts +++ b/apps/dashboard/src/lib/time.ts @@ -1,4 +1,5 @@ import { getFiltersFromSearchParams } from "@/lib/time"; +import type { DurationId } from "../components/analytics/date-range-selector"; export function getNebulaFiltersFromSearchParams(params: { from: string | undefined | string[]; @@ -17,11 +18,12 @@ export function getUniversalBridgeFiltersFromSearchParams(params: { from: string | undefined | string[]; to: string | undefined | string[]; interval: string | undefined | string[]; + defaultRange: DurationId; }) { return getFiltersFromSearchParams({ from: params.from, to: params.to, interval: params.interval, - defaultRange: "last-120", + defaultRange: params.defaultRange, }); }