From b823a6ca3e5277296a00ed4e55ed25a4f2fbc8c2 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 13 Mar 2026 14:46:10 -0700 Subject: [PATCH] apply white-labeling to all share pages --- src/app/api/share/[slug]/route.ts | 57 ++++++------------- .../[slug]/[[...path]]/ShareBranding.tsx | 32 +++++++++++ .../share/[slug]/[[...path]]/ShareFooter.tsx | 25 ++++++++ src/app/share/[slug]/[[...path]]/ShareNav.tsx | 26 ++------- .../share/[slug]/[[...path]]/SharePage.tsx | 38 ++++++------- 5 files changed, 98 insertions(+), 80 deletions(-) create mode 100644 src/app/share/[slug]/[[...path]]/ShareBranding.tsx create mode 100644 src/app/share/[slug]/[[...path]]/ShareFooter.tsx diff --git a/src/app/api/share/[slug]/route.ts b/src/app/api/share/[slug]/route.ts index a237a8f17..f110ae936 100644 --- a/src/app/api/share/[slug]/route.ts +++ b/src/app/api/share/[slug]/route.ts @@ -1,11 +1,11 @@ +import { getBoardWebsiteIds } from '@/lib/boards'; import { ENTITY_TYPE, ROLES } from '@/lib/constants'; import { secret } from '@/lib/crypto'; import { createToken } from '@/lib/jwt'; -import { getBoardWebsiteIds } from '@/lib/boards'; import prisma from '@/lib/prisma'; import redis from '@/lib/redis'; import { json, notFound } from '@/lib/response'; -import type { WhiteLabel } from '@/lib/types'; +import type { BoardParameters, WhiteLabel } from '@/lib/types'; import { getBoard, getLink, getPixel, getShareByCode, getWebsite } from '@/queries/prisma'; async function getAccountId(entity: { userId?: string; teamId?: string }): Promise { @@ -53,57 +53,35 @@ export async function GET(_request: Request, { params }: { params: Promise<{ slu return notFound(); } - if (share.shareType === ENTITY_TYPE.board) { - const board = await getBoard(share.entityId); - - if (!board) { - return notFound(); - } - - const data: Record = { - shareId: share.id, - shareType: share.shareType, - boardId: share.entityId, - parameters: share.parameters, - websiteIds: getBoardWebsiteIds(board), - }; - - data.token = createToken(data, secret()); - - return json(data); - } - - let entity: { userId?: string; teamId?: string } | null = null; const data: Record = { shareId: share.id, shareType: share.shareType, parameters: share.parameters, }; - if (share.shareType === ENTITY_TYPE.website) { + let entity: { userId?: string; teamId?: string } | null = null; + + if (share.shareType === ENTITY_TYPE.board) { + const board = await getBoard(share.entityId); + if (!board) return notFound(); + entity = board; + data.boardId = share.entityId; + data.websiteIds = getBoardWebsiteIds({ + type: board.type, + parameters: share.parameters as BoardParameters, + }); + } else if (share.shareType === ENTITY_TYPE.website) { entity = await getWebsite(share.entityId); - - if (!entity) { - return notFound(); - } - + if (!entity) return notFound(); data.websiteId = share.entityId; } else if (share.shareType === ENTITY_TYPE.pixel) { entity = await getPixel(share.entityId); - - if (!entity) { - return notFound(); - } - + if (!entity) return notFound(); data.websiteId = share.entityId; data.pixelId = share.entityId; } else if (share.shareType === ENTITY_TYPE.link) { entity = await getLink(share.entityId); - - if (!entity) { - return notFound(); - } - + if (!entity) return notFound(); data.websiteId = share.entityId; data.linkId = share.entityId; } else { @@ -116,7 +94,6 @@ export async function GET(_request: Request, { params }: { params: Promise<{ slu if (accountId) { const whiteLabel = await getWhiteLabel(accountId); - if (whiteLabel) { data.whiteLabel = whiteLabel; } diff --git a/src/app/share/[slug]/[[...path]]/ShareBranding.tsx b/src/app/share/[slug]/[[...path]]/ShareBranding.tsx new file mode 100644 index 000000000..89573b288 --- /dev/null +++ b/src/app/share/[slug]/[[...path]]/ShareBranding.tsx @@ -0,0 +1,32 @@ +'use client'; +import { useShare } from '@/components/hooks'; +import { Logo } from '@/components/svg'; +import { Icon, Row, Text } from '@umami/react-zen'; + +const LOGO_SIZE = { sm: 24, md: 32, lg: 40 }; +const TEXT_SIZE = { sm: 'sm', md: 'base', lg: 'lg' } as const; + +export function ShareBranding({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) { + const share = useShare(); + const logoDomain = share?.whiteLabel?.domainName || 'https://umami.is'; + const logoName = share?.whiteLabel?.displayName || 'umami'; + const logoImage = share?.whiteLabel?.logoUrl; + const height = LOGO_SIZE[size]; + + return ( + + + {logoImage ? ( + {logoName} + ) : ( + + + + )} + + {logoName} + + + + ); +} diff --git a/src/app/share/[slug]/[[...path]]/ShareFooter.tsx b/src/app/share/[slug]/[[...path]]/ShareFooter.tsx new file mode 100644 index 000000000..69177b34c --- /dev/null +++ b/src/app/share/[slug]/[[...path]]/ShareFooter.tsx @@ -0,0 +1,25 @@ +'use client'; +import { useShare } from '@/components/hooks'; +import { Column, Row } from '@umami/react-zen'; +import { ShareBranding } from './ShareBranding'; + +export function ShareFooter() { + const share = useShare(); + + if (!share?.whiteLabel) { + return null; + } + + return ( + + + + + + ); +} diff --git a/src/app/share/[slug]/[[...path]]/ShareNav.tsx b/src/app/share/[slug]/[[...path]]/ShareNav.tsx index 103e9733f..1c49fe16b 100644 --- a/src/app/share/[slug]/[[...path]]/ShareNav.tsx +++ b/src/app/share/[slug]/[[...path]]/ShareNav.tsx @@ -1,10 +1,11 @@ -import { Button, Column, Icon, Row, Text, ThemeButton } from '@umami/react-zen'; +import { Button, Column, Icon, Row, ThemeButton } from '@umami/react-zen'; import { NavMenu } from '@/components/common/NavMenu'; 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'; +import { Funnel, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg'; +import { ShareBranding } from './ShareBranding'; export function ShareNav({ collapsed, @@ -18,11 +19,7 @@ export function ShareNav({ const share = useShare(); const { t, labels } = useMessages(); const { pathname } = useNavigation(); - const { slug, parameters, whiteLabel } = share; - - const logoDomain = whiteLabel?.domainName || 'https://umami.is'; - const logoName = whiteLabel?.displayName || 'umami'; - const logoImage = whiteLabel?.logoUrl; + const { slug, parameters } = share; const renderPath = (path: string) => `/share/${slug}${path}`; @@ -149,20 +146,7 @@ export function ShareNav({ border={isMobile ? undefined : 'right'} > - {!collapsed && ( - - - {logoImage ? ( - {logoName} - ) : ( - - - - )} - {logoName} - - - )} + {!collapsed && } {!onItemClick && (