diff --git a/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx b/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx
index a7851d78..21501973 100644
--- a/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx
+++ b/src/app/(main)/admin/teams/[teamId]/AdminTeamPage.tsx
@@ -1,6 +1,6 @@
'use client';
import { TeamSettings } from '@/app/(main)/teams/[teamId]/TeamSettings';
-import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
+import { TeamProvider } from '@/app/(main)/teams/TeamProvider';
export function AdminTeamPage({ teamId }: { teamId: string }) {
return (
diff --git a/src/app/(main)/admin/websites/[websiteId]/AdminWebsitePage.tsx b/src/app/(main)/admin/websites/[websiteId]/AdminWebsitePage.tsx
index 54cb9384..5da82afd 100644
--- a/src/app/(main)/admin/websites/[websiteId]/AdminWebsitePage.tsx
+++ b/src/app/(main)/admin/websites/[websiteId]/AdminWebsitePage.tsx
@@ -1,6 +1,6 @@
'use client';
import { WebsiteSettings } from '@/app/(main)/websites/[websiteId]/settings/WebsiteSettings';
-import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
import { Panel } from '@/components/common/Panel';
export function AdminWebsitePage({ websiteId }: { websiteId: string }) {
diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx
index a6257768..38298d9f 100644
--- a/src/app/(main)/links/LinkEditForm.tsx
+++ b/src/app/(main)/links/LinkEditForm.tsx
@@ -32,8 +32,8 @@ export function LinkEditForm({
onSave?: () => void;
onClose?: () => void;
}) {
- const { formatMessage, labels } = useMessages();
- const { mutate, error, isPending, touch } = useUpdateQuery(
+ const { formatMessage, labels, messages } = useMessages();
+ const { mutate, error, isPending, touch, toast } = useUpdateQuery(
linkId ? `/links/${linkId}` : '/links',
{
id: linkId,
@@ -48,6 +48,7 @@ export function LinkEditForm({
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
+ toast(formatMessage(messages.saved));
touch('links');
onSave?.();
onClose?.();
diff --git a/src/app/(main)/links/LinkProvider.tsx b/src/app/(main)/links/LinkProvider.tsx
new file mode 100644
index 00000000..6593d85a
--- /dev/null
+++ b/src/app/(main)/links/LinkProvider.tsx
@@ -0,0 +1,20 @@
+'use client';
+import { createContext, ReactNode } from 'react';
+import { useLinkQuery } from '@/components/hooks';
+import { Loading } from '@umami/react-zen';
+
+export const LinkContext = createContext(null);
+
+export function LinkProvider({ linkId, children }: { linkId?: string; children: ReactNode }) {
+ const { data: link, isLoading, isFetching } = useLinkQuery(linkId);
+
+ if (isFetching && isLoading) {
+ return ;
+ }
+
+ if (!link) {
+ return null;
+ }
+
+ return {children};
+}
diff --git a/src/app/(main)/links/LinksTable.tsx b/src/app/(main)/links/LinksTable.tsx
index 5e854a4a..b8471829 100644
--- a/src/app/(main)/links/LinksTable.tsx
+++ b/src/app/(main)/links/LinksTable.tsx
@@ -1,17 +1,16 @@
+import Link from 'next/link';
import { DataTable, DataColumn, Row } from '@umami/react-zen';
-import { useConfig, useMessages, useNavigation } from '@/components/hooks';
+import { useMessages, useNavigation, useSlug } from '@/components/hooks';
import { Empty } from '@/components/common/Empty';
import { DateDistance } from '@/components/common/DateDistance';
import { ExternalLink } from '@/components/common/ExternalLink';
import { LinkEditButton } from './LinkEditButton';
import { LinkDeleteButton } from './LinkDeleteButton';
-import { LINKS_URL } from '@/lib/constants';
export function LinksTable({ data = [] }) {
const { formatMessage, labels } = useMessages();
- const { websiteId } = useNavigation();
- const { linksUrl } = useConfig();
- const hostUrl = linksUrl || LINKS_URL;
+ const { websiteId, renderUrl } = useNavigation();
+ const { getSlugUrl } = useSlug('link');
if (data.length === 0) {
return ;
@@ -19,10 +18,14 @@ export function LinksTable({ data = [] }) {
return (
-
+
+ {({ id, name }: any) => {
+ return {name};
+ }}
+
{({ slug }: any) => {
- const url = `${hostUrl}/${slug}`;
+ const url = getSlugUrl(slug);
return {url};
}}
diff --git a/src/app/(main)/links/[linkId]/LinkControls.tsx b/src/app/(main)/links/[linkId]/LinkControls.tsx
new file mode 100644
index 00000000..c9fd595f
--- /dev/null
+++ b/src/app/(main)/links/[linkId]/LinkControls.tsx
@@ -0,0 +1,34 @@
+import { Column, Row } from '@umami/react-zen';
+import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
+import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
+import { FilterBar } from '@/components/input/FilterBar';
+import { WebsiteMonthSelect } from '@/components/input/WebsiteMonthSelect';
+import { ExportButton } from '@/components/input/ExportButton';
+
+export function LinkControls({
+ linkId: websiteId,
+ allowFilter = true,
+ allowDateFilter = true,
+ allowMonthFilter,
+ allowCompare,
+ allowDownload = false,
+}: {
+ linkId: string;
+ allowFilter?: boolean;
+ allowCompare?: boolean;
+ allowDateFilter?: boolean;
+ allowMonthFilter?: boolean;
+ allowDownload?: boolean;
+}) {
+ return (
+
+
+ {allowFilter ? : }
+ {allowDateFilter && }
+ {allowDownload && }
+ {allowMonthFilter && }
+
+ {allowFilter && }
+
+ );
+}
diff --git a/src/app/(main)/links/[linkId]/LinkHeader.tsx b/src/app/(main)/links/[linkId]/LinkHeader.tsx
new file mode 100644
index 00000000..6f8f7a57
--- /dev/null
+++ b/src/app/(main)/links/[linkId]/LinkHeader.tsx
@@ -0,0 +1,22 @@
+import { useLink, useMessages, useSlug } from '@/components/hooks';
+import { PageHeader } from '@/components/common/PageHeader';
+import { Icon, Text } from '@umami/react-zen';
+import { ExternalLink } from '@/components/icons';
+import { LinkButton } from '@/components/common/LinkButton';
+
+export function LinkHeader() {
+ const { formatMessage, labels } = useMessages();
+ const { getSlugUrl } = useSlug('link');
+ const link = useLink();
+
+ return (
+
+
+
+
+
+ {formatMessage(labels.view)}
+
+
+ );
+}
diff --git a/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx b/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx
new file mode 100644
index 00000000..5e9d6cbf
--- /dev/null
+++ b/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx
@@ -0,0 +1,71 @@
+import { useDateRange, useMessages } from '@/components/hooks';
+import { MetricCard } from '@/components/metrics/MetricCard';
+import { MetricsBar } from '@/components/metrics/MetricsBar';
+import { formatLongNumber } from '@/lib/format';
+import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
+import { LoadingPanel } from '@/components/common/LoadingPanel';
+
+export function LinkMetricsBar({
+ linkId,
+}: {
+ linkId: string;
+ showChange?: boolean;
+ compareMode?: boolean;
+}) {
+ const { dateRange } = useDateRange(linkId);
+ const { formatMessage, labels } = useMessages();
+ const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(linkId);
+ const isAllTime = dateRange.value === 'all';
+
+ const { pageviews, visitors, visits, comparison } = data || {};
+
+ const metrics = data
+ ? [
+ {
+ value: visitors,
+ label: formatMessage(labels.visitors),
+ change: visitors - comparison.visitors,
+ formatValue: formatLongNumber,
+ },
+ {
+ value: visits,
+ label: formatMessage(labels.visits),
+ change: visits - comparison.visits,
+ formatValue: formatLongNumber,
+ },
+ {
+ value: pageviews,
+ label: formatMessage(labels.views),
+ change: pageviews - comparison.pageviews,
+ formatValue: formatLongNumber,
+ },
+ ]
+ : null;
+
+ return (
+
+
+ {metrics?.map(({ label, value, prev, change, formatValue, reverseColors }: any) => {
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/app/(main)/links/[linkId]/LinkPage.tsx b/src/app/(main)/links/[linkId]/LinkPage.tsx
new file mode 100644
index 00000000..d57f937a
--- /dev/null
+++ b/src/app/(main)/links/[linkId]/LinkPage.tsx
@@ -0,0 +1,59 @@
+'use client';
+import { PageBody } from '@/components/common/PageBody';
+import { LinkProvider } from '@/app/(main)/links/LinkProvider';
+import { LinkHeader } from '@/app/(main)/links/[linkId]/LinkHeader';
+import { Panel } from '@/components/common/Panel';
+import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
+import { LinkMetricsBar } from '@/app/(main)/links/[linkId]/LinkMetricsBar';
+import { LinkControls } from '@/app/(main)/links/[linkId]/LinkControls';
+import { Grid } from '@umami/react-zen';
+import { GridRow } from '@/components/common/GridRow';
+import { ReferrersTable } from '@/components/metrics/ReferrersTable';
+import { BrowsersTable } from '@/components/metrics/BrowsersTable';
+import { OSTable } from '@/components/metrics/OSTable';
+import { DevicesTable } from '@/components/metrics/DevicesTable';
+import { WorldMap } from '@/components/metrics/WorldMap';
+import { CountriesTable } from '@/components/metrics/CountriesTable';
+
+export function LinkPage({ linkId }: { linkId: string }) {
+ const props = { websiteId: linkId, limit: 10, allowDownload: false };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/links/[linkId]/page.tsx b/src/app/(main)/links/[linkId]/page.tsx
new file mode 100644
index 00000000..ae3b2c9a
--- /dev/null
+++ b/src/app/(main)/links/[linkId]/page.tsx
@@ -0,0 +1,12 @@
+import { LinkPage } from './LinkPage';
+import { Metadata } from 'next';
+
+export default async function ({ params }: { params: Promise<{ linkId: string }> }) {
+ const { linkId } = await params;
+
+ return ;
+}
+
+export const metadata: Metadata = {
+ title: 'Link',
+};
diff --git a/src/app/(main)/pixels/PixelEditForm.tsx b/src/app/(main)/pixels/PixelEditForm.tsx
index 8026c76a..6024a745 100644
--- a/src/app/(main)/pixels/PixelEditForm.tsx
+++ b/src/app/(main)/pixels/PixelEditForm.tsx
@@ -31,8 +31,8 @@ export function PixelEditForm({
onSave?: () => void;
onClose?: () => void;
}) {
- const { formatMessage, labels } = useMessages();
- const { mutate, error, isPending, touch } = useUpdateQuery(
+ const { formatMessage, labels, messages } = useMessages();
+ const { mutate, error, isPending, touch, toast } = useUpdateQuery(
pixelId ? `/pixels/${pixelId}` : '/pixels',
{
id: pixelId,
@@ -47,6 +47,7 @@ export function PixelEditForm({
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
+ toast(formatMessage(messages.saved));
touch('pixels');
onSave?.();
onClose?.();
diff --git a/src/app/(main)/pixels/PixelProvider.tsx b/src/app/(main)/pixels/PixelProvider.tsx
new file mode 100644
index 00000000..1a5c1d5d
--- /dev/null
+++ b/src/app/(main)/pixels/PixelProvider.tsx
@@ -0,0 +1,20 @@
+'use client';
+import { createContext, ReactNode } from 'react';
+import { usePixelQuery } from '@/components/hooks';
+import { Loading } from '@umami/react-zen';
+
+export const PixelContext = createContext(null);
+
+export function PixelProvider({ pixelId, children }: { pixelId?: string; children: ReactNode }) {
+ const { data: pixel, isLoading, isFetching } = usePixelQuery(pixelId);
+
+ if (isFetching && isLoading) {
+ return ;
+ }
+
+ if (!pixel) {
+ return null;
+ }
+
+ return {children};
+}
diff --git a/src/app/(main)/pixels/PixelsTable.tsx b/src/app/(main)/pixels/PixelsTable.tsx
index cb11554e..dcb5307c 100644
--- a/src/app/(main)/pixels/PixelsTable.tsx
+++ b/src/app/(main)/pixels/PixelsTable.tsx
@@ -1,16 +1,16 @@
+import Link from 'next/link';
import { DataTable, DataColumn, Row } from '@umami/react-zen';
-import { useConfig, useMessages } from '@/components/hooks';
+import { useMessages, useNavigation, useSlug } from '@/components/hooks';
import { Empty } from '@/components/common/Empty';
import { DateDistance } from '@/components/common/DateDistance';
import { PixelEditButton } from './PixelEditButton';
import { PixelDeleteButton } from './PixelDeleteButton';
-import { PIXELS_URL } from '@/lib/constants';
import { ExternalLink } from '@/components/common/ExternalLink';
export function PixelsTable({ data = [] }) {
const { formatMessage, labels } = useMessages();
- const { pixelsUrl } = useConfig();
- const hostUrl = pixelsUrl || PIXELS_URL;
+ const { renderUrl } = useNavigation();
+ const { getSlugUrl } = useSlug('pixel');
if (data.length === 0) {
return ;
@@ -18,10 +18,14 @@ export function PixelsTable({ data = [] }) {
return (
-
+
+ {({ id, name }: any) => {
+ return {name};
+ }}
+
{({ slug }: any) => {
- const url = `${hostUrl}/${slug}`;
+ const url = getSlugUrl(slug);
return {url};
}}
diff --git a/src/app/(main)/pixels/[pixelId]/PixelControls.tsx b/src/app/(main)/pixels/[pixelId]/PixelControls.tsx
new file mode 100644
index 00000000..6bd829cf
--- /dev/null
+++ b/src/app/(main)/pixels/[pixelId]/PixelControls.tsx
@@ -0,0 +1,34 @@
+import { Column, Row } from '@umami/react-zen';
+import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
+import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
+import { FilterBar } from '@/components/input/FilterBar';
+import { WebsiteMonthSelect } from '@/components/input/WebsiteMonthSelect';
+import { ExportButton } from '@/components/input/ExportButton';
+
+export function PixelControls({
+ pixelId: websiteId,
+ allowFilter = true,
+ allowDateFilter = true,
+ allowMonthFilter,
+ allowCompare,
+ allowDownload = false,
+}: {
+ pixelId: string;
+ allowFilter?: boolean;
+ allowCompare?: boolean;
+ allowDateFilter?: boolean;
+ allowMonthFilter?: boolean;
+ allowDownload?: boolean;
+}) {
+ return (
+
+
+ {allowFilter ? : }
+ {allowDateFilter && }
+ {allowDownload && }
+ {allowMonthFilter && }
+
+ {allowFilter && }
+
+ );
+}
diff --git a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx
new file mode 100644
index 00000000..ad81a9bf
--- /dev/null
+++ b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx
@@ -0,0 +1,22 @@
+import { usePixel, useMessages, useSlug } from '@/components/hooks';
+import { PageHeader } from '@/components/common/PageHeader';
+import { Icon, Text } from '@umami/react-zen';
+import { ExternalLink } from '@/components/icons';
+import { LinkButton } from '@/components/common/LinkButton';
+
+export function PixelHeader() {
+ const { formatMessage, labels } = useMessages();
+ const { getSlugUrl } = useSlug('pixel');
+ const pixel = usePixel();
+
+ return (
+
+
+
+
+
+ {formatMessage(labels.view)}
+
+
+ );
+}
diff --git a/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx b/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx
new file mode 100644
index 00000000..5b01ef84
--- /dev/null
+++ b/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx
@@ -0,0 +1,71 @@
+import { useDateRange, useMessages } from '@/components/hooks';
+import { MetricCard } from '@/components/metrics/MetricCard';
+import { MetricsBar } from '@/components/metrics/MetricsBar';
+import { formatLongNumber } from '@/lib/format';
+import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
+import { LoadingPanel } from '@/components/common/LoadingPanel';
+
+export function PixelMetricsBar({
+ pixelId,
+}: {
+ pixelId: string;
+ showChange?: boolean;
+ compareMode?: boolean;
+}) {
+ const { dateRange } = useDateRange(pixelId);
+ const { formatMessage, labels } = useMessages();
+ const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(pixelId);
+ const isAllTime = dateRange.value === 'all';
+
+ const { pageviews, visitors, visits, comparison } = data || {};
+
+ const metrics = data
+ ? [
+ {
+ value: visitors,
+ label: formatMessage(labels.visitors),
+ change: visitors - comparison.visitors,
+ formatValue: formatLongNumber,
+ },
+ {
+ value: visits,
+ label: formatMessage(labels.visits),
+ change: visits - comparison.visits,
+ formatValue: formatLongNumber,
+ },
+ {
+ value: pageviews,
+ label: formatMessage(labels.views),
+ change: pageviews - comparison.pageviews,
+ formatValue: formatLongNumber,
+ },
+ ]
+ : null;
+
+ return (
+
+
+ {metrics?.map(({ label, value, prev, change, formatValue, reverseColors }: any) => {
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/app/(main)/pixels/[pixelId]/PixelPage.tsx b/src/app/(main)/pixels/[pixelId]/PixelPage.tsx
new file mode 100644
index 00000000..eb42b125
--- /dev/null
+++ b/src/app/(main)/pixels/[pixelId]/PixelPage.tsx
@@ -0,0 +1,59 @@
+'use client';
+import { PageBody } from '@/components/common/PageBody';
+import { PixelProvider } from '@/app/(main)/pixels/PixelProvider';
+import { PixelHeader } from '@/app/(main)/pixels/[pixelId]/PixelHeader';
+import { Panel } from '@/components/common/Panel';
+import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
+import { PixelMetricsBar } from '@/app/(main)/pixels/[pixelId]/PixelMetricsBar';
+import { PixelControls } from '@/app/(main)/pixels/[pixelId]/PixelControls';
+import { Grid } from '@umami/react-zen';
+import { GridRow } from '@/components/common/GridRow';
+import { ReferrersTable } from '@/components/metrics/ReferrersTable';
+import { BrowsersTable } from '@/components/metrics/BrowsersTable';
+import { OSTable } from '@/components/metrics/OSTable';
+import { DevicesTable } from '@/components/metrics/DevicesTable';
+import { WorldMap } from '@/components/metrics/WorldMap';
+import { CountriesTable } from '@/components/metrics/CountriesTable';
+
+export function PixelPage({ pixelId }: { pixelId: string }) {
+ const props = { websiteId: pixelId, limit: 10, allowDownload: false };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/pixels/[pixelId]/page.tsx b/src/app/(main)/pixels/[pixelId]/page.tsx
new file mode 100644
index 00000000..1cb72c1d
--- /dev/null
+++ b/src/app/(main)/pixels/[pixelId]/page.tsx
@@ -0,0 +1,12 @@
+import { PixelPage } from './PixelPage';
+import { Metadata } from 'next';
+
+export default async function ({ params }: { params: Promise<{ pixelId: string }> }) {
+ const { pixelId } = await params;
+
+ return ;
+}
+
+export const metadata: Metadata = {
+ title: 'Pixel',
+};
diff --git a/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx b/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx
index 3736299d..8ad2b694 100644
--- a/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx
+++ b/src/app/(main)/settings/teams/[teamId]/TeamSettingsPage.tsx
@@ -1,5 +1,5 @@
'use client';
-import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
+import { TeamProvider } from '@/app/(main)/teams/TeamProvider';
import { TeamSettings } from '@/app/(main)/teams/[teamId]/TeamSettings';
export function TeamSettingsPage({ teamId }: { teamId: string }) {
diff --git a/src/app/(main)/settings/websites/[websiteId]/WebsiteSettingsPage.tsx b/src/app/(main)/settings/websites/[websiteId]/WebsiteSettingsPage.tsx
index 59df2f3c..e5140cb2 100644
--- a/src/app/(main)/settings/websites/[websiteId]/WebsiteSettingsPage.tsx
+++ b/src/app/(main)/settings/websites/[websiteId]/WebsiteSettingsPage.tsx
@@ -1,6 +1,6 @@
'use client';
import { Column } from '@umami/react-zen';
-import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
import { WebsiteSettings } from '@/app/(main)/websites/[websiteId]/settings/WebsiteSettings';
import { WebsiteSettingsHeader } from '@/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader';
import { Panel } from '@/components/common/Panel';
diff --git a/src/app/(main)/teams/[teamId]/TeamProvider.tsx b/src/app/(main)/teams/TeamProvider.tsx
similarity index 100%
rename from src/app/(main)/teams/[teamId]/TeamProvider.tsx
rename to src/app/(main)/teams/TeamProvider.tsx
diff --git a/src/app/(main)/teams/[teamId]/TeamEditForm.tsx b/src/app/(main)/teams/[teamId]/TeamEditForm.tsx
index 9c053ac8..ec3b8fb0 100644
--- a/src/app/(main)/teams/[teamId]/TeamEditForm.tsx
+++ b/src/app/(main)/teams/[teamId]/TeamEditForm.tsx
@@ -10,7 +10,7 @@ import {
import { getRandomChars } from '@/lib/crypto';
import { useContext } from 'react';
import { useApi, useMessages, useModified } from '@/components/hooks';
-import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
+import { TeamContext } from '@/app/(main)/teams/TeamProvider';
const generateId = () => `team_${getRandomChars(16)}`;
diff --git a/src/app/(main)/teams/[teamId]/TeamSettings.tsx b/src/app/(main)/teams/[teamId]/TeamSettings.tsx
index acf1a936..af2a4f32 100644
--- a/src/app/(main)/teams/[teamId]/TeamSettings.tsx
+++ b/src/app/(main)/teams/[teamId]/TeamSettings.tsx
@@ -1,6 +1,6 @@
import { useContext, useState } from 'react';
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
-import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
+import { TeamContext } from '@/app/(main)/teams/TeamProvider';
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
import { ROLES } from '@/lib/constants';
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx b/src/app/(main)/websites/WebsiteProvider.tsx
similarity index 100%
rename from src/app/(main)/websites/[websiteId]/WebsiteProvider.tsx
rename to src/app/(main)/websites/WebsiteProvider.tsx
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx b/src/app/(main)/websites/[websiteId]/WebsitePage.tsx
similarity index 95%
rename from src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx
rename to src/app/(main)/websites/[websiteId]/WebsitePage.tsx
index 33bca5ae..8a0edbfc 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsitePage.tsx
@@ -8,7 +8,7 @@ import { WebsiteMetricsBar } from './WebsiteMetricsBar';
import { WebsiteTableView } from './WebsiteTableView';
import { WebsiteControls } from './WebsiteControls';
-export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
+export function WebsitePage({ websiteId }: { websiteId: string }) {
const {
router,
query: { view, compare },
diff --git a/src/app/(main)/websites/[websiteId]/page.tsx b/src/app/(main)/websites/[websiteId]/page.tsx
index 286a4612..9755e6d0 100644
--- a/src/app/(main)/websites/[websiteId]/page.tsx
+++ b/src/app/(main)/websites/[websiteId]/page.tsx
@@ -1,10 +1,10 @@
-import { WebsiteDetailsPage } from './WebsiteDetailsPage';
+import { WebsitePage } from './WebsitePage';
import { Metadata } from 'next';
-export default async function WebsitePage({ params }: { params: Promise<{ websiteId: string }> }) {
+export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
const { websiteId } = await params;
- return ;
+ return ;
}
export const metadata: Metadata = {
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
index cb7cb0d1..dbefd669 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
@@ -9,7 +9,7 @@ import {
} from '@umami/react-zen';
import { useApi, useMessages, useModified } from '@/components/hooks';
import { DOMAIN_REGEX } from '@/lib/constants';
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
const website = useContext(WebsiteContext);
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx
index baf59452..de775b22 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettings.tsx
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
import { useMessages } from '@/components/hooks';
import { WebsiteShareForm } from './WebsiteShareForm';
import { WebsiteTrackingCode } from './WebsiteTrackingCode';
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader.tsx
index ee542f2f..5f8c8536 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader.tsx
@@ -1,5 +1,5 @@
import { useContext } from 'react';
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
import { PageHeader } from '@/components/common/PageHeader';
import { Globe } from '@/components/icons';
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx
index 1545f84d..b1b3a45b 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteTransferForm.tsx
@@ -11,7 +11,7 @@ import {
Text,
} from '@umami/react-zen';
import { useApi, useLoginQuery, useMessages, useUserTeamsQuery } from '@/components/hooks';
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
import { ROLES } from '@/lib/constants';
export function WebsiteTransferForm({
diff --git a/src/app/share/[...shareId]/SharePage.tsx b/src/app/share/[...shareId]/SharePage.tsx
index c5f734b5..ae2be609 100644
--- a/src/app/share/[...shareId]/SharePage.tsx
+++ b/src/app/share/[...shareId]/SharePage.tsx
@@ -1,6 +1,6 @@
'use client';
-import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
-import { WebsiteDetailsPage } from '@/app/(main)/websites/[websiteId]/WebsiteDetailsPage';
+import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
+import { WebsitePage } from '@/app/(main)/websites/[websiteId]/WebsitePage';
import { useShareTokenQuery } from '@/components/hooks';
import { PageBody } from '@/components/common/PageBody';
import { Header } from './Header';
@@ -17,7 +17,7 @@ export function SharePage({ shareId }) {
-
+
diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx
index 92f8af0c..69361115 100644
--- a/src/components/common/PageHeader.tsx
+++ b/src/components/common/PageHeader.tsx
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
-import { Heading, Icon, Row, RowProps, Text } from '@umami/react-zen';
+import { Heading, Icon, Row, RowProps, Text, Column } from '@umami/react-zen';
export function PageHeader({
title,
@@ -26,11 +26,13 @@ export function PageHeader({
width="100%"
{...props}
>
-
- {icon && {icon}}
- {title && {title}}
+
+
+ {icon && {icon}}
+ {title && {title}}
+
{description && {description}}
-
+
{children}
);
diff --git a/src/components/hooks/context/useLink.ts b/src/components/hooks/context/useLink.ts
new file mode 100644
index 00000000..765b9f59
--- /dev/null
+++ b/src/components/hooks/context/useLink.ts
@@ -0,0 +1,6 @@
+import { LinkContext } from '@/app/(main)/links/LinkProvider';
+import { useContext } from 'react';
+
+export function useLink() {
+ return useContext(LinkContext);
+}
diff --git a/src/components/hooks/context/usePixel.ts b/src/components/hooks/context/usePixel.ts
new file mode 100644
index 00000000..bb129c31
--- /dev/null
+++ b/src/components/hooks/context/usePixel.ts
@@ -0,0 +1,6 @@
+import { PixelContext } from '@/app/(main)/pixels/PixelProvider';
+import { useContext } from 'react';
+
+export function usePixel() {
+ return useContext(PixelContext);
+}
diff --git a/src/components/hooks/useTeam.ts b/src/components/hooks/context/useTeam.ts
similarity index 58%
rename from src/components/hooks/useTeam.ts
rename to src/components/hooks/context/useTeam.ts
index 8162fcb1..11b23daf 100644
--- a/src/components/hooks/useTeam.ts
+++ b/src/components/hooks/context/useTeam.ts
@@ -1,4 +1,4 @@
-import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
+import { TeamContext } from '@/app/(main)/teams/TeamProvider';
import { useContext } from 'react';
export function useTeam() {
diff --git a/src/components/hooks/useWebsite.ts b/src/components/hooks/context/useWebsite.ts
similarity index 56%
rename from src/components/hooks/useWebsite.ts
rename to src/components/hooks/context/useWebsite.ts
index 69a477d2..eb731875 100644
--- a/src/components/hooks/useWebsite.ts
+++ b/src/components/hooks/context/useWebsite.ts
@@ -1,4 +1,4 @@
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
import { useContext } from 'react';
export function useWebsite() {
diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts
index 8505f29d..2de25765 100644
--- a/src/components/hooks/index.ts
+++ b/src/components/hooks/index.ts
@@ -1,5 +1,11 @@
'use client';
+// Context hooks
+export * from './context/useLink';
+export * from './context/usePixel';
+export * from './context/useTeam';
+export * from './context/useWebsite';
+
// Query hooks
export * from './queries/useActiveUsersQuery';
export * from './queries/useDeleteQuery';
@@ -70,7 +76,6 @@ export * from './useNavigation';
export * from './usePagedQuery';
export * from './usePageParameters';
export * from './useRegionNames';
+export * from './useSlug';
export * from './useSticky';
-export * from './useTeam';
export * from './useTimezone';
-export * from './useWebsite';
diff --git a/src/components/hooks/queries/useUpdateQuery.ts b/src/components/hooks/queries/useUpdateQuery.ts
index f5cc7cec..dd4e2247 100644
--- a/src/components/hooks/queries/useUpdateQuery.ts
+++ b/src/components/hooks/queries/useUpdateQuery.ts
@@ -1,4 +1,6 @@
-import { useApi, useModified } from '@/components/hooks';
+import { useApi } from '../useApi';
+import { useModified } from '../useModified';
+import { useToast } from '@umami/react-zen';
export function useUpdateQuery(path: string, params?: Record) {
const { post, useMutation } = useApi();
@@ -6,6 +8,7 @@ export function useUpdateQuery(path: string, params?: Record) {
mutationFn: (data: Record) => post(path, { ...data, ...params }),
});
const { touch } = useModified();
+ const { toast } = useToast();
- return { mutate, isPending, error, touch };
+ return { mutate, isPending, error, touch, toast };
}
diff --git a/src/components/hooks/useSlug.ts b/src/components/hooks/useSlug.ts
new file mode 100644
index 00000000..f795dfeb
--- /dev/null
+++ b/src/components/hooks/useSlug.ts
@@ -0,0 +1,14 @@
+import { useConfig } from '@/components/hooks/useConfig';
+import { LINKS_URL, PIXELS_URL } from '@/lib/constants';
+
+export function useSlug(type: 'link' | 'pixel') {
+ const { linksUrl, pixelsUrl } = useConfig();
+
+ const hostUrl = type === 'link' ? linksUrl || LINKS_URL : pixelsUrl || PIXELS_URL;
+
+ const getSlugUrl = (slug: string) => {
+ return `${hostUrl}/${slug}`;
+ };
+
+ return { getSlugUrl, hostUrl };
+}
diff --git a/src/components/messages.ts b/src/components/messages.ts
index 94988d5e..c2e4232c 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -358,7 +358,7 @@ export const labels = defineMessages({
export const messages = defineMessages({
error: { id: 'message.error', defaultMessage: 'Something went wrong.' },
- saved: { id: 'message.saved', defaultMessage: 'Saved.' },
+ saved: { id: 'message.saved', defaultMessage: 'Saved successfully.' },
noUsers: { id: 'message.no-users', defaultMessage: 'There are no users.' },
userDeleted: { id: 'message.user-deleted', defaultMessage: 'User deleted.' },
noDataAvailable: { id: 'message.no-data-available', defaultMessage: 'No data available.' },
diff --git a/src/components/metrics/PagesTable.tsx b/src/components/metrics/PagesTable.tsx
index 086d3dc7..a4705960 100644
--- a/src/components/metrics/PagesTable.tsx
+++ b/src/components/metrics/PagesTable.tsx
@@ -1,4 +1,4 @@
-import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteContext } from '@/app/(main)/websites/WebsiteProvider';
import { FilterButtons } from '@/components/input/FilterButtons';
import { FilterLink } from '@/components/common/FilterLink';
import { useMessages, useNavigation } from '@/components/hooks';
diff --git a/src/index.ts b/src/index.ts
index 0c84624c..6916f558 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -17,7 +17,7 @@ export * from '@/app/(main)/teams/TeamAddForm';
export * from '@/app/(main)/teams/TeamJoinForm';
export * from '@/app/(main)/teams/TeamLeaveButton';
export * from '@/app/(main)/teams/TeamLeaveForm';
-export * from '@/app/(main)/teams/[teamId]/TeamProvider';
+export * from '@/app/(main)/teams/TeamProvider';
export * from '@/app/(main)/teams/TeamsAddButton';
export * from '@/app/(main)/teams/TeamsDataTable';
export * from '@/app/(main)/teams/TeamsHeader';
@@ -38,7 +38,7 @@ export * from '@/app/(main)/websites/WebsitesDataTable';
export * from '@/app/(main)/websites/WebsitesHeader';
export * from '@/app/(main)/websites/WebsitesTable';
-export * from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+export * from '@/app/(main)/websites/WebsiteProvider';
export * from '@/components/common/ConfirmationForm';
export * from '@/components/common/DataGrid';