Use FormattedMessage. Updated icons. Fixed bugs.

This commit is contained in:
Mike Cao 2025-09-23 23:08:40 -07:00
parent 3afe843461
commit 83a014e884
20 changed files with 129 additions and 84 deletions

View file

@ -15,7 +15,7 @@ export function LinkDeleteButton({
name: string;
onSave?: () => void;
}) {
const { formatMessage, labels, getErrorMessage } = useMessages();
const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error, touch } = useDeleteQuery(`/links/${linkId}`);
const handleConfirm = async (close: () => void) => {
@ -33,9 +33,14 @@ export function LinkDeleteButton({
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
{({ close }) => (
<ConfirmationForm
message={formatMessage(messages.confirmRemove, {
target: name,
})}
message={
<FormattedMessage
{...messages.confirmRemove}
values={{
target: <b>{name}</b>,
}}
/>
}
isLoading={isPending}
error={getErrorMessage(error)}
onConfirm={handleConfirm.bind(null, close)}

View file

@ -13,7 +13,7 @@ export function PixelDeleteButton({
name: string;
onSave?: () => void;
}) {
const { formatMessage, labels, getErrorMessage } = useMessages();
const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`);
const { touch } = useModified();
@ -32,9 +32,14 @@ export function PixelDeleteButton({
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
{({ close }) => (
<ConfirmationForm
message={formatMessage(messages.confirmRemove, {
target: name,
})}
message={
<FormattedMessage
{...messages.confirmRemove}
values={{
target: <b>{name}</b>,
}}
/>
}
isLoading={isPending}
error={getErrorMessage(error)}
onConfirm={handleConfirm.bind(null, close)}

View file

@ -14,7 +14,7 @@ export function TeamLeaveForm({
onSave: () => void;
onClose: () => void;
}) {
const { formatMessage, labels, messages, getErrorMessage } = useMessages();
const { formatMessage, labels, messages, getErrorMessage, FormattedMessage } = useMessages();
const { mutateAsync, error, isPending } = useDeleteQuery(`/teams/${teamId}/users/${userId}`);
const { touch } = useModified();
@ -31,9 +31,14 @@ export function TeamLeaveForm({
return (
<ConfirmationForm
buttonLabel={formatMessage(labels.leave)}
message={formatMessage(messages.confirmLeave, {
target: teamName,
})}
message={
<FormattedMessage
{...messages.confirmLeave}
values={{
target: <b>{teamName}</b>,
}}
/>
}
onConfirm={handleConfirm}
onClose={onClose}
isLoading={isPending}

View file

@ -1,16 +1,9 @@
import { ReactNode } from 'react';
import Link from 'next/link';
import { DataGrid } from '@/components/common/DataGrid';
import { TeamsTable } from './TeamsTable';
import { useLoginQuery, useNavigation, useUserTeamsQuery } from '@/components/hooks';
export function TeamsDataTable({
showActions,
}: {
allowEdit?: boolean;
showActions?: boolean;
children?: ReactNode;
}) {
export function TeamsDataTable() {
const { user } = useLoginQuery();
const query = useUserTeamsQuery(user.id);
const { pathname } = useNavigation();
@ -27,7 +20,7 @@ export function TeamsDataTable({
return (
<DataGrid query={query}>
{({ data }) => {
return <TeamsTable data={data} showActions={showActions} renderLink={renderLink} />;
return <TeamsTable data={data} renderLink={renderLink} />;
}}
</DataGrid>
);

View file

@ -1,5 +1,5 @@
import { Button, Icon, Modal, DialogTrigger, Dialog, Text, useToast } from '@umami/react-zen';
import { AddUser } from '@/components/icons';
import { UserPlus } from '@/components/icons';
import { useMessages, useModified } from '@/components/hooks';
import { TeamJoinForm } from './TeamJoinForm';
@ -17,7 +17,7 @@ export function TeamsJoinButton() {
<DialogTrigger>
<Button>
<Icon>
<AddUser />
<UserPlus />
</Icon>
<Text>{formatMessage(labels.joinTeam)}</Text>
</Button>

View file

@ -17,7 +17,7 @@ export function TeamMemberRemoveButton({
disabled?: boolean;
onSave?: () => void;
}) {
const { formatMessage, labels } = useMessages();
const { formatMessage, labels, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error } = useDeleteQuery(`/teams/${teamId}/users/${userId}`);
const { touch } = useModified();
@ -36,9 +36,14 @@ export function TeamMemberRemoveButton({
<Dialog title={formatMessage(labels.removeMember)} style={{ width: 400 }}>
{({ close }) => (
<ConfirmationForm
message={formatMessage(messages.confirmRemove, {
target: userName,
})}
message={
<FormattedMessage
{...messages.confirmRemove}
values={{
target: <b>{userName}</b>,
}}
/>
}
isLoading={isPending}
error={error}
onConfirm={handleConfirm.bind(null, close)}

View file

@ -2,7 +2,7 @@ import Link from 'next/link';
import { Column, Icon, Text, Row } from '@umami/react-zen';
import { useLoginQuery, useMessages, useNavigation, useTeam } from '@/components/hooks';
import { ROLES } from '@/lib/constants';
import { Users, Arrow } from '@/components/icons';
import { Users, ArrowRight } from '@/components/icons';
import { TeamLeaveButton } from '@/app/(main)/teams/TeamLeaveButton';
import { TeamManage } from './TeamManage';
import { TeamEditForm } from './TeamEditForm';
@ -35,7 +35,7 @@ export function TeamSettings({ teamId }: { teamId: string }) {
<Link href="/settings/teams">
<Row marginTop="2" alignItems="center" gap>
<Icon rotate={180}>
<Arrow />
<ArrowRight />
</Icon>
<Text>{formatMessage(labels.teams)}</Text>
</Row>

View file

@ -14,7 +14,7 @@ export function WebsitesPage() {
return (
<PageBody>
<Column gap="6" margin="2">
<PageHeader title={formatMessage(labels.websites)}>
<PageHeader title={formatMessage(labels.websites)} label={'back'} description={'Websites'}>
<WebsiteAddButton teamId={teamId} />
</PageHeader>
<Panel>

View file

@ -14,12 +14,12 @@ export function WebsiteFilterButton({
showText?: boolean;
}) {
const { formatMessage, labels } = useMessages();
const { replaceParams, router } = useNavigation();
const { updateParams, router } = useNavigation();
const handleChange = ({ filters, segment, cohort }: any) => {
const params = filtersArrayToObject(filters);
const url = replaceParams({ ...params, segment, cohort });
const url = updateParams({ ...params, segment, cohort });
router.push(url);
};

View file

@ -16,7 +16,7 @@ export function CohortDeleteButton({
name: string;
onSave?: () => void;
}) {
const { formatMessage, labels } = useMessages();
const { formatMessage, labels, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error, touch } = useDeleteQuery(
`/websites/${websiteId}/segments/${cohortId}`,
);
@ -36,9 +36,14 @@ export function CohortDeleteButton({
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
{({ close }) => (
<ConfirmationForm
message={formatMessage(messages.confirmRemove, {
target: name,
})}
message={
<FormattedMessage
{...messages.confirmRemove}
values={{
target: <b>{name}</b>,
}}
/>
}
isLoading={isPending}
error={error}
onConfirm={handleConfirm.bind(null, close)}

View file

@ -3,7 +3,7 @@ import { useFormat, useMessages, useNavigation } from '@/components/hooks';
import { Empty } from '@/components/common/Empty';
import { Avatar } from '@/components/common/Avatar';
import Link from 'next/link';
import { Bolt, Eye } from '@/components/icons';
import { LightningSvg, Eye } from '@/components/icons';
import { DateDistance } from '@/components/common/DateDistance';
import { TypeIcon } from '@/components/common/TypeIcon';
@ -25,7 +25,7 @@ export function EventsTable({ data = [] }) {
<Link href={renderUrl(`/websites/${row.websiteId}/sessions/${row.sessionId}`)}>
<Avatar seed={row.sessionId} size={32} />
</Link>
<Icon>{row.eventName ? <Bolt /> : <Eye />}</Icon>
<Icon>{row.eventName ? <LightningSvg /> : <Eye />}</Icon>
<Text>
{formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)}
</Text>

View file

@ -8,7 +8,7 @@ import {
useTimezone,
useWebsite,
} from '@/components/hooks';
import { Eye, Visitor, Bolt } from '@/components/icons';
import { Eye, User, LightningSvg } from '@/components/icons';
import { BROWSERS, OS_NAMES } from '@/lib/constants';
import { stringToColor } from '@/lib/format';
import { useMemo, useState } from 'react';
@ -23,14 +23,14 @@ const TYPE_EVENT = 'event';
const icons = {
[TYPE_PAGEVIEW]: <Eye />,
[TYPE_SESSION]: <Visitor />,
[TYPE_EVENT]: <Bolt />,
[TYPE_SESSION]: <User />,
[TYPE_EVENT]: <LightningSvg />,
};
export function RealtimeLog({ data }: { data: any }) {
const website = useWebsite();
const [search, setSearch] = useState('');
const { formatMessage, labels, messages } = useMessages();
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
const { formatValue } = useFormat();
const { locale } = useLocale();
const { formatTimezoneDate } = useTimezone();
@ -74,7 +74,10 @@ export function RealtimeLog({ data }: { data: any }) {
const { __type, eventName, urlPath, browser, os, country, device } = log;
if (__type === TYPE_EVENT) {
return formatMessage(messages.eventLog, {
return (
<FormattedMessage
{...messages.eventLog}
values={{
event: <b key="b">{eventName || formatMessage(labels.unknown)}</b>,
url: (
<a
@ -87,7 +90,9 @@ export function RealtimeLog({ data }: { data: any }) {
{urlPath}
</a>
),
});
}}
/>
);
}
if (__type === TYPE_PAGEVIEW) {
@ -104,12 +109,17 @@ export function RealtimeLog({ data }: { data: any }) {
}
if (__type === TYPE_SESSION) {
return formatMessage(messages.visitorLog, {
return (
<FormattedMessage
{...messages.visitorLog}
values={{
country: <b key="country">{countryNames[country] || formatMessage(labels.unknown)}</b>,
browser: <b key="browser">{BROWSERS[browser]}</b>,
os: <b key="os">{OS_NAMES[os] || os}</b>,
device: <b key="device">{formatMessage(labels[device] || labels.unknown)}</b>,
});
}}
/>
);
}
};

View file

@ -16,7 +16,7 @@ export function SegmentDeleteButton({
name: string;
onSave?: () => void;
}) {
const { formatMessage, labels } = useMessages();
const { formatMessage, labels, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error, touch } = useDeleteQuery(
`/websites/${websiteId}/segments/${segmentId}`,
);
@ -36,9 +36,14 @@ export function SegmentDeleteButton({
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
{({ close }) => (
<ConfirmationForm
message={formatMessage(messages.confirmRemove, {
target: name,
})}
message={
<FormattedMessage
{...messages.confirmRemove}
values={{
target: <b>{name}</b>,
}}
/>
}
isLoading={isPending}
error={error}
onConfirm={handleConfirm.bind(null, close)}

View file

@ -12,7 +12,7 @@ import {
Dialog,
} from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Bolt, Eye, FileText } from '@/components/icons';
import { LightningSvg, Eye, FileText } from '@/components/icons';
import { useMessages, useSessionActivityQuery, useTimezone } from '@/components/hooks';
import { EventData } from '@/components/metrics/EventData';
@ -52,7 +52,7 @@ export function SessionActivity({
{formatTimezoneDate(createdAt, 'pp')}
</StatusLight>
<Row alignItems="center" gap="2">
<Icon>{eventName ? <Bolt /> : <Eye />}</Icon>
<Icon>{eventName ? <LightningSvg /> : <Eye />}</Icon>
<Text>
{eventName
? formatMessage(labels.triggeredEvent)

View file

@ -4,6 +4,7 @@ import { Heading, Icon, Row, RowProps, Text, Column } from '@umami/react-zen';
export function PageHeader({
title,
description,
label,
icon,
showBorder = true,
children,
@ -11,6 +12,7 @@ export function PageHeader({
}: {
title: string;
description?: string;
label?: ReactNode;
icon?: ReactNode;
showBorder?: boolean;
allowEdit?: boolean;
@ -26,7 +28,8 @@ export function PageHeader({
width="100%"
{...props}
>
<Column>
<Column gap="2">
{label}
<Row alignItems="center" gap="3">
{icon && (
<Icon size="md" color="muted">

View file

@ -1,7 +1,22 @@
import { useIntl } from 'react-intl';
import { useIntl, FormattedMessage, type MessageDescriptor } from 'react-intl';
import { messages, labels } from '@/components/messages';
export function useMessages() {
type FormatMessage = (
descriptor: MessageDescriptor,
values?: Record<string, string | number | boolean | null | undefined>,
opts?: any,
) => string | null;
interface UseMessages {
formatMessage: FormatMessage;
messages: typeof messages;
labels: typeof labels;
getMessage: (id: string) => string;
getErrorMessage: (error: unknown) => string | undefined;
FormattedMessage: typeof FormattedMessage;
}
export function useMessages(): UseMessages {
const intl = useIntl();
const getMessage = (id: string) => {
@ -21,15 +36,12 @@ export function useMessages() {
};
const formatMessage = (
descriptor: {
id: string;
defaultMessage: string;
},
values?: Record<string, string>,
descriptor: MessageDescriptor,
values?: Record<string, string | number | boolean | null | undefined>,
opts?: any,
) => {
return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
};
return { formatMessage, messages, labels, getMessage, getErrorMessage };
return { formatMessage, messages, labels, getMessage, getErrorMessage, FormattedMessage };
}

View file

@ -1,17 +1,13 @@
export * from 'lucide-react';
export {
Logo as LogoSvg,
Bolt as BoltSvg,
Change as ChangeSvg,
Compare as CompareSvg,
Funnel as FunnelSvg,
Lightbulb as LightbulbSvg,
Lightning as LightningSvg,
Location as LocationSvg,
Magnet as MagnetSvg,
Money as MoneySvg,
Network as NetworkSvg,
Path as PathSvg,
Tag as TagSvg,
Target as TargetSvg,
} from '@/components/svg';

View file

@ -18,7 +18,7 @@ export function WebsiteDateFilter({
showButtons = true,
allowCompare,
}: WebsiteDateFilterProps) {
const { dateRange } = useDateRange(websiteId);
const { dateRange, saveDateRange } = useDateRange(websiteId);
const { value, endDate } = dateRange;
const { formatMessage, labels } = useMessages();
const {
@ -32,6 +32,7 @@ export function WebsiteDateFilter({
const disableForward = value === 'all' || isAfter(endDate, new Date());
const handleChange = (date: string) => {
saveDateRange(date);
router.push(updateParams({ date, offset: undefined }));
};

View file

@ -85,7 +85,7 @@ export function WeeklyTraffic({ websiteId }: { websiteId: string }) {
height="16px"
borderRadius="full"
style={{ margin: '0 auto' }}
role="cell"
role="button"
>
<Row
backgroundColor="primary"

View file

@ -136,7 +136,7 @@ export async function getQueryFilters(
...dateRange,
...filters,
page: params?.page,
pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
pageSize: params?.pageSize ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
orderBy: params?.orderBy,
sortDescending: params?.sortDescending,
search: params?.search,