Compare commits

...

14 commits

Author SHA1 Message Date
Mike Cao
489c2712d1 Make ShareNav full width on mobile
Some checks are pending
Node.js CI / build (push) Waiting to run
- Remove fixed width, position, and border on mobile
- Use 100% width when onItemClick is provided (mobile context)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:44:18 -08:00
Mike Cao
b43e7fd3a7 Hide sidebar collapse button on mobile
onItemClick is only passed on mobile, so use it to detect mobile context

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:37:29 -08:00
Mike Cao
5880eae4e4 Fix NavMenu scrolling on mobile
- Add overflowY="auto" and flexGrow to menu container
- Menu now scrolls when content exceeds viewport height

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:34:47 -08:00
Mike Cao
6169a58e86 Center bottom icons when sidebar collapsed
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:13:23 -08:00
Mike Cao
452a385c4e Fix ShareNav collapse button and icon layout
- Use single PanelLeft icon with muted color
- Align collapse button to right of header
- Bottom icons: horizontal (Row) when expanded, vertical (Column) when collapsed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:12:23 -08:00
Mike Cao
482d6c1e47 Add collapsible ShareNav sidebar
- Add collapse/expand button in header
- When collapsed: hide menu items and logo, show only toggle button
- Stack bottom icons vertically when collapsed
- Adjust grid layout to match collapsed nav width (60px vs 240px)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 00:08:53 -08:00
Mike Cao
4e8be724ac Handle domain name in share URL path
Skip domain-like segments (containing dots) when parsing the share path.
e.g., /share/slug/aol.com is treated as /share/slug
      /share/slug/aol.com/events is treated as /share/slug/events

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 23:38:07 -08:00
Mike Cao
c9e14f3bce Restructure share routes to fix client-side navigation
- Change from [...shareId] catch-all to [slug]/[[...path]] structure
- Layout with ShareProvider now persists across sub-route navigation
- Add slug to ShareData context (separate from shareId UUID)
- Links now use slug instead of UUID for proper routing
- Remove unused ShareFooter and ShareHeader files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 23:32:51 -08:00
Mike Cao
d028bfa1f5 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>
2026-01-28 23:18:05 -08:00
Mike Cao
78d467b478 Redesigned share page. 2026-01-28 23:10:42 -08:00
Mike Cao
9d3f5ad0fd Merge remote-tracking branch 'origin/dev' into dev 2026-01-28 20:14:43 -08:00
Mike Cao
018e76b067 Fixed website nav. 2026-01-28 19:24:13 -08:00
Francis Cao
2df24a78ca bug fix. remove All time filter for websites with no data. 2026-01-28 18:05:34 -08:00
Francis Cao
9339383497 remove www. prefix from hostname during comparison. Closes #3256 2026-01-28 17:18:03 -08:00
22 changed files with 313 additions and 264 deletions

View file

@ -9,11 +9,10 @@ import { WebsiteNav } from './WebsiteNav';
export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) {
return (
<WebsiteProvider websiteId={websiteId}>
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%">
<Column
display={{ xs: 'none', lg: 'flex' }}
width="240px"
height="100%"
border="right"
backgroundColor
marginRight="2"

View file

@ -0,0 +1,77 @@
'use client';
import { Loading } from '@umami/react-zen';
import { usePathname, useRouter } from 'next/navigation';
import { createContext, type ReactNode, useEffect } from 'react';
import { useShareTokenQuery } from '@/components/hooks';
import type { WhiteLabel } from '@/lib/types';
export interface ShareData {
shareId: string;
slug: string;
websiteId: string;
parameters: any;
token: string;
whiteLabel?: WhiteLabel;
}
export const ShareContext = createContext<ShareData>(null);
const ALL_SECTION_IDS = [
'overview',
'events',
'sessions',
'realtime',
'compare',
'breakdown',
'goals',
'funnels',
'journeys',
'retention',
'utm',
'revenue',
'attribution',
];
function getSharePath(pathname: string) {
const segments = pathname.split('/');
const firstSegment = segments[3];
// If first segment looks like a domain name, skip it
if (firstSegment?.includes('.')) {
return segments[4];
}
return firstSegment;
}
export function ShareProvider({ slug, children }: { slug: string; children: ReactNode }) {
const { share, isLoading, isFetching } = useShareTokenQuery(slug);
const router = useRouter();
const pathname = usePathname();
const path = getSharePath(pathname);
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/${slug}/${allowedSections[0]}`);
}
}, [shouldRedirect, slug, allowedSections, router]);
if (isFetching && isLoading) {
return <Loading placement="absolute" />;
}
if (!share || shouldRedirect) {
return null;
}
return <ShareContext.Provider value={{ ...share, slug }}>{children}</ShareContext.Provider>;
}

View file

@ -1,23 +0,0 @@
import { Row, Text } from '@umami/react-zen';
import { CURRENT_VERSION, HOMEPAGE_URL } from '@/lib/constants';
import type { WhiteLabel } from '@/lib/types';
export function ShareFooter({ whiteLabel }: { whiteLabel?: WhiteLabel }) {
if (whiteLabel) {
return (
<Row as="footer" paddingY="6" justifyContent="flex-end">
<a href={whiteLabel.url} target="_blank">
<Text weight="bold">{whiteLabel.name}</Text>
</a>
</Row>
);
}
return (
<Row as="footer" paddingY="6" justifyContent="flex-end">
<a href={HOMEPAGE_URL} target="_blank">
<Text weight="bold">umami</Text> {`v${CURRENT_VERSION}`}
</a>
</Row>
);
}

View file

@ -1,33 +0,0 @@
import { Icon, Row, Text, ThemeButton } from '@umami/react-zen';
import { LanguageButton } from '@/components/input/LanguageButton';
import { PreferencesButton } from '@/components/input/PreferencesButton';
import { Logo } from '@/components/svg';
import type { WhiteLabel } from '@/lib/types';
export function ShareHeader({ whiteLabel }: { whiteLabel?: WhiteLabel }) {
const logoUrl = whiteLabel?.url || 'https://umami.is';
const logoName = whiteLabel?.name || 'umami';
const logoImage = whiteLabel?.image;
return (
<Row as="header" justifyContent="space-between" alignItems="center" paddingY="3">
<a href={logoUrl} target="_blank" rel="noopener">
<Row alignItems="center" gap>
{logoImage ? (
<img src={logoImage} alt={logoName} style={{ height: 24 }} />
) : (
<Icon>
<Logo />
</Icon>
)}
<Text weight="bold">{logoName}</Text>
</Row>
</a>
<Row alignItems="center" gap>
<ThemeButton />
<LanguageButton />
<PreferencesButton />
</Row>
</Row>
);
}

View file

@ -1,155 +0,0 @@
'use client';
import { Column, Grid, Row, useTheme } from '@umami/react-zen';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo } from 'react';
import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage';
import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage';
import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage';
import { GoalsPage } from '@/app/(main)/websites/[websiteId]/(reports)/goals/GoalsPage';
import { JourneysPage } from '@/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage';
import { RetentionPage } from '@/app/(main)/websites/[websiteId]/(reports)/retention/RetentionPage';
import { RevenuePage } from '@/app/(main)/websites/[websiteId]/(reports)/revenue/RevenuePage';
import { UTMPage } from '@/app/(main)/websites/[websiteId]/(reports)/utm/UTMPage';
import { ComparePage } from '@/app/(main)/websites/[websiteId]/compare/ComparePage';
import { EventsPage } from '@/app/(main)/websites/[websiteId]/events/EventsPage';
import { RealtimePage } from '@/app/(main)/websites/[websiteId]/realtime/RealtimePage';
import { SessionsPage } from '@/app/(main)/websites/[websiteId]/sessions/SessionsPage';
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage';
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
import { PageBody } from '@/components/common/PageBody';
import { useShareTokenQuery } from '@/components/hooks';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
import { ShareFooter } from './ShareFooter';
import { ShareHeader } from './ShareHeader';
import { ShareNav } from './ShareNav';
const PAGE_COMPONENTS: Record<string, React.ComponentType<{ websiteId: string }>> = {
'': WebsitePage,
overview: WebsitePage,
events: EventsPage,
sessions: SessionsPage,
realtime: RealtimePage,
compare: ComparePage,
breakdown: BreakdownPage,
goals: GoalsPage,
funnels: FunnelsPage,
journeys: JourneysPage,
retention: RetentionPage,
utm: UTMPage,
revenue: RevenuePage,
attribution: AttributionPage,
};
// All section IDs that can be enabled/disabled via parameters
const ALL_SECTION_IDS = [
'overview',
'events',
'sessions',
'realtime',
'compare',
'breakdown',
'goals',
'funnels',
'journeys',
'retention',
'utm',
'revenue',
'attribution',
];
export function SharePage({ shareId, path = '' }: { shareId: string; path?: string }) {
const { shareToken, isLoading } = useShareTokenQuery(shareId);
const { setTheme } = useTheme();
const router = useRouter();
// Calculate allowed sections
const allowedSections = useMemo(() => {
if (!shareToken?.parameters) return [];
const params = shareToken.parameters;
return ALL_SECTION_IDS.filter(id => params[id] !== false);
}, [shareToken?.parameters]);
useEffect(() => {
const url = new URL(window?.location?.href);
const theme = url.searchParams.get('theme');
if (theme === 'light' || theme === 'dark') {
setTheme(theme);
}
}, []);
// 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]);
if (isLoading || !shareToken) {
return null;
}
const { websiteId, parameters = {}, whiteLabel } = shareToken;
// 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
const pageKey = path || '';
const isAllowed = pageKey === '' || pageKey === 'overview' || parameters[pageKey] !== false;
if (!isAllowed) {
return null;
}
const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage;
return (
<Column backgroundColor="2">
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
<Grid columns="auto 1fr" flexGrow={1} backgroundColor="3" borderRadius>
<MobileMenuButton>
{({ close }) => {
return <ShareNav shareId={shareId} parameters={parameters} onItemClick={close} />;
}}
</MobileMenuButton>
</Grid>
</Row>
<Column
display={{ xs: 'none', lg: 'flex' }}
width="240px"
height="100%"
border="right"
backgroundColor
marginRight="2"
>
<Column display={{ xs: 'none', lg: 'flex' }}>
<ShareNav shareId={shareId} parameters={parameters} />
</Column>
</Column>
<PageBody gap>
<WebsiteProvider websiteId={websiteId}>
<ShareHeader whiteLabel={whiteLabel} />
<Column>
<WebsiteHeader showActions={false} />
<PageComponent websiteId={websiteId} />
</Column>
<ShareFooter whiteLabel={whiteLabel} />
</WebsiteProvider>
</PageBody>
</Grid>
</Column>
);
}

View file

@ -1,8 +0,0 @@
import { SharePage } from './SharePage';
export default async function ({ params }: { params: Promise<{ shareId: string[] }> }) {
const { shareId } = await params;
const [slug, ...path] = shareId;
return <SharePage shareId={slug} path={path.join('/')} />;
}

View file

@ -1,23 +1,30 @@
'use client';
import { Column } from '@umami/react-zen';
import { Button, Column, Icon, Row, Text, ThemeButton } from '@umami/react-zen';
import { SideMenu } from '@/components/common/SideMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import { AlignEndHorizontal, Clock, Eye, Sheet, Tag, User } from '@/components/icons';
import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
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 { Funnel, Lightning, Logo, Magnet, Money, Network, Path, Target } from '@/components/svg';
export function ShareNav({
shareId,
parameters,
collapsed,
onCollapse,
onItemClick,
}: {
shareId: string;
parameters: Record<string, boolean>;
collapsed?: boolean;
onCollapse?: (collapsed: boolean) => void;
onItemClick?: () => void;
}) {
const share = useShare();
const { formatMessage, labels } = useMessages();
const { pathname } = useNavigation();
const { slug, parameters, whiteLabel } = share;
const renderPath = (path: string) => `/share/${shareId}${path}`;
const logoUrl = whiteLabel?.url || 'https://umami.is';
const logoName = whiteLabel?.name || 'umami';
const logoImage = whiteLabel?.image;
const renderPath = (path: string) => `/share/${slug}${path}`;
const allItems = [
{
@ -130,14 +137,70 @@ export function ShareNav({
.flatMap(e => e.items)
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
const isMobile = !!onItemClick;
return (
<Column padding="3" position="sticky" top="0" gap>
<SideMenu
items={items}
selectedKey={selectedKey}
allowMinimize={false}
onItemClick={onItemClick}
/>
<Column
position={isMobile ? undefined : 'fixed'}
padding="3"
width={isMobile ? '100%' : collapsed ? '60px' : '240px'}
maxHeight="100vh"
height="100vh"
border={isMobile ? undefined : 'right'}
borderColor={isMobile ? undefined : '4'}
>
<Row as="header" gap alignItems="center" paddingY="3" justifyContent="space-between">
{!collapsed && (
<a href={logoUrl} target="_blank" rel="noopener" style={{ marginLeft: 12 }}>
<Row alignItems="center" gap>
{logoImage ? (
<img src={logoImage} alt={logoName} style={{ height: 24 }} />
) : (
<Icon>
<Logo />
</Icon>
)}
<Text weight="bold">{logoName}</Text>
</Row>
</a>
)}
{!onItemClick && (
<Button variant="quiet" onPress={() => onCollapse?.(!collapsed)}>
<Icon color="muted">
<PanelLeft />
</Icon>
</Button>
)}
</Row>
{!collapsed && (
<Column flexGrow={1} overflowY="auto">
<SideMenu
items={items}
selectedKey={selectedKey}
allowMinimize={false}
onItemClick={onItemClick}
/>
</Column>
)}
<Column
flexGrow={collapsed ? 1 : undefined}
justifyContent="flex-end"
alignItems={collapsed ? 'center' : undefined}
>
{collapsed ? (
<Column gap="2" alignItems="center">
<ThemeButton />
<LanguageButton />
<PreferencesButton />
</Column>
) : (
<Row>
<ThemeButton />
<LanguageButton />
<PreferencesButton />
</Row>
)}
</Column>
</Column>
);
}

View file

@ -0,0 +1,103 @@
'use client';
import { Column, Grid, Row, useTheme } from '@umami/react-zen';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage';
import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage';
import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage';
import { GoalsPage } from '@/app/(main)/websites/[websiteId]/(reports)/goals/GoalsPage';
import { JourneysPage } from '@/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage';
import { RetentionPage } from '@/app/(main)/websites/[websiteId]/(reports)/retention/RetentionPage';
import { RevenuePage } from '@/app/(main)/websites/[websiteId]/(reports)/revenue/RevenuePage';
import { UTMPage } from '@/app/(main)/websites/[websiteId]/(reports)/utm/UTMPage';
import { ComparePage } from '@/app/(main)/websites/[websiteId]/compare/ComparePage';
import { EventsPage } from '@/app/(main)/websites/[websiteId]/events/EventsPage';
import { RealtimePage } from '@/app/(main)/websites/[websiteId]/realtime/RealtimePage';
import { SessionsPage } from '@/app/(main)/websites/[websiteId]/sessions/SessionsPage';
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage';
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
import { PageBody } from '@/components/common/PageBody';
import { useShare } from '@/components/hooks';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
import { ShareNav } from './ShareNav';
const PAGE_COMPONENTS: Record<string, React.ComponentType<{ websiteId: string }>> = {
'': WebsitePage,
overview: WebsitePage,
events: EventsPage,
sessions: SessionsPage,
realtime: RealtimePage,
compare: ComparePage,
breakdown: BreakdownPage,
goals: GoalsPage,
funnels: FunnelsPage,
journeys: JourneysPage,
retention: RetentionPage,
utm: UTMPage,
revenue: RevenuePage,
attribution: AttributionPage,
};
function getSharePath(pathname: string) {
const segments = pathname.split('/');
const firstSegment = segments[3];
// If first segment looks like a domain name, skip it
if (firstSegment?.includes('.')) {
return segments[4];
}
return firstSegment;
}
export function SharePage() {
const [navCollapsed, setNavCollapsed] = useState(false);
const share = useShare();
const { setTheme } = useTheme();
const pathname = usePathname();
const path = getSharePath(pathname);
const { websiteId, parameters = {} } = share;
useEffect(() => {
const url = new URL(window?.location?.href);
const theme = url.searchParams.get('theme');
if (theme === 'light' || theme === 'dark') {
setTheme(theme);
}
}, []);
// Check if the requested path is allowed
const pageKey = path || '';
const isAllowed = pageKey === '' || pageKey === 'overview' || parameters[pageKey] !== false;
if (!isAllowed) {
return null;
}
const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage;
return (
<Grid columns={{ xs: '1fr', lg: `${navCollapsed ? '60px' : '240px'} 1fr` }} width="100%">
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
<MobileMenuButton>
{({ close }) => {
return <ShareNav onItemClick={close} />;
}}
</MobileMenuButton>
</Row>
<Column display={{ xs: 'none', lg: 'flex' }} marginRight="2">
<ShareNav collapsed={navCollapsed} onCollapse={setNavCollapsed} />
</Column>
<PageBody gap>
<WebsiteProvider websiteId={websiteId}>
<Column>
<WebsiteHeader showActions={false} />
<PageComponent websiteId={websiteId} />
</Column>
</WebsiteProvider>
</PageBody>
</Grid>
);
}

View file

@ -0,0 +1,5 @@
import { SharePage } from './SharePage';
export default function () {
return <SharePage />;
}

View file

@ -0,0 +1,13 @@
import { ShareProvider } from '@/app/share/ShareProvider';
export default async function ({
params,
children,
}: {
params: Promise<{ slug: string }>;
children: React.ReactNode;
}) {
const { slug } = await params;
return <ShareProvider slug={slug}>{children}</ShareProvider>;
}

View file

@ -7,6 +7,7 @@ import {
NavMenuItem,
type NavMenuProps,
Row,
Text,
} from '@umami/react-zen';
import Link from 'next/link';
@ -42,9 +43,11 @@ export function SideMenu({
return (
<Link key={id} href={path}>
<NavMenuItem isSelected={isSelected}>
<IconLabel icon={icon}>{label}</IconLabel>
</NavMenuItem>
<Row padding hoverBackgroundColor="3">
<IconLabel icon={icon}>
<Text weight={isSelected ? 'bold' : undefined}>{label}</Text>
</IconLabel>
</Row>
</Link>
);
});

View file

@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ShareContext } from '@/app/share/ShareProvider';
export function useShare() {
return useContext(ShareContext);
}

View file

@ -3,6 +3,7 @@
// Context hooks
export * from './context/useLink';
export * from './context/usePixel';
export * from './context/useShare';
export * from './context/useTeam';
export * from './context/useUser';
export * from './context/useWebsite';

View file

@ -1,25 +1,21 @@
import { setShareToken, useApp } from '@/store/app';
import { setShare, useApp } from '@/store/app';
import { useApi } from '../useApi';
const selector = (state: { shareToken: string }) => state.shareToken;
const selector = state => state.share;
export function useShareTokenQuery(slug: string): {
shareToken: any;
isLoading?: boolean;
error?: Error;
} {
const shareToken = useApp(selector);
export function useShareTokenQuery(slug: string) {
const share = useApp(selector);
const { get, useQuery } = useApi();
const { isLoading, error } = useQuery({
const query = useQuery({
queryKey: ['share', slug],
queryFn: async () => {
const data = await get(`/share/${slug}`);
setShareToken(data);
setShare(data);
return data;
},
});
return { shareToken, isLoading, error };
return { share, ...query };
}

View file

@ -9,8 +9,8 @@ export function MobileMenuButton(props: DialogProps) {
<Menu />
</Icon>
</Button>
<Modal placement="left" offset="80px">
<Dialog variant="sheet" {...props} />
<Modal placement="left">
<Dialog variant="sheet" {...props} style={{ width: 'auto' }} />
</Modal>
</DialogTrigger>
);

View file

@ -31,9 +31,11 @@ export function WebsiteDateFilter({
const showCompare = allowCompare && !isAllTime;
const websiteDateRange = useDateRangeQuery(websiteId);
const { startDate, endDate } = websiteDateRange;
const hasData = startDate && endDate;
const handleChange = (date: string) => {
if (date === 'all') {
if (date === 'all' && hasData) {
router.push(
updateParams({
date: `${getDateRangeValue(websiteDateRange.startDate, websiteDateRange.endDate)}:all`,
@ -78,7 +80,7 @@ export function WebsiteDateFilter({
<DateFilter
value={dateValue}
onChange={handleChange}
showAllTime={showAllTime}
showAllTime={hasData && showAllTime}
renderDate={+offset !== 0}
/>
</Row>

View file

@ -108,7 +108,7 @@ function getFilterQuery(filters: Record<string, any>, options: QueryOptions = {}
if (name === 'referrer') {
arr.push(
`and (website_event.referrer_domain != website_event.hostname or website_event.referrer_domain is null)`,
`and (website_event.referrer_domain != regexp_replace(website_event.hostname, '^www.', '') or website_event.referrer_domain is null)`,
);
}
}

View file

@ -23,7 +23,7 @@ async function relationalQuery(websiteId: string, column: string, filters: Query
let excludeDomain = '';
if (column === 'referrer_domain') {
excludeDomain = `and website_event.referrer_domain != website_event.hostname
excludeDomain = `and website_event.referrer_domain != regexp_replace(website_event.hostname, '^www.', '')
and website_event.referrer_domain != ''`;
}

View file

@ -50,7 +50,7 @@ async function relationalQuery(
let excludeDomain = '';
if (column === 'referrer_domain') {
excludeDomain = `and website_event.referrer_domain != website_event.hostname
excludeDomain = `and website_event.referrer_domain != regexp_replace(website_event.hostname, '^www.', '')
and website_event.referrer_domain != ''`;
if (type === 'domain') {
column = toPostgresGroupedReferrer(GROUPED_DOMAINS);

View file

@ -46,7 +46,7 @@ async function relationalQuery(
let excludeDomain = '';
if (column === 'referrer_domain') {
excludeDomain = `and website_event.referrer_domain != website_event.hostname
excludeDomain = `and website_event.referrer_domain != regexp_replace(website_event.hostname, '^www.', '')
and website_event.referrer_domain != ''`;
}

View file

@ -142,7 +142,7 @@ async function relationalQuery(
${
currency
? ''
: `and we.referrer_domain != hostname
: `and we.referrer_domain != regexp_replace(we.hostname, '^www.', '')
and we.referrer_domain != ''`
}
group by 1

View file

@ -16,7 +16,7 @@ const initialState = {
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
dateRangeValue: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
shareToken: null,
share: null,
user: null,
config: null,
};
@ -31,8 +31,8 @@ export function setLocale(locale: string) {
store.setState({ locale });
}
export function setShareToken(shareToken: string) {
store.setState({ shareToken });
export function setShare(share: object) {
store.setState({ share });
}
export function setUser(user: object) {