diff --git a/.gitignore b/.gitignore index de893d0f..0649999d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,47 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -node_modules -.pnp -.pnp.js -.pnpm-store -package-lock.json - -# testing -/coverage - -# next.js -/.next -/out - -# production -/build -/public/script.js -/geo -/dist -/generated -/src/generated -pm2.yml - -# misc -.DS_Store -.idea -.yarn -*.iml -*.log -.vscode -.tool-versions - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env.* -*.env.* - -*.dev.yml - +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js +.pnpm-store +package-lock.json + +# testing +/coverage + +# next.js +/.next +/out + +# production +/build +/public/script.js +/geo +/dist +/generated +/src/generated +pm2.yml + +# misc +.DS_Store +.idea +.yarn +*.iml +*.log +.vscode +.tool-versions +.claude + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.* +*.env.* + +*.dev.yml + diff --git a/package.json b/package.json index 76b1e1fa..48816881 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "type": "module", "scripts": { - "dev": "next dev -p 3001 --turbo", + "dev": "next dev -p 3003 --turbo", "build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app", "start": "next start", "build-docker": "npm-run-all build-db build-tracker build-geo build-app", diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx index e79576dd..1dee8022 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx @@ -6,7 +6,13 @@ import { useMessages, useNavigation, useWebsite } from '@/components/hooks'; import { Edit } from '@/components/icons'; import { ActiveUsers } from '@/components/metrics/ActiveUsers'; -export function WebsiteHeader({ showActions }: { showActions?: boolean }) { +export function WebsiteHeader({ + showActions, + allowLink = true, +}: { + showActions?: boolean; + allowLink?: boolean; +}) { const website = useWebsite(); const { renderUrl, pathname } = useNavigation(); const isSettings = pathname.endsWith('/settings'); @@ -21,7 +27,7 @@ export function WebsiteHeader({ showActions }: { showActions?: boolean }) { } - titleHref={renderUrl(`/websites/${website.id}`, false)} + titleHref={allowLink ? renderUrl(`/websites/${website.id}`, false) : undefined} > diff --git a/src/app/api/share/[slug]/route.ts b/src/app/api/share/[slug]/route.ts index ed3271ea..62e2fc42 100644 --- a/src/app/api/share/[slug]/route.ts +++ b/src/app/api/share/[slug]/route.ts @@ -1,7 +1,52 @@ +import { ROLES } from '@/lib/constants'; import { secret } from '@/lib/crypto'; import { createToken } from '@/lib/jwt'; +import prisma from '@/lib/prisma'; +import redis from '@/lib/redis'; import { json, notFound } from '@/lib/response'; -import { getShareByCode } from '@/queries/prisma'; +import { getShareByCode, getWebsite } from '@/queries/prisma'; + +export interface WhiteLabel { + name: string; + url: string; + image: string; +} + +async function getAccountId(website: { userId?: string; teamId?: string }): Promise { + if (website.userId) { + return website.userId; + } + + if (website.teamId) { + const teamOwner = await prisma.client.teamUser.findFirst({ + where: { + teamId: website.teamId, + role: ROLES.teamOwner, + }, + select: { + userId: true, + }, + }); + + return teamOwner?.userId || null; + } + + return null; +} + +async function getWhiteLabel(accountId: string): Promise { + if (!redis.enabled) { + return null; + } + + const data = await redis.client.get(`white-label:${accountId}`); + + if (data) { + return data as WhiteLabel; + } + + return null; +} export async function GET(_request: Request, { params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; @@ -12,12 +57,25 @@ export async function GET(_request: Request, { params }: { params: Promise<{ slu return notFound(); } - const data = { + const website = await getWebsite(share.entityId); + + const data: Record = { shareId: share.id, websiteId: share.entityId, parameters: share.parameters, }; - const token = createToken(data, secret()); - return json({ ...data, token }); + data.token = createToken(data, secret()); + + const accountId = await getAccountId(website); + + if (accountId) { + const whiteLabel = await getWhiteLabel(accountId); + + if (whiteLabel) { + data.whiteLabel = whiteLabel; + } + } + + return json(data); } diff --git a/src/app/share/[...shareId]/Footer.tsx b/src/app/share/[...shareId]/Footer.tsx index f2948628..0f17187c 100644 --- a/src/app/share/[...shareId]/Footer.tsx +++ b/src/app/share/[...shareId]/Footer.tsx @@ -1,7 +1,18 @@ import { Row, Text } from '@umami/react-zen'; +import type { WhiteLabel } from '@/app/api/share/[shareId]/route'; import { CURRENT_VERSION, HOMEPAGE_URL } from '@/lib/constants'; -export function Footer() { +export function Footer({ whiteLabel }: { whiteLabel?: WhiteLabel }) { + if (whiteLabel) { + return ( + + + {whiteLabel.name} + + + ); + } + return ( diff --git a/src/app/share/[...shareId]/Header.tsx b/src/app/share/[...shareId]/Header.tsx index d7b7dcb4..78e022af 100644 --- a/src/app/share/[...shareId]/Header.tsx +++ b/src/app/share/[...shareId]/Header.tsx @@ -1,17 +1,26 @@ import { Icon, Row, Text, ThemeButton } from '@umami/react-zen'; +import type { WhiteLabel } from '@/app/api/share/[shareId]/route'; import { LanguageButton } from '@/components/input/LanguageButton'; import { PreferencesButton } from '@/components/input/PreferencesButton'; import { Logo } from '@/components/svg'; -export function Header() { +export function Header({ whiteLabel }: { whiteLabel?: WhiteLabel }) { + const logoUrl = whiteLabel?.url || 'https://umami.is'; + const logoName = whiteLabel?.name || 'umami'; + const logoImage = whiteLabel?.image; + return ( - + - - - - umami + {logoImage ? ( + {logoName} + ) : ( + + + + )} + {logoName} diff --git a/src/app/share/[...shareId]/SharePage.tsx b/src/app/share/[...shareId]/SharePage.tsx index 3e1cedc0..9e8bd8bf 100644 --- a/src/app/share/[...shareId]/SharePage.tsx +++ b/src/app/share/[...shareId]/SharePage.tsx @@ -56,7 +56,7 @@ export function SharePage({ shareId, path = '' }: { shareId: string; path?: stri return null; } - const { websiteId, parameters = {} } = shareToken; + const { websiteId, parameters = {}, whiteLabel } = shareToken; // Check if the requested path is allowed const pageKey = path || ''; @@ -84,10 +84,11 @@ export function SharePage({ shareId, path = '' }: { shareId: string; path?: stri - +
+