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}
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
-
+
+