Move share page redirect logic to ShareProvider

Centralizes the single-section redirect logic in ShareProvider instead of
SharePage, reducing useEffect complexity and preventing children from
rendering during redirect.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mike Cao 2026-01-28 23:18:05 -08:00
parent 78d467b478
commit d028bfa1f5
3 changed files with 41 additions and 51 deletions

View file

@ -1,6 +1,7 @@
'use client'; 'use client';
import { Loading } from '@umami/react-zen'; import { Loading } from '@umami/react-zen';
import { createContext, type ReactNode } from 'react'; import { usePathname, useRouter } from 'next/navigation';
import { createContext, type ReactNode, useEffect } from 'react';
import { useShareTokenQuery } from '@/components/hooks'; import { useShareTokenQuery } from '@/components/hooks';
import type { WhiteLabel } from '@/lib/types'; import type { WhiteLabel } from '@/lib/types';
@ -14,14 +15,48 @@ export interface ShareData {
export const ShareContext = createContext<ShareData>(null); export const ShareContext = createContext<ShareData>(null);
const ALL_SECTION_IDS = [
'overview',
'events',
'sessions',
'realtime',
'compare',
'breakdown',
'goals',
'funnels',
'journeys',
'retention',
'utm',
'revenue',
'attribution',
];
export function ShareProvider({ shareId, children }: { shareId: string; children: ReactNode }) { export function ShareProvider({ shareId, children }: { shareId: string; children: ReactNode }) {
const { share, isLoading, isFetching } = useShareTokenQuery(shareId); const { share, isLoading, isFetching } = useShareTokenQuery(shareId);
const router = useRouter();
const pathname = usePathname();
const path = pathname.split('/')[3];
const allowedSections = share?.parameters
? ALL_SECTION_IDS.filter(id => share.parameters[id] !== false)
: [];
const shouldRedirect =
allowedSections.length === 1 &&
allowedSections[0] !== 'overview' &&
(path === undefined || path === '' || path === 'overview');
useEffect(() => {
if (shouldRedirect) {
router.replace(`/share/${shareId}/${allowedSections[0]}`);
}
}, [shouldRedirect, shareId, allowedSections, router]);
if (isFetching && isLoading) { if (isFetching && isLoading) {
return <Loading placement="absolute" />; return <Loading placement="absolute" />;
} }
if (!share) { if (!share || shouldRedirect) {
return null; return null;
} }

View file

@ -1,7 +1,7 @@
'use client'; 'use client';
import { Column, Grid, Row, useTheme } from '@umami/react-zen'; import { Column, Grid, Row, useTheme } from '@umami/react-zen';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage'; import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage';
import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage'; import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage';
import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage'; import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage';
@ -39,38 +39,13 @@ const PAGE_COMPONENTS: Record<string, React.ComponentType<{ websiteId: string }>
attribution: AttributionPage, attribution: AttributionPage,
}; };
// All section IDs that can be enabled/disabled via parameters export function SharePage() {
const ALL_SECTION_IDS = [
'overview',
'events',
'sessions',
'realtime',
'compare',
'breakdown',
'goals',
'funnels',
'journeys',
'retention',
'utm',
'revenue',
'attribution',
];
export function SharePage({ shareId }: { shareId: string }) {
const share = useShare(); const share = useShare();
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const path = pathname.split('/')[3]; const path = pathname.split('/')[3];
const { websiteId, parameters = {} } = share; const { websiteId, parameters = {} } = share;
// Calculate allowed sections
const allowedSections = useMemo(() => {
if (!share?.parameters) return [];
const params = share.parameters;
return ALL_SECTION_IDS.filter(id => params[id] !== false);
}, [share?.parameters]);
useEffect(() => { useEffect(() => {
const url = new URL(window?.location?.href); const url = new URL(window?.location?.href);
const theme = url.searchParams.get('theme'); const theme = url.searchParams.get('theme');
@ -80,26 +55,6 @@ export function SharePage({ shareId }: { shareId: string }) {
} }
}, []); }, []);
// Redirect to the only allowed section if there's just one and we're on the base path
useEffect(() => {
if (
allowedSections.length === 1 &&
allowedSections[0] !== 'overview' &&
(path === '' || path === 'overview')
) {
router.replace(`/share/${shareId}/${allowedSections[0]}`);
}
}, [allowedSections, shareId, path, router]);
// Redirect to only allowed section - return null while redirecting
if (
allowedSections.length === 1 &&
allowedSections[0] !== 'overview' &&
(path === '' || path === 'overview')
) {
return null;
}
// Check if the requested path is allowed // Check if the requested path is allowed
const pageKey = path || ''; const pageKey = path || '';
const isAllowed = pageKey === '' || pageKey === 'overview' || parameters[pageKey] !== false; const isAllowed = pageKey === '' || pageKey === 'overview' || parameters[pageKey] !== false;

View file

@ -7,7 +7,7 @@ export default async function ({ params }: { params: Promise<{ shareId: string[]
return ( return (
<ShareProvider shareId={slug}> <ShareProvider shareId={slug}>
<SharePage shareId={slug} /> <SharePage />
</ShareProvider> </ShareProvider>
); );
} }