Skip to content

Commit 78d467b

Browse files
committed
Redesigned share page.
1 parent 9d3f5ad commit 78d467b

File tree

9 files changed

+130
-88
lines changed

9 files changed

+130
-88
lines changed

src/app/share/ShareProvider.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client';
2+
import { Loading } from '@umami/react-zen';
3+
import { createContext, type ReactNode } from 'react';
4+
import { useShareTokenQuery } from '@/components/hooks';
5+
import type { WhiteLabel } from '@/lib/types';
6+
7+
export interface ShareData {
8+
shareId: string;
9+
websiteId: string;
10+
parameters: any;
11+
token: string;
12+
whiteLabel?: WhiteLabel;
13+
}
14+
15+
export const ShareContext = createContext<ShareData>(null);
16+
17+
export function ShareProvider({ shareId, children }: { shareId: string; children: ReactNode }) {
18+
const { share, isLoading, isFetching } = useShareTokenQuery(shareId);
19+
20+
if (isFetching && isLoading) {
21+
return <Loading placement="absolute" />;
22+
}
23+
24+
if (!share) {
25+
return null;
26+
}
27+
28+
return <ShareContext.Provider value={share}>{children}</ShareContext.Provider>;
29+
}

src/app/share/[...shareId]/ShareNav.tsx

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
'use client';
2-
import { Column } from '@umami/react-zen';
1+
import { Column, Icon, Row, Text, ThemeButton } from '@umami/react-zen';
32
import { SideMenu } from '@/components/common/SideMenu';
4-
import { useMessages, useNavigation } from '@/components/hooks';
3+
import { useMessages, useNavigation, useShare } from '@/components/hooks';
54
import { AlignEndHorizontal, Clock, Eye, Sheet, Tag, User } from '@/components/icons';
6-
import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
5+
import { LanguageButton } from '@/components/input/LanguageButton';
6+
import { PreferencesButton } from '@/components/input/PreferencesButton';
7+
import { Funnel, Lightning, Logo, Magnet, Money, Network, Path, Target } from '@/components/svg';
78

8-
export function ShareNav({
9-
shareId,
10-
parameters,
11-
onItemClick,
12-
}: {
13-
shareId: string;
14-
parameters: Record<string, boolean>;
15-
onItemClick?: () => void;
16-
}) {
9+
export function ShareNav({ onItemClick }: { onItemClick?: () => void }) {
10+
const share = useShare();
1711
const { formatMessage, labels } = useMessages();
1812
const { pathname } = useNavigation();
13+
const { shareId, parameters, whiteLabel } = share;
14+
15+
const logoUrl = whiteLabel?.url || 'https://umami.is';
16+
const logoName = whiteLabel?.name || 'umami';
17+
const logoImage = whiteLabel?.image;
1918

2019
const renderPath = (path: string) => `/share/${shareId}${path}`;
2120

@@ -131,13 +130,36 @@ export function ShareNav({
131130
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
132131

133132
return (
134-
<Column padding="3" position="sticky" top="0" gap>
135-
<SideMenu
136-
items={items}
137-
selectedKey={selectedKey}
138-
allowMinimize={false}
139-
onItemClick={onItemClick}
140-
/>
133+
<Column position="fixed" padding="3" width="240px" maxHeight="100vh" height="100vh">
134+
<Row as="header" gap alignItems="center" paddingY="3" marginLeft="3">
135+
<a href={logoUrl} target="_blank" rel="noopener">
136+
<Row alignItems="center" gap>
137+
{logoImage ? (
138+
<img src={logoImage} alt={logoName} style={{ height: 24 }} />
139+
) : (
140+
<Icon>
141+
<Logo />
142+
</Icon>
143+
)}
144+
<Text weight="bold">{logoName}</Text>
145+
</Row>
146+
</a>
147+
</Row>
148+
<Column>
149+
<SideMenu
150+
items={items}
151+
selectedKey={selectedKey}
152+
allowMinimize={false}
153+
onItemClick={onItemClick}
154+
/>
155+
</Column>
156+
<Column flexGrow={1} justifyContent="flex-end">
157+
<Row>
158+
<ThemeButton />
159+
<LanguageButton />
160+
<PreferencesButton />
161+
</Row>
162+
</Column>
141163
</Column>
142164
);
143165
}

src/app/share/[...shareId]/SharePage.tsx

Lines changed: 29 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22
import { Column, Grid, Row, useTheme } from '@umami/react-zen';
3-
import { useRouter } from 'next/navigation';
3+
import { usePathname, useRouter } from 'next/navigation';
44
import { useEffect, useMemo } from 'react';
55
import { AttributionPage } from '@/app/(main)/websites/[websiteId]/(reports)/attribution/AttributionPage';
66
import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/BreakdownPage';
@@ -18,10 +18,8 @@ import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
1818
import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage';
1919
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
2020
import { PageBody } from '@/components/common/PageBody';
21-
import { useShareTokenQuery } from '@/components/hooks';
21+
import { useShare } from '@/components/hooks';
2222
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
23-
import { ShareFooter } from './ShareFooter';
24-
import { ShareHeader } from './ShareHeader';
2523
import { ShareNav } from './ShareNav';
2624

2725
const PAGE_COMPONENTS: Record<string, React.ComponentType<{ websiteId: string }>> = {
@@ -58,17 +56,20 @@ const ALL_SECTION_IDS = [
5856
'attribution',
5957
];
6058

61-
export function SharePage({ shareId, path = '' }: { shareId: string; path?: string }) {
62-
const { shareToken, isLoading } = useShareTokenQuery(shareId);
59+
export function SharePage({ shareId }: { shareId: string }) {
60+
const share = useShare();
6361
const { setTheme } = useTheme();
6462
const router = useRouter();
63+
const pathname = usePathname();
64+
const path = pathname.split('/')[3];
65+
const { websiteId, parameters = {} } = share;
6566

6667
// Calculate allowed sections
6768
const allowedSections = useMemo(() => {
68-
if (!shareToken?.parameters) return [];
69-
const params = shareToken.parameters;
69+
if (!share?.parameters) return [];
70+
const params = share.parameters;
7071
return ALL_SECTION_IDS.filter(id => params[id] !== false);
71-
}, [shareToken?.parameters]);
72+
}, [share?.parameters]);
7273

7374
useEffect(() => {
7475
const url = new URL(window?.location?.href);
@@ -90,12 +91,6 @@ export function SharePage({ shareId, path = '' }: { shareId: string; path?: stri
9091
}
9192
}, [allowedSections, shareId, path, router]);
9293

93-
if (isLoading || !shareToken) {
94-
return null;
95-
}
96-
97-
const { websiteId, parameters = {}, whiteLabel } = shareToken;
98-
9994
// Redirect to only allowed section - return null while redirecting
10095
if (
10196
allowedSections.length === 1 &&
@@ -116,40 +111,25 @@ export function SharePage({ shareId, path = '' }: { shareId: string; path?: stri
116111
const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage;
117112

118113
return (
119-
<Column backgroundColor="2">
120-
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
121-
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
122-
<Grid columns="auto 1fr" flexGrow={1} backgroundColor="3" borderRadius>
123-
<MobileMenuButton>
124-
{({ close }) => {
125-
return <ShareNav shareId={shareId} parameters={parameters} onItemClick={close} />;
126-
}}
127-
</MobileMenuButton>
128-
</Grid>
129-
</Row>
130-
<Column
131-
display={{ xs: 'none', lg: 'flex' }}
132-
width="240px"
133-
height="100%"
134-
border="right"
135-
backgroundColor
136-
marginRight="2"
137-
>
138-
<Column display={{ xs: 'none', lg: 'flex' }}>
139-
<ShareNav shareId={shareId} parameters={parameters} />
114+
<Grid columns={{ xs: '1fr', lg: '240px 1fr' }} width="100%">
115+
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
116+
<MobileMenuButton>
117+
{({ close }) => {
118+
return <ShareNav onItemClick={close} />;
119+
}}
120+
</MobileMenuButton>
121+
</Row>
122+
<Column display={{ xs: 'none', lg: 'flex' }} marginRight="2">
123+
<ShareNav />
124+
</Column>
125+
<PageBody gap>
126+
<WebsiteProvider websiteId={websiteId}>
127+
<Column>
128+
<WebsiteHeader showActions={false} />
129+
<PageComponent websiteId={websiteId} />
140130
</Column>
141-
</Column>
142-
<PageBody gap>
143-
<WebsiteProvider websiteId={websiteId}>
144-
<ShareHeader whiteLabel={whiteLabel} />
145-
<Column>
146-
<WebsiteHeader showActions={false} />
147-
<PageComponent websiteId={websiteId} />
148-
</Column>
149-
<ShareFooter whiteLabel={whiteLabel} />
150-
</WebsiteProvider>
151-
</PageBody>
152-
</Grid>
153-
</Column>
131+
</WebsiteProvider>
132+
</PageBody>
133+
</Grid>
154134
);
155135
}
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import { ShareProvider } from '@/app/share/ShareProvider';
12
import { SharePage } from './SharePage';
23

34
export default async function ({ params }: { params: Promise<{ shareId: string[] }> }) {
45
const { shareId } = await params;
5-
const [slug, ...path] = shareId;
6+
const [slug] = shareId;
67

7-
return <SharePage shareId={slug} path={path.join('/')} />;
8+
return (
9+
<ShareProvider shareId={slug}>
10+
<SharePage shareId={slug} />
11+
</ShareProvider>
12+
);
813
}

src/components/common/SideMenu.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
NavMenuItem,
88
type NavMenuProps,
99
Row,
10+
Text,
1011
} from '@umami/react-zen';
1112
import Link from 'next/link';
1213

@@ -42,9 +43,11 @@ export function SideMenu({
4243

4344
return (
4445
<Link key={id} href={path}>
45-
<NavMenuItem isSelected={isSelected}>
46-
<IconLabel icon={icon}>{label}</IconLabel>
47-
</NavMenuItem>
46+
<Row padding hoverBackgroundColor="3">
47+
<IconLabel icon={icon}>
48+
<Text weight={isSelected ? 'bold' : undefined}>{label}</Text>
49+
</IconLabel>
50+
</Row>
4851
</Link>
4952
);
5053
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { useContext } from 'react';
2+
import { ShareContext } from '@/app/share/ShareProvider';
3+
4+
export function useShare() {
5+
return useContext(ShareContext);
6+
}

src/components/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Context hooks
44
export * from './context/useLink';
55
export * from './context/usePixel';
6+
export * from './context/useShare';
67
export * from './context/useTeam';
78
export * from './context/useUser';
89
export * from './context/useWebsite';
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
import { setShareToken, useApp } from '@/store/app';
1+
import { setShare, useApp } from '@/store/app';
22
import { useApi } from '../useApi';
33

4-
const selector = (state: { shareToken: string }) => state.shareToken;
4+
const selector = state => state.share;
55

6-
export function useShareTokenQuery(slug: string): {
7-
shareToken: any;
8-
isLoading?: boolean;
9-
error?: Error;
10-
} {
11-
const shareToken = useApp(selector);
6+
export function useShareTokenQuery(slug: string) {
7+
const share = useApp(selector);
128
const { get, useQuery } = useApi();
13-
const { isLoading, error } = useQuery({
9+
const query = useQuery({
1410
queryKey: ['share', slug],
1511
queryFn: async () => {
1612
const data = await get(`/share/${slug}`);
1713

18-
setShareToken(data);
14+
setShare(data);
1915

2016
return data;
2117
},
2218
});
2319

24-
return { shareToken, isLoading, error };
20+
return { share, ...query };
2521
}

src/store/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const initialState = {
1616
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
1717
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
1818
dateRangeValue: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
19-
shareToken: null,
19+
share: null,
2020
user: null,
2121
config: null,
2222
};
@@ -31,8 +31,8 @@ export function setLocale(locale: string) {
3131
store.setState({ locale });
3232
}
3333

34-
export function setShareToken(shareToken: string) {
35-
store.setState({ shareToken });
34+
export function setShare(share: object) {
35+
store.setState({ share });
3636
}
3737

3838
export function setUser(user: object) {

0 commit comments

Comments
 (0)