mirror of
https://github.com/umami-software/umami.git
synced 2026-02-15 18:15:35 +01:00
Simplify i18n: remove old react-intl artifacts, rename formatMessage to t, replace FormattedMessage with t.rich().
- Rewrite messages.ts to plain string key maps (remove MessageDescriptor) - Rewrite useMessages hook to expose t from useTranslations() directly - Rename formatMessage → t across 193 consumer files - Replace custom FormattedMessage component with next-intl t.rich() - Update 52 language files to use rich text tags (<b>, <a>) - Remove all direct imports from @/components/messages in favor of useMessages() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
80cad6ea65
commit
50edb71687
247 changed files with 1660 additions and 2194 deletions
|
|
@ -11,7 +11,7 @@ import { AdminNav } from './admin/AdminNav';
|
|||
import { SettingsNav } from './settings/SettingsNav';
|
||||
|
||||
export function MobileNav() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname, websiteId, renderUrl } = useNavigation();
|
||||
const isAdmin = pathname.includes('/admin');
|
||||
const isSettings = pathname.includes('/settings');
|
||||
|
|
@ -19,19 +19,19 @@ export function MobileNav() {
|
|||
const links = [
|
||||
{
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
label: t(labels.websites),
|
||||
path: '/websites',
|
||||
icon: <Globe />,
|
||||
},
|
||||
{
|
||||
id: 'links',
|
||||
label: formatMessage(labels.links),
|
||||
label: t(labels.links),
|
||||
path: '/links',
|
||||
icon: <LinkIcon />,
|
||||
},
|
||||
{
|
||||
id: 'pixels',
|
||||
label: formatMessage(labels.pixels),
|
||||
label: t(labels.pixels),
|
||||
path: '/pixels',
|
||||
icon: <Grid2x2 />,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { NavButton } from '@/components/input/NavButton';
|
|||
import { Logo } from '@/components/svg';
|
||||
|
||||
export function SideNav(props: any) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname, renderUrl, websiteId, router } = useNavigation();
|
||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed', false);
|
||||
|
||||
|
|
@ -30,25 +30,25 @@ export function SideNav(props: any) {
|
|||
const links = [
|
||||
{
|
||||
id: 'boards',
|
||||
label: formatMessage(labels.boards),
|
||||
label: t(labels.boards),
|
||||
path: '/boards',
|
||||
icon: <LayoutDashboard />,
|
||||
},
|
||||
{
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
label: t(labels.websites),
|
||||
path: '/websites',
|
||||
icon: <Globe />,
|
||||
},
|
||||
{
|
||||
id: 'links',
|
||||
label: formatMessage(labels.links),
|
||||
label: t(labels.links),
|
||||
path: '/links',
|
||||
icon: <LinkIcon />,
|
||||
},
|
||||
{
|
||||
id: 'pixels',
|
||||
label: formatMessage(labels.pixels),
|
||||
label: t(labels.pixels),
|
||||
path: '/pixels',
|
||||
icon: <Grid2x2 />,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { setItem } from '@/lib/storage';
|
|||
import { checkVersion, useVersion } from '@/store/version';
|
||||
|
||||
export function UpdateNotice({ user, config }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { latest, checked, hasUpdate, releaseUrl } = useVersion();
|
||||
const pathname = usePathname();
|
||||
const [dismissed, setDismissed] = useState(checked);
|
||||
|
|
@ -49,11 +49,11 @@ export function UpdateNotice({ user, config }) {
|
|||
return (
|
||||
<Column justifyContent="center" alignItems="center" position="fixed" top="10px" width="100%">
|
||||
<Row width="600px">
|
||||
<AlertBanner title={formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}>
|
||||
<AlertBanner title={t(messages.newVersionAvailable, { version: `v${latest}` })}>
|
||||
<Button variant="primary" onPress={handleViewClick}>
|
||||
{formatMessage(labels.viewDetails)}
|
||||
{t(labels.viewDetails)}
|
||||
</Button>
|
||||
<Button onPress={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
|
||||
<Button onPress={handleDismissClick}>{t(labels.dismiss)}</Button>
|
||||
</AlertBanner>
|
||||
</Row>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -3,28 +3,28 @@ import { useMessages, useNavigation } from '@/components/hooks';
|
|||
import { Globe, User, Users } from '@/components/icons';
|
||||
|
||||
export function AdminNav({ onItemClick }: { onItemClick?: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: formatMessage(labels.manage),
|
||||
label: t(labels.manage),
|
||||
items: [
|
||||
{
|
||||
id: 'users',
|
||||
label: formatMessage(labels.users),
|
||||
label: t(labels.users),
|
||||
path: '/admin/users',
|
||||
icon: <User />,
|
||||
},
|
||||
{
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
label: t(labels.websites),
|
||||
path: '/admin/websites',
|
||||
icon: <Globe />,
|
||||
},
|
||||
{
|
||||
id: 'teams',
|
||||
label: formatMessage(labels.teams),
|
||||
label: t(labels.teams),
|
||||
path: '/admin/teams',
|
||||
icon: <Users />,
|
||||
},
|
||||
|
|
@ -39,7 +39,7 @@ export function AdminNav({ onItemClick }: { onItemClick?: () => void }) {
|
|||
return (
|
||||
<NavMenu
|
||||
items={items}
|
||||
title={formatMessage(labels.admin)}
|
||||
title={t(labels.admin)}
|
||||
selectedKey={selectedKey}
|
||||
allowMinimize={false}
|
||||
onItemClick={onItemClick}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import { TeamsAddButton } from '../../teams/TeamsAddButton';
|
|||
import { AdminTeamsDataTable } from './AdminTeamsDataTable';
|
||||
|
||||
export function AdminTeamsPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {};
|
||||
|
||||
return (
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<PageHeader title={t(labels.teams)}>
|
||||
<TeamsAddButton onSave={handleSave} isAdmin={true} />
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -14,22 +14,22 @@ export function AdminTeamsTable({
|
|||
data: any[];
|
||||
showActions?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const [deleteTeam, setDeleteTeam] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)} width="1fr">
|
||||
<DataColumn id="name" label={t(labels.name)} width="1fr">
|
||||
{(row: any) => <Link href={`/admin/teams/${row.id}`}>{row.name}</Link>}
|
||||
</DataColumn>
|
||||
<DataColumn id="websites" label={formatMessage(labels.members)} width="140px">
|
||||
<DataColumn id="websites" label={t(labels.members)} width="140px">
|
||||
{(row: any) => row?._count?.members}
|
||||
</DataColumn>
|
||||
<DataColumn id="members" label={formatMessage(labels.websites)} width="140px">
|
||||
<DataColumn id="members" label={t(labels.websites)} width="140px">
|
||||
{(row: any) => row?._count?.websites}
|
||||
</DataColumn>
|
||||
<DataColumn id="owner" label={formatMessage(labels.owner)}>
|
||||
<DataColumn id="owner" label={t(labels.owner)}>
|
||||
{(row: any) => {
|
||||
const name = row?.members?.[0]?.user?.username;
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ export function AdminTeamsTable({
|
|||
);
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)} width="160px">
|
||||
<DataColumn id="created" label={t(labels.created)} width="160px">
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
{showActions && (
|
||||
|
|
@ -55,7 +55,7 @@ export function AdminTeamsTable({
|
|||
<Icon>
|
||||
<Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
<Text>{t(labels.edit)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
|
|
@ -67,7 +67,7 @@ export function AdminTeamsTable({
|
|||
<Icon>
|
||||
<Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
<Text>{t(labels.delete)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
</MenuButton>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { Plus } from '@/components/icons';
|
|||
import { UserAddForm } from './UserAddForm';
|
||||
|
||||
export function UserAddButton({ onSave }: { onSave?: () => void }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('users');
|
||||
onSave?.();
|
||||
};
|
||||
|
|
@ -20,10 +20,10 @@ export function UserAddButton({ onSave }: { onSave?: () => void }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.createUser)}</Text>
|
||||
<Text>{t(labels.createUser)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.createUser)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.createUser)} style={{ width: 400 }}>
|
||||
{({ close }) => <UserAddForm onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ import {
|
|||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
||||
import { messages } from '@/components/messages';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
||||
export function UserAddForm({ onSave, onClose }) {
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery(`/users`);
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
|
|
@ -29,45 +28,41 @@ export function UserAddForm({ onSave, onClose }) {
|
|||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
|
||||
<FormField
|
||||
label={formatMessage(labels.username)}
|
||||
label={t(labels.username)}
|
||||
name="username"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<TextField autoComplete="new-username" data-test="input-username" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={formatMessage(labels.password)}
|
||||
label={t(labels.password)}
|
||||
name="password"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) },
|
||||
required: t(labels.required),
|
||||
minLength: { value: 8, message: t(messages.minPasswordLength, { n: '8' }) },
|
||||
}}
|
||||
>
|
||||
<PasswordField autoComplete="new-password" data-test="input-password" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={formatMessage(labels.role)}
|
||||
name="role"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField label={t(labels.role)} name="role" rules={{ required: t(labels.required) }}>
|
||||
<Select>
|
||||
<ListItem id={ROLES.viewOnly} data-test="dropdown-item-viewOnly">
|
||||
{formatMessage(labels.viewOnly)}
|
||||
{t(labels.viewOnly)}
|
||||
</ListItem>
|
||||
<ListItem id={ROLES.user} data-test="dropdown-item-user">
|
||||
{formatMessage(labels.user)}
|
||||
{t(labels.user)}
|
||||
</ListItem>
|
||||
<ListItem id={ROLES.admin} data-test="dropdown-item-admin">
|
||||
{formatMessage(labels.admin)}
|
||||
{t(labels.admin)}
|
||||
</ListItem>
|
||||
</Select>
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton variant="primary" data-test="button-submit" isDisabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function UserDeleteButton({
|
|||
username: string;
|
||||
onDelete?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { user } = useLoginQuery();
|
||||
|
||||
return (
|
||||
|
|
@ -21,10 +21,10 @@ export function UserDeleteButton({
|
|||
<Icon size="sm">
|
||||
<Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
<Text>{t(labels.delete)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.deleteUser)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.deleteUser)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<UserDeleteForm userId={userId} username={username} onSave={onDelete} onClose={close} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function UserDeleteForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { messages, labels, formatMessage } = useMessages();
|
||||
const { messages, labels, t } = useMessages();
|
||||
const { mutateAsync } = useDeleteQuery(`/users/${userId}`);
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -29,13 +29,13 @@ export function UserDeleteForm({
|
|||
|
||||
return (
|
||||
<AlertDialog
|
||||
title={formatMessage(labels.delete)}
|
||||
title={t(labels.delete)}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={onClose}
|
||||
confirmLabel={formatMessage(labels.delete)}
|
||||
confirmLabel={t(labels.delete)}
|
||||
isDanger
|
||||
>
|
||||
<Row gap="1">{formatMessage(messages.confirmDelete, { target: username })}</Row>
|
||||
<Row gap="1">{t(messages.confirmDelete, { target: username })}</Row>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import { UserAddButton } from './UserAddButton';
|
|||
import { UsersDataTable } from './UsersDataTable';
|
||||
|
||||
export function UsersPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {};
|
||||
|
||||
return (
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.users)}>
|
||||
<PageHeader title={t(labels.users)}>
|
||||
<UserAddButton onSave={handleSave} />
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -15,26 +15,24 @@ export function UsersTable({
|
|||
data: any[];
|
||||
showActions?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const [deleteUser, setDeleteUser] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="username" label={formatMessage(labels.username)} width="2fr">
|
||||
<DataColumn id="username" label={t(labels.username)} width="2fr">
|
||||
{(row: any) => <Link href={`/admin/users/${row.id}`}>{row.username}</Link>}
|
||||
</DataColumn>
|
||||
<DataColumn id="role" label={formatMessage(labels.role)}>
|
||||
<DataColumn id="role" label={t(labels.role)}>
|
||||
{(row: any) =>
|
||||
formatMessage(
|
||||
labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown,
|
||||
)
|
||||
t(labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown)
|
||||
}
|
||||
</DataColumn>
|
||||
<DataColumn id="websites" label={formatMessage(labels.websites)}>
|
||||
<DataColumn id="websites" label={t(labels.websites)}>
|
||||
{(row: any) => row._count.websites}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)}>
|
||||
<DataColumn id="created" label={t(labels.created)}>
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
{showActions && (
|
||||
|
|
@ -49,7 +47,7 @@ export function UsersTable({
|
|||
<Icon>
|
||||
<Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
<Text>{t(labels.edit)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
|
|
@ -61,7 +59,7 @@ export function UsersTable({
|
|||
<Icon>
|
||||
<Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
<Text>{t(labels.delete)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
</MenuButton>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { useLoginQuery, useMessages, useUpdateQuery, useUser } from '@/component
|
|||
import { ROLES } from '@/lib/constants';
|
||||
|
||||
export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () => void }) {
|
||||
const { formatMessage, labels, messages, getMessage } = useMessages();
|
||||
const { t, labels, messages, getMessage } = useMessages();
|
||||
const user = useUser();
|
||||
const { user: login } = useLoginQuery();
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () =
|
|||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('users');
|
||||
touch(`user:${user.id}`);
|
||||
onSave?.();
|
||||
|
|
@ -31,41 +31,37 @@ export function UserEditForm({ userId, onSave }: { userId: string; onSave?: () =
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getMessage(error?.code)} values={user}>
|
||||
<FormField name="username" label={formatMessage(labels.username)}>
|
||||
<FormField name="username" label={t(labels.username)}>
|
||||
<TextField data-test="input-username" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="password"
|
||||
label={formatMessage(labels.password)}
|
||||
label={t(labels.password)}
|
||||
rules={{
|
||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) },
|
||||
minLength: { value: 8, message: t(messages.minPasswordLength, { n: '8' }) },
|
||||
}}
|
||||
>
|
||||
<PasswordField autoComplete="new-password" data-test="input-password" />
|
||||
</FormField>
|
||||
|
||||
{user.id !== login.id && (
|
||||
<FormField
|
||||
name="role"
|
||||
label={formatMessage(labels.role)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="role" label={t(labels.role)} rules={{ required: t(labels.required) }}>
|
||||
<Select defaultValue={user.role}>
|
||||
<ListItem id={ROLES.viewOnly} data-test="dropdown-item-viewOnly">
|
||||
{formatMessage(labels.viewOnly)}
|
||||
{t(labels.viewOnly)}
|
||||
</ListItem>
|
||||
<ListItem id={ROLES.user} data-test="dropdown-item-user">
|
||||
{formatMessage(labels.user)}
|
||||
{t(labels.user)}
|
||||
</ListItem>
|
||||
<ListItem id={ROLES.admin} data-test="dropdown-item-admin">
|
||||
{formatMessage(labels.admin)}
|
||||
{t(labels.admin)}
|
||||
</ListItem>
|
||||
</Select>
|
||||
</FormField>
|
||||
)}
|
||||
<FormButtons>
|
||||
<FormSubmitButton data-test="button-submit" variant="primary">
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import { UserEditForm } from './UserEditForm';
|
|||
import { UserWebsites } from './UserWebsites';
|
||||
|
||||
export function UserSettings({ userId }: { userId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Column gap="6">
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
<Tab id="websites">{formatMessage(labels.websites)}</Tab>
|
||||
<Tab id="details">{t(labels.details)}</Tab>
|
||||
<Tab id="websites">{t(labels.websites)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="details" style={{ width: 500 }}>
|
||||
<UserEditForm userId={userId} />
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { useMessages } from '@/components/hooks';
|
|||
import { AdminWebsitesDataTable } from './AdminWebsitesDataTable';
|
||||
|
||||
export function AdminWebsitesPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.websites)} />
|
||||
<PageHeader title={t(labels.websites)} />
|
||||
<Panel>
|
||||
<AdminWebsitesDataTable />
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -8,23 +8,23 @@ import { Edit, Trash, Users } from '@/components/icons';
|
|||
import { MenuButton } from '@/components/input/MenuButton';
|
||||
|
||||
export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const [deleteWebsite, setDeleteWebsite] = useState(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{(row: any) => (
|
||||
<Text truncate>
|
||||
<Link href={`/admin/websites/${row.id}`}>{row.name}</Link>
|
||||
</Text>
|
||||
)}
|
||||
</DataColumn>
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)}>
|
||||
<DataColumn id="domain" label={t(labels.domain)}>
|
||||
{(row: any) => <Text truncate>{row.domain}</Text>}
|
||||
</DataColumn>
|
||||
<DataColumn id="owner" label={formatMessage(labels.owner)}>
|
||||
<DataColumn id="owner" label={t(labels.owner)}>
|
||||
{(row: any) => {
|
||||
if (row?.team) {
|
||||
return (
|
||||
|
|
@ -45,7 +45,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
|||
);
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)} width="180px">
|
||||
<DataColumn id="created" label={t(labels.created)} width="180px">
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
<DataColumn id="action" align="end" width="50px">
|
||||
|
|
@ -59,7 +59,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
|||
<Icon>
|
||||
<Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
<Text>{t(labels.edit)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
|
|
@ -71,7 +71,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
|||
<Icon>
|
||||
<Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
<Text>{t(labels.delete)}</Text>
|
||||
</Row>
|
||||
</MenuItem>
|
||||
</MenuButton>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { Plus } from '@/components/icons';
|
|||
import { BoardAddForm } from './BoardAddForm';
|
||||
|
||||
export function BoardAddButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
const { teamId } = useNavigation();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('boards');
|
||||
};
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ export function BoardAddButton() {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addBoard)}</Text>
|
||||
<Text>{t(labels.addBoard)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addBoard)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.addBoard)} style={{ width: 400 }}>
|
||||
{({ close }) => <BoardAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function BoardAddForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery('/boards', { teamId });
|
||||
const [websiteId, setWebsiteId] = useState<string>();
|
||||
|
||||
|
|
@ -30,34 +30,30 @@ export function BoardAddForm({
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error?.message}>
|
||||
<FormField
|
||||
label={formatMessage(labels.name)}
|
||||
name="name"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField label={t(labels.name)} name="name" rules={{ required: t(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={formatMessage(labels.description)}
|
||||
label={t(labels.description)}
|
||||
name="description"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
required: t(labels.required),
|
||||
}}
|
||||
>
|
||||
<TextField asTextArea autoComplete="off" />
|
||||
</FormField>
|
||||
<Row alignItems="center" gap="3" paddingTop="3">
|
||||
<Text>{formatMessage(labels.website)}</Text>
|
||||
<Text>{t(labels.website)}</Text>
|
||||
<WebsiteSelect websiteId={websiteId} teamId={teamId} onChange={setWebsiteId} />
|
||||
</Row>
|
||||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton data-test="button-submit" isDisabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</Row>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function BoardProvider({
|
|||
const { post, useMutation } = useApi();
|
||||
const { touch } = useModified();
|
||||
const { toast } = useToast();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { router, renderUrl } = useNavigation();
|
||||
|
||||
const [board, setBoard] = useState<Partial<Board>>(data ?? createDefaultBoard());
|
||||
|
|
@ -70,7 +70,7 @@ export function BoardProvider({
|
|||
}, []);
|
||||
|
||||
const saveBoard = useCallback(async () => {
|
||||
const defaultName = formatMessage(labels.untitled);
|
||||
const defaultName = t(labels.untitled);
|
||||
|
||||
// Get current layout sizes from BoardBody if registered
|
||||
const layoutData = layoutGetterRef.current?.();
|
||||
|
|
@ -82,7 +82,7 @@ export function BoardProvider({
|
|||
parameters,
|
||||
});
|
||||
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('boards');
|
||||
|
||||
if (board.id) {
|
||||
|
|
@ -92,17 +92,7 @@ export function BoardProvider({
|
|||
}
|
||||
|
||||
return result;
|
||||
}, [
|
||||
board,
|
||||
mutateAsync,
|
||||
toast,
|
||||
formatMessage,
|
||||
labels.untitled,
|
||||
messages.saved,
|
||||
touch,
|
||||
router,
|
||||
renderUrl,
|
||||
]);
|
||||
}, [board, mutateAsync, toast, t, labels.untitled, messages.saved, touch, router, renderUrl]);
|
||||
|
||||
if (boardId && isFetching && isLoading) {
|
||||
return <Loading placement="absolute" />;
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import { Plus } from '@/components/icons';
|
|||
import { BoardsDataTable } from './BoardsDataTable';
|
||||
|
||||
export function BoardsPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<Column margin="2">
|
||||
<PageHeader title={formatMessage(labels.boards)}>
|
||||
<PageHeader title={t(labels.boards)}>
|
||||
<LinkButton href="/boards/create" variant="primary">
|
||||
<IconLabel icon={<Plus />} label={formatMessage(labels.addBoard)} />
|
||||
<IconLabel icon={<Plus />} label={t(labels.addBoard)} />
|
||||
</LinkButton>
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ import { DateDistance } from '@/components/common/DateDistance';
|
|||
import { useMessages, useNavigation, useSlug } from '@/components/hooks';
|
||||
|
||||
export function BoardsTable(props: DataTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { websiteId, renderUrl } = useNavigation();
|
||||
const { getSlugUrl } = useSlug('link');
|
||||
|
||||
return (
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{({ id, name }: any) => {
|
||||
return <Board href={renderUrl(`/boards/${id}`)}>{name}</Board>;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="description" label={formatMessage(labels.description)} />
|
||||
<DataColumn id="created" label={formatMessage(labels.created)} width="200px">
|
||||
<DataColumn id="description" label={t(labels.description)} />
|
||||
<DataColumn id="created" label={t(labels.created)} width="200px">
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
<DataColumn id="action" align="end" width="100px">
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
|||
|
||||
export function BoardEditHeader() {
|
||||
const { board, updateBoard, saveBoard, isPending } = useBoard();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { teamId, router, renderUrl } = useNavigation();
|
||||
const defaultName = formatMessage(labels.untitled);
|
||||
const { t, labels } = useMessages();
|
||||
const { router, renderUrl } = useNavigation();
|
||||
const defaultName = t(labels.untitled);
|
||||
|
||||
const handleNameChange = (value: string) => {
|
||||
updateBoard({ name: value });
|
||||
|
|
@ -71,7 +71,7 @@ export function BoardEditHeader() {
|
|||
variant="quiet"
|
||||
name="description"
|
||||
value={board?.description ?? ''}
|
||||
placeholder={`+ ${formatMessage(labels.addDescription)}`}
|
||||
placeholder={`+ ${t(labels.addDescription)}`}
|
||||
autoComplete="off"
|
||||
onChange={handleDescriptionChange}
|
||||
style={{ width: '100%' }}
|
||||
|
|
@ -80,21 +80,17 @@ export function BoardEditHeader() {
|
|||
</TextField>
|
||||
</Row>
|
||||
<Row alignItems="center" gap="3">
|
||||
<Text>{formatMessage(labels.website)}</Text>
|
||||
<WebsiteSelect
|
||||
websiteId={board?.parameters?.websiteId}
|
||||
teamId={teamId}
|
||||
onChange={handleWebsiteChange}
|
||||
/>
|
||||
<Text>{t(labels.website)}</Text>
|
||||
<WebsiteSelect websiteId={board?.parameters?.websiteId} onChange={handleWebsiteChange} />
|
||||
</Row>
|
||||
</Column>
|
||||
<Column justifyContent="center" alignItems="flex-end">
|
||||
<Row gap="3">
|
||||
<Button variant="quiet" onPress={handleCancel}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<LoadingButton variant="primary" onPress={handleSave} isLoading={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</LoadingButton>
|
||||
</Row>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ import { Edit } from '@/components/icons';
|
|||
export function BoardViewHeader() {
|
||||
const { board } = useBoard();
|
||||
const { renderUrl } = useNavigation();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data: website } = useWebsiteQuery(board?.parameters?.websiteId);
|
||||
|
||||
return (
|
||||
<PageHeader title={board?.name} description={board?.description}>
|
||||
{website?.name && <Text>{website.name}</Text>}
|
||||
<LinkButton href={renderUrl(`/boards/${board?.id}/edit`, false)}>
|
||||
<IconLabel icon={<Edit />}>{formatMessage(labels.edit)}</IconLabel>
|
||||
<IconLabel icon={<Edit />}>{t(labels.edit)}</IconLabel>
|
||||
</LinkButton>
|
||||
</PageHeader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { PageHeader } from '@/components/common/PageHeader';
|
|||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function DashboardPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<Column margin="2">
|
||||
<PageHeader title={formatMessage(labels.dashboard)}></PageHeader>
|
||||
<PageHeader title={t(labels.dashboard)}></PageHeader>
|
||||
</Column>
|
||||
</PageBody>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,15 +4,10 @@ import { DialogButton } from '@/components/input/DialogButton';
|
|||
import { LinkEditForm } from './LinkEditForm';
|
||||
|
||||
export function LinkAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addLink)}
|
||||
variant="primary"
|
||||
width="600px"
|
||||
>
|
||||
<DialogButton icon={<Plus />} label={t(labels.addLink)} variant="primary" width="600px">
|
||||
{({ close }) => <LinkEditForm teamId={teamId} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
|||
import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import { messages } from '@/components/messages';
|
||||
|
||||
export function LinkDeleteButton({
|
||||
linkId,
|
||||
|
|
@ -14,7 +13,7 @@ export function LinkDeleteButton({
|
|||
name: string;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, isPending, error } = useDeleteQuery(`/links/${linkId}`);
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -29,27 +28,18 @@ export function LinkDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
title={formatMessage(labels.confirm)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
<DialogButton icon={<Trash />} title={t(labels.confirm)} variant="quiet" width="400px">
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
message={t.rich(messages.confirmRemove, {
|
||||
target: name,
|
||||
b: chunks => <b>{chunks}</b>,
|
||||
})}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonLabel={t(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { DialogButton } from '@/components/input/DialogButton';
|
|||
import { LinkEditForm } from './LinkEditForm';
|
||||
|
||||
export function LinkEditButton({ linkId }: { linkId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton icon={<Edit />} title={formatMessage(labels.link)} variant="quiet" width="800px">
|
||||
<DialogButton icon={<Edit />} title={t(labels.link)} variant="quiet" width="800px">
|
||||
{({ close }) => {
|
||||
return <LinkEditForm linkId={linkId} onClose={close} />;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function LinkEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages, getErrorMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
|
||||
linkId ? `/links/${linkId}` : '/links',
|
||||
{
|
||||
|
|
@ -48,7 +48,7 @@ export function LinkEditForm({
|
|||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('links');
|
||||
touch(`link:${linkId}`);
|
||||
onSave?.();
|
||||
|
|
@ -59,7 +59,7 @@ export function LinkEditForm({
|
|||
|
||||
const checkUrl = (url: string) => {
|
||||
if (!isValidUrl(url)) {
|
||||
return formatMessage(labels.invalidUrl);
|
||||
return t(labels.invalidUrl);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
@ -79,18 +79,14 @@ export function LinkEditForm({
|
|||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
label={formatMessage(labels.name)}
|
||||
name="name"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField label={t(labels.name)} name="name" rules={{ required: t(labels.required) }}>
|
||||
<TextField autoComplete="off" autoFocus />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={formatMessage(labels.destinationUrl)}
|
||||
label={t(labels.destinationUrl)}
|
||||
name="url"
|
||||
rules={{ required: formatMessage(labels.required), validate: checkUrl }}
|
||||
rules={{ required: t(labels.required), validate: checkUrl }}
|
||||
>
|
||||
<TextField placeholder="https://example.com" autoComplete="off" />
|
||||
</FormField>
|
||||
|
|
@ -98,9 +94,9 @@ export function LinkEditForm({
|
|||
<Grid columns="1fr auto" alignItems="end" gap>
|
||||
<FormField
|
||||
name="slug"
|
||||
label={formatMessage({ id: 'label.slug', defaultMessage: 'Slug' })}
|
||||
label={t({ id: 'label.slug', defaultMessage: 'Slug' })}
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
required: t(labels.required),
|
||||
}}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
|
|
@ -116,7 +112,7 @@ export function LinkEditForm({
|
|||
</Grid>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.link)}</Label>
|
||||
<Label>{t(labels.link)}</Label>
|
||||
<Row alignItems="center" gap>
|
||||
<TextField
|
||||
value={`${hostUrl}/${slug}`}
|
||||
|
|
@ -131,10 +127,10 @@ export function LinkEditForm({
|
|||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton>{t(labels.save)}</FormSubmitButton>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { LinkAddButton } from './LinkAddButton';
|
|||
|
||||
export function LinksPage() {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { teamId } = useNavigation();
|
||||
const { data } = useTeamMembersQuery(teamId);
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export function LinksPage() {
|
|||
return (
|
||||
<PageBody>
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.links)}>
|
||||
<PageHeader title={t(labels.links)}>
|
||||
{showActions && <LinkAddButton teamId={teamId} />}
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -11,18 +11,18 @@ export interface LinksTableProps extends DataTableProps {
|
|||
}
|
||||
|
||||
export function LinksTable({ showActions, ...props }: LinksTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { websiteId, renderUrl } = useNavigation();
|
||||
const { getSlugUrl } = useSlug('link');
|
||||
|
||||
return (
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{({ id, name }: any) => {
|
||||
return <Link href={renderUrl(`/links/${id}`)}>{name}</Link>;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="slug" label={formatMessage(labels.link)}>
|
||||
<DataColumn id="slug" label={t(labels.link)}>
|
||||
{({ slug }: any) => {
|
||||
const url = getSlugUrl(slug);
|
||||
return (
|
||||
|
|
@ -32,12 +32,12 @@ export function LinksTable({ showActions, ...props }: LinksTableProps) {
|
|||
);
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="url" label={formatMessage(labels.destinationUrl)}>
|
||||
<DataColumn id="url" label={t(labels.destinationUrl)}>
|
||||
{({ url }: any) => {
|
||||
return <ExternalLink href={url}>{url}</ExternalLink>;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)} width="200px">
|
||||
<DataColumn id="created" label={t(labels.created)} width="200px">
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
{showActions && (
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { useLink, useMessages, useSlug } from '@/components/hooks';
|
|||
import { ExternalLink, Link } from '@/components/icons';
|
||||
|
||||
export function LinkHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { getSlugUrl } = useSlug('link');
|
||||
const link = useLink();
|
||||
|
||||
return (
|
||||
<PageHeader title={link.name} description={link.url} icon={<Link />}>
|
||||
<LinkButton href={getSlugUrl(link.slug)} target="_blank" prefetch={false} asAnchor>
|
||||
<IconLabel icon={<ExternalLink />} label={formatMessage(labels.view)} />
|
||||
<IconLabel icon={<ExternalLink />} label={t(labels.view)} />
|
||||
</LinkButton>
|
||||
</PageHeader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function LinkMetricsBar({
|
|||
compareMode?: boolean;
|
||||
}) {
|
||||
const { isAllTime } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(linkId);
|
||||
|
||||
const { pageviews, visitors, visits, comparison } = data || {};
|
||||
|
|
@ -22,19 +22,19 @@ export function LinkMetricsBar({
|
|||
? [
|
||||
{
|
||||
value: visitors,
|
||||
label: formatMessage(labels.visitors),
|
||||
label: t(labels.visitors),
|
||||
change: visitors - comparison.visitors,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: visits,
|
||||
label: formatMessage(labels.visits),
|
||||
label: t(labels.visits),
|
||||
change: visits - comparison.visits,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: pageviews,
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
change: pageviews - comparison.pageviews,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { MetricsTable } from '@/components/metrics/MetricsTable';
|
|||
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||
|
||||
export function LinkPanels({ linkId }: { linkId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const tableProps = {
|
||||
websiteId: linkId,
|
||||
limit: 10,
|
||||
allowDownload: false,
|
||||
showMore: true,
|
||||
metric: formatMessage(labels.visitors),
|
||||
metric: t(labels.visitors),
|
||||
};
|
||||
const rowProps = { minHeight: 570 };
|
||||
|
||||
|
|
@ -20,36 +20,36 @@ export function LinkPanels({ linkId }: { linkId: string }) {
|
|||
<Grid gap="3">
|
||||
<GridRow layout="two" {...rowProps}>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.sources)}</Heading>
|
||||
<Heading size="2xl">{t(labels.sources)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="referrer">{formatMessage(labels.referrers)}</Tab>
|
||||
<Tab id="channel">{formatMessage(labels.channels)}</Tab>
|
||||
<Tab id="referrer">{t(labels.referrers)}</Tab>
|
||||
<Tab id="channel">{t(labels.channels)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="referrer">
|
||||
<MetricsTable type="referrer" title={formatMessage(labels.domain)} {...tableProps} />
|
||||
<MetricsTable type="referrer" title={t(labels.domain)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="channel">
|
||||
<MetricsTable type="channel" title={formatMessage(labels.type)} {...tableProps} />
|
||||
<MetricsTable type="channel" title={t(labels.type)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.environment)}</Heading>
|
||||
<Heading size="2xl">{t(labels.environment)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="browser">{formatMessage(labels.browsers)}</Tab>
|
||||
<Tab id="os">{formatMessage(labels.os)}</Tab>
|
||||
<Tab id="device">{formatMessage(labels.devices)}</Tab>
|
||||
<Tab id="browser">{t(labels.browsers)}</Tab>
|
||||
<Tab id="os">{t(labels.os)}</Tab>
|
||||
<Tab id="device">{t(labels.devices)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="browser">
|
||||
<MetricsTable type="browser" title={formatMessage(labels.browser)} {...tableProps} />
|
||||
<MetricsTable type="browser" title={t(labels.browser)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="os">
|
||||
<MetricsTable type="os" title={formatMessage(labels.os)} {...tableProps} />
|
||||
<MetricsTable type="os" title={t(labels.os)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="device">
|
||||
<MetricsTable type="device" title={formatMessage(labels.device)} {...tableProps} />
|
||||
<MetricsTable type="device" title={t(labels.device)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
|
|
@ -59,21 +59,21 @@ export function LinkPanels({ linkId }: { linkId: string }) {
|
|||
<WorldMap websiteId={linkId} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.location)}</Heading>
|
||||
<Heading size="2xl">{t(labels.location)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="country">{formatMessage(labels.countries)}</Tab>
|
||||
<Tab id="region">{formatMessage(labels.regions)}</Tab>
|
||||
<Tab id="city">{formatMessage(labels.cities)}</Tab>
|
||||
<Tab id="country">{t(labels.countries)}</Tab>
|
||||
<Tab id="region">{t(labels.regions)}</Tab>
|
||||
<Tab id="city">{t(labels.cities)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="country">
|
||||
<MetricsTable type="country" title={formatMessage(labels.country)} {...tableProps} />
|
||||
<MetricsTable type="country" title={t(labels.country)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="region">
|
||||
<MetricsTable type="region" title={formatMessage(labels.region)} {...tableProps} />
|
||||
<MetricsTable type="region" title={t(labels.region)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="city">
|
||||
<MetricsTable type="city" title={formatMessage(labels.city)} {...tableProps} />
|
||||
<MetricsTable type="city" title={t(labels.city)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -4,15 +4,10 @@ import { DialogButton } from '@/components/input/DialogButton';
|
|||
import { PixelEditForm } from './PixelEditForm';
|
||||
|
||||
export function PixelAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addPixel)}
|
||||
variant="primary"
|
||||
width="600px"
|
||||
>
|
||||
<DialogButton icon={<Plus />} label={t(labels.addPixel)} variant="primary" width="600px">
|
||||
{({ close }) => <PixelEditForm teamId={teamId} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
|||
import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import { messages } from '@/components/messages';
|
||||
|
||||
export function PixelDeleteButton({
|
||||
pixelId,
|
||||
|
|
@ -13,7 +12,7 @@ export function PixelDeleteButton({
|
|||
name: string;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`);
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -28,27 +27,18 @@ export function PixelDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
variant="quiet"
|
||||
title={formatMessage(labels.confirm)}
|
||||
width="400px"
|
||||
>
|
||||
<DialogButton icon={<Trash />} variant="quiet" title={t(labels.confirm)} width="400px">
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
message={t.rich(messages.confirmRemove, {
|
||||
target: name,
|
||||
b: chunks => <b>{chunks}</b>,
|
||||
})}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonLabel={t(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,10 @@ import { DialogButton } from '@/components/input/DialogButton';
|
|||
import { PixelEditForm } from './PixelEditForm';
|
||||
|
||||
export function PixelEditButton({ pixelId }: { pixelId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
title={formatMessage(labels.addPixel)}
|
||||
variant="quiet"
|
||||
width="600px"
|
||||
>
|
||||
<DialogButton icon={<Edit />} title={t(labels.addPixel)} variant="quiet" width="600px">
|
||||
{({ close }) => {
|
||||
return <PixelEditForm pixelId={pixelId} onClose={close} />;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function PixelEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages, getErrorMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
|
||||
pixelId ? `/pixels/${pixelId}` : '/pixels',
|
||||
{
|
||||
|
|
@ -46,7 +46,7 @@ export function PixelEditForm({
|
|||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('pixels');
|
||||
touch(`pixel:${pixelId}`);
|
||||
onSave?.();
|
||||
|
|
@ -78,18 +78,14 @@ export function PixelEditForm({
|
|||
{({ setValue }) => {
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
label={formatMessage(labels.name)}
|
||||
name="name"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField label={t(labels.name)} name="name" rules={{ required: t(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="slug"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
required: t(labels.required),
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
|
|
@ -97,7 +93,7 @@ export function PixelEditForm({
|
|||
</FormField>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.link)}</Label>
|
||||
<Label>{t(labels.link)}</Label>
|
||||
<Row alignItems="center" gap>
|
||||
<TextField
|
||||
value={`${hostUrl}/${slug}`}
|
||||
|
|
@ -117,10 +113,10 @@ export function PixelEditForm({
|
|||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton isDisabled={false}>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton isDisabled={false}>{t(labels.save)}</FormSubmitButton>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { PixelsDataTable } from './PixelsDataTable';
|
|||
|
||||
export function PixelsPage() {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { teamId } = useNavigation();
|
||||
const { data } = useTeamMembersQuery(teamId);
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ export function PixelsPage() {
|
|||
return (
|
||||
<PageBody>
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.pixels)}>
|
||||
<PageHeader title={t(labels.pixels)}>
|
||||
{showActions && <PixelAddButton teamId={teamId} />}
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ export interface PixelsTableProps extends DataTableProps {
|
|||
}
|
||||
|
||||
export function PixelsTable({ showActions, ...props }: PixelsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { renderUrl } = useNavigation();
|
||||
const { getSlugUrl } = useSlug('pixel');
|
||||
|
||||
return (
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{({ id, name }: any) => {
|
||||
return <Link href={renderUrl(`/pixels/${id}`)}>{name}</Link>;
|
||||
}}
|
||||
|
|
@ -32,7 +32,7 @@ export function PixelsTable({ showActions, ...props }: PixelsTableProps) {
|
|||
);
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)}>
|
||||
<DataColumn id="created" label={t(labels.created)}>
|
||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||
</DataColumn>
|
||||
{showActions && (
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { useMessages, usePixel, useSlug } from '@/components/hooks';
|
|||
import { ExternalLink, Grid2x2 } from '@/components/icons';
|
||||
|
||||
export function PixelHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { getSlugUrl } = useSlug('pixel');
|
||||
const pixel = usePixel();
|
||||
|
||||
return (
|
||||
<PageHeader title={pixel.name} icon={<Grid2x2 />}>
|
||||
<LinkButton href={getSlugUrl(pixel.slug)} target="_blank" prefetch={false} asAnchor>
|
||||
<IconLabel icon={<ExternalLink />} label={formatMessage(labels.view)} />
|
||||
<IconLabel icon={<ExternalLink />} label={t(labels.view)} />
|
||||
</LinkButton>
|
||||
</PageHeader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function PixelMetricsBar({
|
|||
compareMode?: boolean;
|
||||
}) {
|
||||
const { isAllTime } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(pixelId);
|
||||
|
||||
const { pageviews, visitors, visits, comparison } = data || {};
|
||||
|
|
@ -22,19 +22,19 @@ export function PixelMetricsBar({
|
|||
? [
|
||||
{
|
||||
value: visitors,
|
||||
label: formatMessage(labels.visitors),
|
||||
label: t(labels.visitors),
|
||||
change: visitors - comparison.visitors,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: visits,
|
||||
label: formatMessage(labels.visits),
|
||||
label: t(labels.visits),
|
||||
change: visits - comparison.visits,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: pageviews,
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
change: pageviews - comparison.pageviews,
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { MetricsTable } from '@/components/metrics/MetricsTable';
|
|||
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||
|
||||
export function PixelPanels({ pixelId }: { pixelId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const tableProps = {
|
||||
websiteId: pixelId,
|
||||
limit: 10,
|
||||
allowDownload: false,
|
||||
showMore: true,
|
||||
metric: formatMessage(labels.visitors),
|
||||
metric: t(labels.visitors),
|
||||
};
|
||||
const rowProps = { minHeight: 570 };
|
||||
|
||||
|
|
@ -20,36 +20,36 @@ export function PixelPanels({ pixelId }: { pixelId: string }) {
|
|||
<Grid gap="3">
|
||||
<GridRow layout="two" {...rowProps}>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.sources)}</Heading>
|
||||
<Heading size="2xl">{t(labels.sources)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="referrer">{formatMessage(labels.referrers)}</Tab>
|
||||
<Tab id="channel">{formatMessage(labels.channels)}</Tab>
|
||||
<Tab id="referrer">{t(labels.referrers)}</Tab>
|
||||
<Tab id="channel">{t(labels.channels)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="referrer">
|
||||
<MetricsTable type="referrer" title={formatMessage(labels.domain)} {...tableProps} />
|
||||
<MetricsTable type="referrer" title={t(labels.domain)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="channel">
|
||||
<MetricsTable type="channel" title={formatMessage(labels.type)} {...tableProps} />
|
||||
<MetricsTable type="channel" title={t(labels.type)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.environment)}</Heading>
|
||||
<Heading size="2xl">{t(labels.environment)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="browser">{formatMessage(labels.browsers)}</Tab>
|
||||
<Tab id="os">{formatMessage(labels.os)}</Tab>
|
||||
<Tab id="device">{formatMessage(labels.devices)}</Tab>
|
||||
<Tab id="browser">{t(labels.browsers)}</Tab>
|
||||
<Tab id="os">{t(labels.os)}</Tab>
|
||||
<Tab id="device">{t(labels.devices)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="browser">
|
||||
<MetricsTable type="browser" title={formatMessage(labels.browser)} {...tableProps} />
|
||||
<MetricsTable type="browser" title={t(labels.browser)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="os">
|
||||
<MetricsTable type="os" title={formatMessage(labels.os)} {...tableProps} />
|
||||
<MetricsTable type="os" title={t(labels.os)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="device">
|
||||
<MetricsTable type="device" title={formatMessage(labels.device)} {...tableProps} />
|
||||
<MetricsTable type="device" title={t(labels.device)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
|
|
@ -59,21 +59,21 @@ export function PixelPanels({ pixelId }: { pixelId: string }) {
|
|||
<WorldMap websiteId={pixelId} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<Heading size="2xl">{formatMessage(labels.location)}</Heading>
|
||||
<Heading size="2xl">{t(labels.location)}</Heading>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="country">{formatMessage(labels.countries)}</Tab>
|
||||
<Tab id="region">{formatMessage(labels.regions)}</Tab>
|
||||
<Tab id="city">{formatMessage(labels.cities)}</Tab>
|
||||
<Tab id="country">{t(labels.countries)}</Tab>
|
||||
<Tab id="region">{t(labels.regions)}</Tab>
|
||||
<Tab id="city">{t(labels.cities)}</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="country">
|
||||
<MetricsTable type="country" title={formatMessage(labels.country)} {...tableProps} />
|
||||
<MetricsTable type="country" title={t(labels.country)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="region">
|
||||
<MetricsTable type="region" title={formatMessage(labels.region)} {...tableProps} />
|
||||
<MetricsTable type="region" title={t(labels.region)} {...tableProps} />
|
||||
</TabPanel>
|
||||
<TabPanel id="city">
|
||||
<MetricsTable type="city" title={formatMessage(labels.city)} {...tableProps} />
|
||||
<MetricsTable type="city" title={t(labels.city)} {...tableProps} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -3,33 +3,33 @@ import { useMessages, useNavigation } from '@/components/hooks';
|
|||
import { Settings2, UserCircle, Users } from '@/components/icons';
|
||||
|
||||
export function SettingsNav({ onItemClick }: { onItemClick?: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { renderUrl, pathname } = useNavigation();
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: formatMessage(labels.application),
|
||||
label: t(labels.application),
|
||||
items: [
|
||||
{
|
||||
id: 'preferences',
|
||||
label: formatMessage(labels.preferences),
|
||||
label: t(labels.preferences),
|
||||
path: renderUrl('/settings/preferences'),
|
||||
icon: <Settings2 />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.account),
|
||||
label: t(labels.account),
|
||||
items: [
|
||||
{
|
||||
id: 'profile',
|
||||
label: formatMessage(labels.profile),
|
||||
label: t(labels.profile),
|
||||
path: renderUrl('/settings/profile'),
|
||||
icon: <UserCircle />,
|
||||
},
|
||||
{
|
||||
id: 'teams',
|
||||
label: formatMessage(labels.teams),
|
||||
label: t(labels.teams),
|
||||
path: renderUrl('/settings/teams'),
|
||||
icon: <Users />,
|
||||
},
|
||||
|
|
@ -44,7 +44,7 @@ export function SettingsNav({ onItemClick }: { onItemClick?: () => void }) {
|
|||
return (
|
||||
<NavMenu
|
||||
items={items}
|
||||
title={formatMessage(labels.settings)}
|
||||
title={t(labels.settings)}
|
||||
selectedKey={selectedKey}
|
||||
allowMinimize={false}
|
||||
onItemClick={onItemClick}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
|||
import { getItem, setItem } from '@/lib/storage';
|
||||
|
||||
export function DateRangeSetting() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const [date, setDate] = useState(getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
|
|
@ -27,7 +27,7 @@ export function DateRangeSetting() {
|
|||
placement="bottom start"
|
||||
style={{ minWidth: '250px' }}
|
||||
/>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
<Button onPress={handleReset}>{t(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { languages } from '@/lib/lang';
|
|||
|
||||
export function LanguageSetting() {
|
||||
const [search, setSearch] = useState('');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const items = search
|
||||
? Object.keys(languages).filter(n => {
|
||||
|
|
@ -43,7 +43,7 @@ export function LanguageSetting() {
|
|||
))}
|
||||
{!items.length && <ListItem></ListItem>}
|
||||
</Select>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
<Button onPress={handleReset}>{t(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { VersionSetting } from './VersionSetting';
|
|||
|
||||
export function PreferenceSettings() {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
|
|
@ -17,23 +17,23 @@ export function PreferenceSettings() {
|
|||
return (
|
||||
<Column gap="6">
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||
<Label>{t(labels.defaultDateRange)}</Label>
|
||||
<DateRangeSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.timezone)}</Label>
|
||||
<Label>{t(labels.timezone)}</Label>
|
||||
<TimezoneSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.language)}</Label>
|
||||
<Label>{t(labels.language)}</Label>
|
||||
<LanguageSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.theme)}</Label>
|
||||
<Label>{t(labels.theme)}</Label>
|
||||
<ThemeSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.version)}</Label>
|
||||
<Label>{t(labels.version)}</Label>
|
||||
<VersionSetting />
|
||||
</Column>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { useMessages } from '@/components/hooks';
|
|||
import { PreferenceSettings } from './PreferenceSettings';
|
||||
|
||||
export function PreferencesPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.preferences)} />
|
||||
<PageHeader title={t(labels.preferences)} />
|
||||
<Panel>
|
||||
<PreferenceSettings />
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const timezones = Intl.supportedValuesOf('timeZone');
|
|||
|
||||
export function TimezoneSetting() {
|
||||
const [search, setSearch] = useState('');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { timezone, saveTimezone } = useTimezone();
|
||||
const items = search
|
||||
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
||||
|
|
@ -39,7 +39,7 @@ export function TimezoneSetting() {
|
|||
))}
|
||||
{!items.length && <ListItem></ListItem>}
|
||||
</Select>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
<Button onPress={handleReset}>{t(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { LockKeyhole } from '@/components/icons';
|
|||
import { PasswordEditForm } from './PasswordEditForm';
|
||||
|
||||
export function PasswordChangeButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -17,10 +17,10 @@ export function PasswordChangeButton() {
|
|||
<Icon>
|
||||
<LockKeyhole />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.changePassword)}</Text>
|
||||
<Text>{t(labels.changePassword)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.changePassword)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.changePassword)} style={{ width: 400 }}>
|
||||
{({ close }) => <PasswordEditForm onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
||||
|
||||
export function PasswordEditForm({ onSave, onClose }) {
|
||||
const { formatMessage, labels, messages, getErrorMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery('/me/password');
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -23,7 +23,7 @@ export function PasswordEditForm({ onSave, onClose }) {
|
|||
|
||||
const samePassword = (value: string, values: Record<string, any>) => {
|
||||
if (value !== values.newPassword) {
|
||||
return formatMessage(messages.noMatchPassword);
|
||||
return t(messages.noMatchPassword);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@ export function PasswordEditForm({ onSave, onClose }) {
|
|||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
|
||||
<FormField
|
||||
label={formatMessage(labels.currentPassword)}
|
||||
label={t(labels.currentPassword)}
|
||||
name="currentPassword"
|
||||
rules={{ required: 'Required' }}
|
||||
>
|
||||
|
|
@ -39,28 +39,28 @@ export function PasswordEditForm({ onSave, onClose }) {
|
|||
</FormField>
|
||||
<FormField
|
||||
name="newPassword"
|
||||
label={formatMessage(labels.newPassword)}
|
||||
label={t(labels.newPassword)}
|
||||
rules={{
|
||||
required: 'Required',
|
||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) },
|
||||
minLength: { value: 8, message: t(messages.minPasswordLength, { n: '8' }) },
|
||||
}}
|
||||
>
|
||||
<PasswordField autoComplete="new-password" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="confirmPassword"
|
||||
label={formatMessage(labels.confirmPassword)}
|
||||
label={t(labels.confirmPassword)}
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: '8' }) },
|
||||
required: t(labels.required),
|
||||
minLength: { value: 8, message: t(messages.minPasswordLength, { n: '8' }) },
|
||||
validate: samePassword,
|
||||
}}
|
||||
>
|
||||
<PasswordField autoComplete="confirm-password" />
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<FormSubmitButton isDisabled={isPending}>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<Button onPress={onClose}>{t(labels.cancel)}</Button>
|
||||
<FormSubmitButton isDisabled={isPending}>{t(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { SectionHeader } from '@/components/common/SectionHeader';
|
|||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ProfileHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return <SectionHeader title={formatMessage(labels.profile)}></SectionHeader>;
|
||||
return <SectionHeader title={t(labels.profile)}></SectionHeader>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { useMessages } from '@/components/hooks';
|
|||
import { ProfileSettings } from './ProfileSettings';
|
||||
|
||||
export function ProfilePage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.profile)} />
|
||||
<PageHeader title={t(labels.profile)} />
|
||||
<Panel>
|
||||
<ProfileSettings />
|
||||
</Panel>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { PasswordChangeButton } from './PasswordChangeButton';
|
|||
|
||||
export function ProfileSettings() {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { cloudMode } = useConfig();
|
||||
|
||||
if (!user) {
|
||||
|
|
@ -16,31 +16,31 @@ export function ProfileSettings() {
|
|||
|
||||
const renderRole = (value: string) => {
|
||||
if (value === ROLES.user) {
|
||||
return formatMessage(labels.user);
|
||||
return t(labels.user);
|
||||
}
|
||||
if (value === ROLES.admin) {
|
||||
return formatMessage(labels.admin);
|
||||
return t(labels.admin);
|
||||
}
|
||||
if (value === ROLES.viewOnly) {
|
||||
return formatMessage(labels.viewOnly);
|
||||
return t(labels.viewOnly);
|
||||
}
|
||||
|
||||
return formatMessage(labels.unknown);
|
||||
return t(labels.unknown);
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap="6">
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.username)}</Label>
|
||||
<Label>{t(labels.username)}</Label>
|
||||
{username}
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.role)}</Label>
|
||||
<Label>{t(labels.role)}</Label>
|
||||
{renderRole(role)}
|
||||
</Column>
|
||||
{!cloudMode && (
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.password)}</Label>
|
||||
<Label>{t(labels.password)}</Label>
|
||||
<Row>
|
||||
<PasswordChangeButton />
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { SectionHeader } from '@/components/common/SectionHeader';
|
|||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function WebsitesSettingsPage({ teamId }: { teamId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.websites)} />
|
||||
<SectionHeader title={t(labels.websites)} />
|
||||
<WebsitesDataTable teamId={teamId} />
|
||||
</Column>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function TeamAddForm({
|
|||
onClose: () => void;
|
||||
isAdmin: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { t, labels, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery('/teams');
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -32,20 +32,20 @@ export function TeamAddForm({
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
|
||||
<FormField name="name" label={formatMessage(labels.name)}>
|
||||
<FormField name="name" label={t(labels.name)}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
{isAdmin && (
|
||||
<FormField name="ownerId" label={formatMessage(labels.teamOwner)}>
|
||||
<FormField name="ownerId" label={t(labels.teamOwner)}>
|
||||
<UserSelect buttonProps={{ style: { outline: 'none' } }} />
|
||||
</FormField>
|
||||
)}
|
||||
<FormButtons>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton variant="primary" isDisabled={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
||||
|
||||
export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { t, labels, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, touch } = useUpdateQuery('/teams/join');
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -25,15 +25,15 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose:
|
|||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
|
||||
<FormField
|
||||
label={formatMessage(labels.accessCode)}
|
||||
label={t(labels.accessCode)}
|
||||
name="accessCode"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<FormSubmitButton variant="primary">{formatMessage(labels.join)}</FormSubmitButton>
|
||||
<Button onPress={onClose}>{t(labels.cancel)}</Button>
|
||||
<FormSubmitButton variant="primary">{t(labels.join)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { LogOut } from '@/components/icons';
|
|||
import { TeamLeaveForm } from './TeamLeaveForm';
|
||||
|
||||
export function TeamLeaveButton({ teamId, teamName }: { teamId: string; teamName: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const router = useRouter();
|
||||
const { user } = useLoginQuery();
|
||||
const { touch } = useModified();
|
||||
|
|
@ -21,10 +21,10 @@ export function TeamLeaveButton({ teamId, teamName }: { teamId: string; teamName
|
|||
<Icon>
|
||||
<LogOut />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.leave)}</Text>
|
||||
<Text>{t(labels.leave)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.leaveTeam)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.leaveTeam)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<TeamLeaveForm
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function TeamLeaveForm({
|
|||
onSave: () => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages, getErrorMessage, FormattedMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useDeleteQuery(`/teams/${teamId}/users/${userId}`);
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -30,15 +30,11 @@ export function TeamLeaveForm({
|
|||
|
||||
return (
|
||||
<ConfirmationForm
|
||||
buttonLabel={formatMessage(labels.leave)}
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmLeave}
|
||||
values={{
|
||||
target: <b>{teamName}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
buttonLabel={t(labels.leave)}
|
||||
message={t.rich(messages.confirmLeave, {
|
||||
target: teamName,
|
||||
b: chunks => <b>{chunks}</b>,
|
||||
})}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={onClose}
|
||||
isLoading={isPending}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export function TeamMemberAddForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { t, labels, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery(`/teams/${teamId}/users`);
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -37,24 +37,20 @@ export function TeamMemberAddForm({
|
|||
const renderRole = role => {
|
||||
switch (role) {
|
||||
case ROLES.teamManager:
|
||||
return formatMessage(labels.manager);
|
||||
return t(labels.manager);
|
||||
case ROLES.teamMember:
|
||||
return formatMessage(labels.member);
|
||||
return t(labels.member);
|
||||
case ROLES.teamViewOnly:
|
||||
return formatMessage(labels.viewOnly);
|
||||
return t(labels.viewOnly);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
|
||||
<FormField
|
||||
name="userId"
|
||||
label={formatMessage(labels.username)}
|
||||
rules={{ required: 'Required' }}
|
||||
>
|
||||
<FormField name="userId" label={t(labels.username)} rules={{ required: 'Required' }}>
|
||||
<UserSelect teamId={teamId} />
|
||||
</FormField>
|
||||
<FormField name="role" label={formatMessage(labels.role)} rules={{ required: 'Required' }}>
|
||||
<FormField name="role" label={t(labels.role)} rules={{ required: 'Required' }}>
|
||||
<Select renderValue={value => renderRole(value as any)}>
|
||||
{roles.map(value => (
|
||||
<ListItem key={value} id={value}>
|
||||
|
|
@ -65,10 +61,10 @@ export function TeamMemberAddForm({
|
|||
</FormField>
|
||||
<FormButtons>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton variant="primary" isDisabled={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Button, Dialog, DialogTrigger, Icon, Modal, Text, useToast } from '@umami/react-zen';
|
||||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { messages } from '@/components/messages';
|
||||
import { TeamAddForm } from './TeamAddForm';
|
||||
|
||||
export function TeamsAddButton({
|
||||
|
|
@ -11,12 +10,12 @@ export function TeamsAddButton({
|
|||
onSave?: () => void;
|
||||
isAdmin?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('teams');
|
||||
onSave?.();
|
||||
};
|
||||
|
|
@ -27,10 +26,10 @@ export function TeamsAddButton({
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.createTeam)}</Text>
|
||||
<Text>{t(labels.createTeam)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.createTeam)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.createTeam)} style={{ width: 400 }}>
|
||||
{({ close }) => <TeamAddForm onSave={handleSave} onClose={close} isAdmin={isAdmin} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ export function TeamsHeader({
|
|||
allowCreate?: boolean;
|
||||
allowJoin?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { user } = useLoginQuery();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<PageHeader title={t(labels.teams)}>
|
||||
<Row gap="3">
|
||||
{allowJoin && <TeamsJoinButton />}
|
||||
{allowCreate && user.role !== ROLES.viewOnly && <TeamsAddButton />}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { UserPlus } from '@/components/icons';
|
|||
import { TeamJoinForm } from './TeamJoinForm';
|
||||
|
||||
export function TeamsJoinButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleJoin = () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('teams');
|
||||
};
|
||||
|
||||
|
|
@ -19,10 +19,10 @@ export function TeamsJoinButton() {
|
|||
<Icon>
|
||||
<UserPlus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
<Text>{t(labels.joinTeam)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.joinTeam)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.joinTeam)} style={{ width: 400 }}>
|
||||
{({ close }) => <TeamJoinForm onSave={handleJoin} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Button, Dialog, DialogTrigger, Icon, Modal, Text, useToast } from '@umami/react-zen';
|
||||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { messages } from '@/components/messages';
|
||||
import { TeamMemberAddForm } from './TeamMemberAddForm';
|
||||
|
||||
export function TeamsMemberAddButton({
|
||||
|
|
@ -12,12 +11,12 @@ export function TeamsMemberAddButton({
|
|||
onSave?: () => void;
|
||||
isAdmin?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('teams:members');
|
||||
onSave?.();
|
||||
};
|
||||
|
|
@ -28,10 +27,10 @@ export function TeamsMemberAddButton({
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addMember)}</Text>
|
||||
<Text>{t(labels.addMember)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addMember)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.addMember)} style={{ width: 400 }}>
|
||||
{({ close }) => <TeamMemberAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ export interface TeamsTableProps extends DataTableProps {
|
|||
}
|
||||
|
||||
export function TeamsTable({ renderLink, ...props }: TeamsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{renderLink}
|
||||
</DataColumn>
|
||||
<DataColumn id="owner" label={formatMessage(labels.owner)}>
|
||||
<DataColumn id="owner" label={t(labels.owner)}>
|
||||
{(row: any) => row?.members?.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
|
||||
</DataColumn>
|
||||
<DataColumn id="members" label={formatMessage(labels.members)} align="end">
|
||||
<DataColumn id="members" label={t(labels.members)} align="end">
|
||||
{(row: any) => row?._count?.members}
|
||||
</DataColumn>
|
||||
<DataColumn id="websites" label={formatMessage(labels.websites)} align="end">
|
||||
<DataColumn id="websites" label={t(labels.websites)} align="end">
|
||||
{(row: any) => row?._count?.websites}
|
||||
</DataColumn>
|
||||
</DataTable>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function TeamDeleteForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { labels, formatMessage, getErrorMessage } = useMessages();
|
||||
const { labels, t, getErrorMessage } = useMessages();
|
||||
const { mutateAsync, error, isPending, touch } = useDeleteQuery(`/teams/${teamId}`);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
|
|
@ -33,7 +33,7 @@ export function TeamDeleteForm({
|
|||
onClose={onClose}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonLabel={t(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ export function TeamEditForm({
|
|||
onSave?: () => void;
|
||||
}) {
|
||||
const team = useTeam();
|
||||
const { formatMessage, labels, messages, getErrorMessage } = useMessages();
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(`/teams/${teamId}`);
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('teams');
|
||||
touch(`teams:${teamId}`);
|
||||
onSave?.();
|
||||
|
|
@ -46,30 +46,22 @@ export function TeamEditForm({
|
|||
{({ setValue }) => {
|
||||
return (
|
||||
<>
|
||||
<FormField name="id" label={formatMessage(labels.teamId)}>
|
||||
<FormField name="id" label={t(labels.teamId)}>
|
||||
<TextField isReadOnly allowCopy />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField isReadOnly={!allowEdit} />
|
||||
</FormField>
|
||||
{showAccessCode && (
|
||||
<Row alignItems="flex-end" gap>
|
||||
<FormField
|
||||
name="accessCode"
|
||||
label={formatMessage(labels.accessCode)}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<FormField name="accessCode" label={t(labels.accessCode)} style={{ flex: 1 }}>
|
||||
<TextField isReadOnly allowCopy />
|
||||
</FormField>
|
||||
{allowEdit && (
|
||||
<Button
|
||||
onPress={() => setValue('accessCode', generateId(), { shouldDirty: true })}
|
||||
>
|
||||
<IconLabel icon={<RefreshCw />} label={formatMessage(labels.regenerate)} />
|
||||
<IconLabel icon={<RefreshCw />} label={t(labels.regenerate)} />
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
|
|
@ -77,7 +69,7 @@ export function TeamEditForm({
|
|||
{allowEdit && (
|
||||
<FormButtons justifyContent="flex-end">
|
||||
<FormSubmitButton variant="primary" isPending={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useMessages, useModified } from '@/components/hooks';
|
|||
import { TeamDeleteForm } from './TeamDeleteForm';
|
||||
|
||||
export function TeamManage({ teamId }: { teamId: string }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const router = useRouter();
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -15,14 +15,11 @@ export function TeamManage({ teamId }: { teamId: string }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionForm
|
||||
label={formatMessage(labels.deleteTeam)}
|
||||
description={formatMessage(messages.deleteTeamWarning)}
|
||||
>
|
||||
<ActionForm label={t(labels.deleteTeam)} description={t(messages.deleteTeamWarning)}>
|
||||
<DialogTrigger>
|
||||
<Button variant="danger">{formatMessage(labels.delete)}</Button>
|
||||
<Button variant="danger">{t(labels.delete)}</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.deleteTeam)} style={{ width: 400 }}>
|
||||
<Dialog title={t(labels.deleteTeam)} style={{ width: 400 }}>
|
||||
{({ close }) => <TeamDeleteForm teamId={teamId} onSave={handleLeave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -15,23 +15,18 @@ export function TeamMemberEditButton({
|
|||
role: string;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = () => {
|
||||
touch('teams:members');
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
title={formatMessage(labels.editMember)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
<DialogButton icon={<Edit />} title={t(labels.editMember)} variant="quiet" width="400px">
|
||||
{({ close }) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function TeamMemberEditForm({
|
|||
onClose?: () => void;
|
||||
}) {
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery(`/teams/${teamId}/users/${userId}`);
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { t, labels, getErrorMessage } = useMessages();
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
|
|
@ -37,24 +37,20 @@ export function TeamMemberEditForm({
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={{ role }}>
|
||||
<FormField
|
||||
name="role"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
label={formatMessage(labels.role)}
|
||||
>
|
||||
<FormField name="role" rules={{ required: t(labels.required) }} label={t(labels.role)}>
|
||||
<Select>
|
||||
<ListItem id={ROLES.teamManager}>{formatMessage(labels.manager)}</ListItem>
|
||||
<ListItem id={ROLES.teamMember}>{formatMessage(labels.member)}</ListItem>
|
||||
<ListItem id={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</ListItem>
|
||||
<ListItem id={ROLES.teamManager}>{t(labels.manager)}</ListItem>
|
||||
<ListItem id={ROLES.teamMember}>{t(labels.member)}</ListItem>
|
||||
<ListItem id={ROLES.teamViewOnly}>{t(labels.viewOnly)}</ListItem>
|
||||
</Select>
|
||||
</FormField>
|
||||
|
||||
<FormButtons>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton variant="primary" isDisabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
|||
import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import { messages } from '@/components/messages';
|
||||
|
||||
export function TeamMemberRemoveButton({
|
||||
teamId,
|
||||
|
|
@ -16,7 +15,7 @@ export function TeamMemberRemoveButton({
|
|||
disabled?: boolean;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, FormattedMessage } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { mutateAsync, isPending, error } = useDeleteQuery(`/teams/${teamId}/users/${userId}`);
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
@ -31,27 +30,18 @@ export function TeamMemberRemoveButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
title={formatMessage(labels.confirm)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
<DialogButton icon={<Trash />} title={t(labels.confirm)} variant="quiet" width="400px">
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{userName}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
message={t.rich(messages.confirmRemove, {
|
||||
target: userName,
|
||||
b: chunks => <b>{chunks}</b>,
|
||||
})}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
buttonLabel={t(labels.remove)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -13,21 +13,21 @@ export function TeamMembersTable({
|
|||
teamId: string;
|
||||
allowEdit: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const roles = {
|
||||
[ROLES.teamOwner]: formatMessage(labels.teamOwner),
|
||||
[ROLES.teamManager]: formatMessage(labels.teamManager),
|
||||
[ROLES.teamMember]: formatMessage(labels.teamMember),
|
||||
[ROLES.teamViewOnly]: formatMessage(labels.viewOnly),
|
||||
[ROLES.teamOwner]: t(labels.teamOwner),
|
||||
[ROLES.teamManager]: t(labels.teamManager),
|
||||
[ROLES.teamMember]: t(labels.teamMember),
|
||||
[ROLES.teamViewOnly]: t(labels.viewOnly),
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="username" label={formatMessage(labels.username)}>
|
||||
<DataColumn id="username" label={t(labels.username)}>
|
||||
{(row: any) => row?.user?.username}
|
||||
</DataColumn>
|
||||
<DataColumn id="role" label={formatMessage(labels.role)}>
|
||||
<DataColumn id="role" label={t(labels.role)}>
|
||||
{(row: any) => roles[row?.role]}
|
||||
</DataColumn>
|
||||
{allowEdit && (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { PageHeader } from '@/components/common/PageHeader';
|
|||
import { Panel } from '@/components/common/Panel';
|
||||
import { useLoginQuery, useMessages, useNavigation, useTeam } from '@/components/hooks';
|
||||
import { Users } from '@/components/icons';
|
||||
import { labels } from '@/components/messages';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { TeamsMemberAddButton } from '../TeamsMemberAddButton';
|
||||
import { TeamEditForm } from './TeamEditForm';
|
||||
|
|
@ -15,7 +14,7 @@ export function TeamSettings({ teamId }: { teamId: string }) {
|
|||
const team: any = useTeam();
|
||||
const { user } = useLoginQuery();
|
||||
const { pathname } = useNavigation();
|
||||
const { formatMessage } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const isAdmin = pathname.includes('/admin');
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ export function TeamSettings({ teamId }: { teamId: string }) {
|
|||
</Panel>
|
||||
<Panel>
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Heading size="base">{formatMessage(labels.members)}</Heading>
|
||||
<Heading size="base">{t(labels.members)}</Heading>
|
||||
{isAdmin && <TeamsMemberAddButton teamId={teamId} />}
|
||||
</Row>
|
||||
<TeamMembersDataTable teamId={teamId} allowEdit={canEdit} />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useDeleteQuery, useMessages } from '@/components/hooks';
|
|||
import { X } from '@/components/icons';
|
||||
|
||||
export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { mutateAsync } = useDeleteQuery(`/teams/${teamId}/websites/${websiteId}`);
|
||||
|
||||
const handleRemoveTeamMember = async () => {
|
||||
|
|
@ -19,7 +19,7 @@ export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) {
|
|||
<Icon>
|
||||
<X />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
<Text>{t(labels.remove)}</Text>
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@ export function TeamWebsitesTable({
|
|||
data: any[];
|
||||
allowEdit: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{(row: any) => <Link href={`/teams/${teamId}/websites/${row.id}`}>{row.name}</Link>}
|
||||
</DataColumn>
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
||||
<DataColumn id="createdBy" label={formatMessage(labels.createdBy)}>
|
||||
<DataColumn id="domain" label={t(labels.domain)} />
|
||||
<DataColumn id="createdBy" label={t(labels.createdBy)}>
|
||||
{(row: any) => row?.createUser?.username}
|
||||
</DataColumn>
|
||||
{allowEdit && (
|
||||
|
|
|
|||
|
|
@ -5,23 +5,18 @@ import { DialogButton } from '@/components/input/DialogButton';
|
|||
import { WebsiteAddForm } from './WebsiteAddForm';
|
||||
|
||||
export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
toast(t(messages.saved));
|
||||
touch('websites');
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addWebsite)}
|
||||
variant="primary"
|
||||
width="400px"
|
||||
>
|
||||
<DialogButton icon={<Plus />} label={t(labels.addWebsite)} variant="primary" width="400px">
|
||||
{({ close }) => <WebsiteAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function WebsiteAddForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { t, labels, messages } = useMessages();
|
||||
const { mutateAsync, error, isPending } = useUpdateQuery('/websites', { teamId });
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -26,21 +26,21 @@ export function WebsiteAddForm({
|
|||
return (
|
||||
<Form onSubmit={handleSubmit} error={error?.message}>
|
||||
<FormField
|
||||
label={formatMessage(labels.name)}
|
||||
label={t(labels.name)}
|
||||
data-test="input-name"
|
||||
name="name"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={formatMessage(labels.domain)}
|
||||
label={t(labels.domain)}
|
||||
data-test="input-domain"
|
||||
name="domain"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
pattern: { value: DOMAIN_REGEX, message: formatMessage(messages.invalidDomain) },
|
||||
required: t(labels.required),
|
||||
pattern: { value: DOMAIN_REGEX, message: t(messages.invalidDomain) },
|
||||
}}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
|
|
@ -48,11 +48,11 @@ export function WebsiteAddForm({
|
|||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton data-test="button-submit" isDisabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
{t(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</Row>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ export interface WebsitesHeaderProps {
|
|||
}
|
||||
|
||||
export function WebsitesHeader({ allowCreate = true }: WebsitesHeaderProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { teamId } = useNavigation();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.websites)}>
|
||||
<PageHeader title={t(labels.websites)}>
|
||||
{allowCreate && <WebsiteAddButton teamId={teamId} />}
|
||||
</PageHeader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { WebsitesDataTable } from './WebsitesDataTable';
|
|||
export function WebsitesPage() {
|
||||
const { user } = useLoginQuery();
|
||||
const { teamId } = useNavigation();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data } = useTeamMembersQuery(teamId);
|
||||
|
||||
const showActions =
|
||||
|
|
@ -23,7 +23,7 @@ export function WebsitesPage() {
|
|||
return (
|
||||
<PageBody>
|
||||
<Column gap="6" margin="2">
|
||||
<PageHeader title={formatMessage(labels.websites)}>
|
||||
<PageHeader title={t(labels.websites)}>
|
||||
{showActions && <WebsiteAddButton teamId={teamId} />}
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ export interface WebsitesTableProps extends DataTableProps {
|
|||
}
|
||||
|
||||
export function WebsitesTable({ showActions, renderLink, ...props }: WebsitesTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { renderUrl } = useNavigation();
|
||||
|
||||
return (
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
<DataColumn id="name" label={t(labels.name)}>
|
||||
{renderLink}
|
||||
</DataColumn>
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
||||
<DataColumn id="domain" label={t(labels.domain)} />
|
||||
{showActions && (
|
||||
<DataColumn id="action" label=" " align="end">
|
||||
{(row: any) => {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export function Attribution({
|
|||
step,
|
||||
});
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
const { pageviews, visitors, visits } = data?.total || {};
|
||||
|
||||
|
|
@ -45,17 +45,17 @@ export function Attribution({
|
|||
? [
|
||||
{
|
||||
value: visitors,
|
||||
label: formatMessage(labels.visitors),
|
||||
label: t(labels.visitors),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: visits,
|
||||
label: formatMessage(labels.visits),
|
||||
label: t(labels.visits),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: pageviews,
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
]
|
||||
|
|
@ -72,7 +72,7 @@ export function Attribution({
|
|||
return (
|
||||
<ListTable
|
||||
title={title}
|
||||
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
|
||||
metric={t(currency ? labels.revenue : labels.visitors)}
|
||||
currency={currency}
|
||||
data={attributionData.map(({ x, y, z }: { x: string; y: number; z: number }) => ({
|
||||
label: x,
|
||||
|
|
@ -94,31 +94,31 @@ export function Attribution({
|
|||
);
|
||||
})}
|
||||
</MetricsBar>
|
||||
<SectionHeader title={formatMessage(labels.sources)} />
|
||||
<SectionHeader title={t(labels.sources)} />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.referrer} title={formatMessage(labels.referrer)} />
|
||||
<AttributionTable data={data?.referrer} title={t(labels.referrer)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.paidAds} title={formatMessage(labels.paidAds)} />
|
||||
<AttributionTable data={data?.paidAds} title={t(labels.paidAds)} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
<SectionHeader title="UTM" />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_source} title={formatMessage(labels.sources)} />
|
||||
<AttributionTable data={data?.utm_source} title={t(labels.sources)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_medium} title={formatMessage(labels.medium)} />
|
||||
<AttributionTable data={data?.utm_medium} title={t(labels.medium)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_cmapaign} title={formatMessage(labels.campaigns)} />
|
||||
<AttributionTable data={data?.utm_cmapaign} title={t(labels.campaigns)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_content} title={formatMessage(labels.content)} />
|
||||
<AttributionTable data={data?.utm_content} title={t(labels.content)} />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<AttributionTable data={data?.utm_term} title={formatMessage(labels.terms)} />
|
||||
<AttributionTable data={data?.utm_term} title={t(labels.terms)} />
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||
const [model, setModel] = useState('first-click');
|
||||
const [type, setType] = useState('path');
|
||||
const [step, setStep] = useState('/');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange();
|
||||
|
|
@ -19,30 +19,20 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||
<WebsiteControls websiteId={websiteId} />
|
||||
<Grid columns={{ base: '1fr', md: '1fr 1fr 1fr' }} gap>
|
||||
<Column>
|
||||
<Select
|
||||
label={formatMessage(labels.model)}
|
||||
value={model}
|
||||
defaultValue={model}
|
||||
onChange={setModel}
|
||||
>
|
||||
<ListItem id="first-click">{formatMessage(labels.firstClick)}</ListItem>
|
||||
<ListItem id="last-click">{formatMessage(labels.lastClick)}</ListItem>
|
||||
<Select label={t(labels.model)} value={model} defaultValue={model} onChange={setModel}>
|
||||
<ListItem id="first-click">{t(labels.firstClick)}</ListItem>
|
||||
<ListItem id="last-click">{t(labels.lastClick)}</ListItem>
|
||||
</Select>
|
||||
</Column>
|
||||
<Column>
|
||||
<Select
|
||||
label={formatMessage(labels.type)}
|
||||
value={type}
|
||||
defaultValue={type}
|
||||
onChange={setType}
|
||||
>
|
||||
<ListItem id="path">{formatMessage(labels.viewedPage)}</ListItem>
|
||||
<ListItem id="event">{formatMessage(labels.triggeredEvent)}</ListItem>
|
||||
<Select label={t(labels.type)} value={type} defaultValue={type} onChange={setType}>
|
||||
<ListItem id="path">{t(labels.viewedPage)}</ListItem>
|
||||
<ListItem id="event">{t(labels.triggeredEvent)}</ListItem>
|
||||
</Select>
|
||||
</Column>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.conversionStep)}
|
||||
label={t(labels.conversionStep)}
|
||||
value={step}
|
||||
defaultValue={step}
|
||||
onSearch={setStep}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface BreakdownProps {
|
|||
}
|
||||
|
||||
export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }: BreakdownProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { formatValue } = useFormat();
|
||||
const { fields } = useFields();
|
||||
const { data, error, isLoading } = useResultQuery<any>(
|
||||
|
|
@ -48,37 +48,22 @@ export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }
|
|||
</DataColumn>
|
||||
);
|
||||
})}
|
||||
<DataColumn
|
||||
id="visitors"
|
||||
label={formatMessage(labels.visitors)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px">
|
||||
{row => row?.visitors?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end" width="120px">
|
||||
<DataColumn id="visits" label={t(labels.visits)} align="end" width="120px">
|
||||
{row => row?.visits?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="views" label={formatMessage(labels.views)} align="end" width="120px">
|
||||
<DataColumn id="views" label={t(labels.views)} align="end" width="120px">
|
||||
{row => row?.views?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn
|
||||
id="bounceRate"
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="bounceRate" label={t(labels.bounceRate)} align="end" width="120px">
|
||||
{row => {
|
||||
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
|
||||
return `${Math.round(+n)}%`;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn
|
||||
id="visitDuration"
|
||||
label={formatMessage(labels.visitDuration)}
|
||||
align="end"
|
||||
width="120px"
|
||||
>
|
||||
<DataColumn id="visitDuration" label={t(labels.visitDuration)} align="end" width="120px">
|
||||
{row => {
|
||||
const n = row?.totaltime / row?.visits;
|
||||
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
|||
}
|
||||
|
||||
const FieldsButton = ({ value, onChange }) => {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogButton
|
||||
icon={<ListCheck />}
|
||||
label={formatMessage(labels.fields)}
|
||||
label={t(labels.fields)}
|
||||
width="400px"
|
||||
minHeight="300px"
|
||||
variant="outline"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function FieldSelectForm({
|
|||
onClose?: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState(selectedFields);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { fields, groupLabels } = useFields();
|
||||
|
||||
const handleChange = (value: string[]) => {
|
||||
|
|
@ -57,9 +57,9 @@ export function FieldSelectForm({
|
|||
</List>
|
||||
</Column>
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<Button onPress={onClose}>{t(labels.cancel)}</Button>
|
||||
<Button onPress={handleApply} variant="primary">
|
||||
{formatMessage(labels.apply)}
|
||||
{t(labels.apply)}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type FunnelResult = {
|
|||
};
|
||||
|
||||
export function Funnel({ id, name, type, parameters, websiteId }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
const { data, error, isLoading } = useResultQuery(type, {
|
||||
|
|
@ -43,10 +43,7 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<ReportEditButton id={id} name={name} type={type}>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={formatMessage(labels.funnel)}
|
||||
style={{ minHeight: 300, minWidth: 400 }}
|
||||
>
|
||||
<Dialog title={t(labels.funnel)} style={{ minHeight: 300, minWidth: 400 }}>
|
||||
<FunnelEditForm id={id} websiteId={websiteId} onClose={close} />
|
||||
</Dialog>
|
||||
);
|
||||
|
|
@ -90,9 +87,9 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<Column gap>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text color="muted">
|
||||
{formatMessage(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
{t(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</Text>
|
||||
<Text color="muted">{formatMessage(labels.conversionRate)}</Text>
|
||||
<Text color="muted">{t(labels.conversionRate)}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Row alignItems="center" gap>
|
||||
|
|
@ -109,7 +106,7 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
|
|||
<User />
|
||||
</Icon>
|
||||
<Text title={visitors.toString()} transform="lowercase">
|
||||
{`${formatLongNumber(visitors)} ${formatMessage(labels.visitors)}`}
|
||||
{`${formatLongNumber(visitors)} ${t(labels.visitors)}`}
|
||||
</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Plus } from '@/components/icons';
|
|||
import { FunnelEditForm } from './FunnelEditForm';
|
||||
|
||||
export function FunnelAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -12,14 +12,10 @@ export function FunnelAddButton({ websiteId }: { websiteId: string }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.funnel)}</Text>
|
||||
<Text>{t(labels.funnel)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
variant="modal"
|
||||
title={formatMessage(labels.funnel)}
|
||||
style={{ minHeight: 375, minWidth: 600 }}
|
||||
>
|
||||
<Dialog variant="modal" title={t(labels.funnel)} style={{ minHeight: 375, minWidth: 600 }}>
|
||||
{({ close }) => <FunnelEditForm websiteId={websiteId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function FunnelEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
|
||||
|
||||
|
|
@ -61,23 +61,15 @@ export function FunnelEditForm({
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error?.message} defaultValues={defaultValues}>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField autoFocus />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="window"
|
||||
label={formatMessage(labels.window)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="window" label={t(labels.window)} rules={{ required: t(labels.required) }}>
|
||||
<TextField />
|
||||
</FormField>
|
||||
<FormFieldArray
|
||||
name="steps"
|
||||
label={formatMessage(labels.steps)}
|
||||
label={t(labels.steps)}
|
||||
rules={{
|
||||
validate: value => value.length > 1 || 'At least two steps are required',
|
||||
}}
|
||||
|
|
@ -91,7 +83,7 @@ export function FunnelEditForm({
|
|||
<Column>
|
||||
<FormField
|
||||
name={`steps.${index}.type`}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<ActionSelect />
|
||||
</FormField>
|
||||
|
|
@ -99,7 +91,7 @@ export function FunnelEditForm({
|
|||
<Column>
|
||||
<FormField
|
||||
name={`steps.${index}.value`}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
{({ field, context }) => {
|
||||
const type = context.watch(`steps.${index}.type`);
|
||||
|
|
@ -123,7 +115,7 @@ export function FunnelEditForm({
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.add)}</Text>
|
||||
<Text>{t(labels.add)}</Text>
|
||||
</Button>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
|
@ -132,9 +124,9 @@ export function FunnelEditForm({
|
|||
</FormFieldArray>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton>{t(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface GoalProps {
|
|||
export type GoalData = { num: number; total: number };
|
||||
|
||||
export function Goal({ id, name, type, parameters, websiteId, startDate, endDate }: GoalProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
const { data, error, isLoading, isFetching } = useResultQuery<GoalData>(type, {
|
||||
|
|
@ -53,7 +53,7 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
{({ close }) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={formatMessage(labels.goal)}
|
||||
title={t(labels.goal)}
|
||||
variant="modal"
|
||||
style={{ minHeight: 300, minWidth: 400 }}
|
||||
>
|
||||
|
|
@ -66,10 +66,8 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
)}
|
||||
</Grid>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text color="muted">
|
||||
{formatMessage(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</Text>
|
||||
<Text color="muted">{formatMessage(labels.conversionRate)}</Text>
|
||||
<Text color="muted">{t(isPage ? labels.viewedPage : labels.triggeredEvent)}</Text>
|
||||
<Text color="muted">{t(labels.conversionRate)}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Row alignItems="center" gap>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Plus } from '@/components/icons';
|
|||
import { GoalEditForm } from './GoalEditForm';
|
||||
|
||||
export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -12,12 +12,12 @@ export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.goal)}</Text>
|
||||
<Text>{t(labels.goal)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
aria-label="add goal"
|
||||
title={formatMessage(labels.goal)}
|
||||
title={t(labels.goal)}
|
||||
style={{ minWidth: 400, minHeight: 300 }}
|
||||
>
|
||||
{({ close }) => <GoalEditForm websiteId={websiteId} onClose={close} />}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function GoalEditForm({
|
|||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
|
||||
|
||||
|
|
@ -59,29 +59,19 @@ export function GoalEditForm({
|
|||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField autoFocus />
|
||||
</FormField>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.action)}</Label>
|
||||
<Label>{t(labels.action)}</Label>
|
||||
<Grid columns="260px 1fr" gap>
|
||||
<Column>
|
||||
<FormField
|
||||
name="parameters.type"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="parameters.type" rules={{ required: t(labels.required) }}>
|
||||
<ActionSelect />
|
||||
</FormField>
|
||||
</Column>
|
||||
<Column>
|
||||
<FormField
|
||||
name="parameters.value"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="parameters.value" rules={{ required: t(labels.required) }}>
|
||||
{({ field }) => {
|
||||
return <LookupField websiteId={websiteId} type={type} {...field} />;
|
||||
}}
|
||||
|
|
@ -92,9 +82,9 @@ export function GoalEditForm({
|
|||
|
||||
<FormButtons>
|
||||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton>{t(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const EVENT_TYPES = {
|
|||
export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyProps) {
|
||||
const [selectedNode, setSelectedNode] = useState(null);
|
||||
const [activeNode, setActiveNode] = useState(null);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<any>('journey', {
|
||||
websiteId,
|
||||
steps,
|
||||
|
|
@ -178,7 +178,7 @@ export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyP
|
|||
<div className={styles.num}>{columnIndex + 1}</div>
|
||||
<div className={styles.stats}>
|
||||
<div className={styles.visitors} title={visitorCount}>
|
||||
{formatLongNumber(visitorCount)} {formatMessage(labels.visitors)}
|
||||
{formatLongNumber(visitorCount)} {t(labels.visitors)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -237,11 +237,11 @@ export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyP
|
|||
</Focusable>
|
||||
<Tooltip placement="top" offset={20} showArrow>
|
||||
<Text transform="lowercase" color="red">
|
||||
{`${dropped}% ${formatMessage(labels.dropoff)}`}
|
||||
{`${dropped}% ${t(labels.dropoff)}`}
|
||||
</Text>
|
||||
<Column>
|
||||
<Text transform="lowercase">
|
||||
{`${remaining}% ${formatMessage(labels.conversion)}`}
|
||||
{`${remaining}% ${t(labels.conversion)}`}
|
||||
</Text>
|
||||
</Column>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const JOURNEY_STEPS = [2, 3, 4, 5, 6, 7];
|
|||
const DEFAULT_STEP = 3;
|
||||
|
||||
export function JourneysPage({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange();
|
||||
|
|
@ -23,15 +23,15 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
const buttons = [
|
||||
{
|
||||
id: 'all',
|
||||
label: formatMessage(labels.all),
|
||||
label: t(labels.all),
|
||||
},
|
||||
{
|
||||
id: 'views',
|
||||
label: formatMessage(labels.views),
|
||||
label: t(labels.views),
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
label: formatMessage(labels.events),
|
||||
label: t(labels.events),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -39,12 +39,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
<Column gap>
|
||||
<WebsiteControls websiteId={websiteId} />
|
||||
<Grid columns="repeat(3, 1fr)" gap>
|
||||
<Select
|
||||
label={formatMessage(labels.steps)}
|
||||
value={steps}
|
||||
defaultValue={steps}
|
||||
onChange={setSteps}
|
||||
>
|
||||
<Select label={t(labels.steps)} value={steps} defaultValue={steps} onChange={setSteps}>
|
||||
{JOURNEY_STEPS.map(step => (
|
||||
<ListItem key={step} id={step}>
|
||||
{step}
|
||||
|
|
@ -53,7 +48,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
</Select>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.startStep)}
|
||||
label={t(labels.startStep)}
|
||||
value={startStep}
|
||||
onSearch={setStartStep}
|
||||
delay={1000}
|
||||
|
|
@ -61,7 +56,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
</Column>
|
||||
<Column>
|
||||
<SearchField
|
||||
label={formatMessage(labels.endStep)}
|
||||
label={t(labels.endStep)}
|
||||
value={endStep}
|
||||
onSearch={setEndStep}
|
||||
delay={1000}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface RetentionProps {
|
|||
}
|
||||
|
||||
export function Retention({ websiteId, days = DAYS, startDate, endDate }: RetentionProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const { data, error, isLoading } = useResultQuery('retention', {
|
||||
websiteId,
|
||||
|
|
@ -72,13 +72,13 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
|
|||
>
|
||||
<Column>
|
||||
<Text weight="bold" align="center">
|
||||
{formatMessage(labels.cohort)}
|
||||
{t(labels.cohort)}
|
||||
</Text>
|
||||
</Column>
|
||||
{days.map(n => (
|
||||
<Column key={n}>
|
||||
<Text weight="bold" align="center" wrap="nowrap">
|
||||
{formatMessage(labels.day)} {n}
|
||||
{t(labels.day)} {n}
|
||||
</Text>
|
||||
</Column>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
setItem(CURRENCY_CONFIG, value);
|
||||
};
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { locale, dateLocale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
const { data, error, isLoading } = useResultQuery<any>('revenue', {
|
||||
|
|
@ -48,7 +48,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
({ label: code }) => (
|
||||
<Row className={classNames(locale)} gap>
|
||||
<TypeIcon type="country" value={code} />
|
||||
<Text>{countryNames[code] || formatMessage(labels.unknown)}</Text>
|
||||
<Text>{countryNames[code] || t(labels.unknown)}</Text>
|
||||
</Row>
|
||||
),
|
||||
[countryNames, locale],
|
||||
|
|
@ -90,22 +90,22 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
return [
|
||||
{
|
||||
value: sum,
|
||||
label: formatMessage(labels.total),
|
||||
label: t(labels.total),
|
||||
formatValue: n => formatLongCurrency(n, currency),
|
||||
},
|
||||
{
|
||||
value: count ? sum / count : 0,
|
||||
label: formatMessage(labels.average),
|
||||
label: t(labels.average),
|
||||
formatValue: n => formatLongCurrency(n, currency),
|
||||
},
|
||||
{
|
||||
value: count,
|
||||
label: formatMessage(labels.transactions),
|
||||
label: t(labels.transactions),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
{
|
||||
value: unique_count,
|
||||
label: formatMessage(labels.uniqueCustomers),
|
||||
label: t(labels.uniqueCustomers),
|
||||
formatValue: formatLongNumber,
|
||||
},
|
||||
] as any;
|
||||
|
|
@ -142,8 +142,8 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
</Panel>
|
||||
<Panel>
|
||||
<ListTable
|
||||
title={formatMessage(labels.country)}
|
||||
metric={formatMessage(labels.revenue)}
|
||||
title={t(labels.country)}
|
||||
metric={t(labels.revenue)}
|
||||
data={data?.country.map(({ name, value }: { name: string; value: number }) => ({
|
||||
label: name,
|
||||
count: Number(value),
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ import { useMessages } from '@/components/hooks';
|
|||
import { formatLongCurrency } from '@/lib/format';
|
||||
|
||||
export function RevenueTable({ data = [] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="currency" label={formatMessage(labels.currency)} align="end" />
|
||||
<DataColumn id="total" label={formatMessage(labels.total)} align="end">
|
||||
<DataColumn id="currency" label={t(labels.currency)} align="end" />
|
||||
<DataColumn id="total" label={t(labels.total)} align="end">
|
||||
{(row: any) => formatLongCurrency(row.sum, row.currency)}
|
||||
</DataColumn>
|
||||
<DataColumn id="average" label={formatMessage(labels.average)} align="end">
|
||||
<DataColumn id="average" label={t(labels.average)} align="end">
|
||||
{(row: any) => formatLongCurrency(row.count ? row.sum / row.count : 0, row.currency)}
|
||||
</DataColumn>
|
||||
<DataColumn id="count" label={formatMessage(labels.transactions)} align="end" />
|
||||
<DataColumn id="unique_count" label={formatMessage(labels.uniqueCustomers)} align="end" />
|
||||
<DataColumn id="count" label={t(labels.transactions)} align="end" />
|
||||
<DataColumn id="unique_count" label={t(labels.uniqueCustomers)} align="end" />
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface UTMProps {
|
|||
}
|
||||
|
||||
export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<any>('utm', {
|
||||
websiteId,
|
||||
startDate,
|
||||
|
|
@ -49,7 +49,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
|||
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>
|
||||
</Heading>
|
||||
<ListTable
|
||||
metric={formatMessage(labels.views)}
|
||||
metric={t(labels.views)}
|
||||
data={items.map(({ utm, views }) => ({
|
||||
label: utm,
|
||||
count: views,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function WebsiteExpandedMenu({
|
|||
excludedIds?: string[];
|
||||
onItemClick?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
updateParams,
|
||||
query: { view },
|
||||
|
|
@ -49,176 +49,176 @@ export function WebsiteExpandedMenu({
|
|||
items: [
|
||||
{
|
||||
id: 'path',
|
||||
label: formatMessage(labels.path),
|
||||
label: t(labels.path),
|
||||
path: updateParams({ view: 'path' }),
|
||||
icon: <SquareSlash />,
|
||||
},
|
||||
{
|
||||
id: 'entry',
|
||||
label: formatMessage(labels.entry),
|
||||
label: t(labels.entry),
|
||||
path: updateParams({ view: 'entry' }),
|
||||
icon: <LogIn />,
|
||||
},
|
||||
{
|
||||
id: 'exit',
|
||||
label: formatMessage(labels.exit),
|
||||
label: t(labels.exit),
|
||||
path: updateParams({ view: 'exit' }),
|
||||
icon: <LogOut />,
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
label: formatMessage(labels.title),
|
||||
label: t(labels.title),
|
||||
path: updateParams({ view: 'title' }),
|
||||
icon: <Type />,
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
label: formatMessage(labels.query),
|
||||
label: t(labels.query),
|
||||
path: updateParams({ view: 'query' }),
|
||||
icon: <Search />,
|
||||
},
|
||||
].filter(filterExcluded),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.sources),
|
||||
label: t(labels.sources),
|
||||
items: [
|
||||
{
|
||||
id: 'referrer',
|
||||
label: formatMessage(labels.referrer),
|
||||
label: t(labels.referrer),
|
||||
path: updateParams({ view: 'referrer' }),
|
||||
icon: <Share2 />,
|
||||
},
|
||||
{
|
||||
id: 'channel',
|
||||
label: formatMessage(labels.channel),
|
||||
label: t(labels.channel),
|
||||
path: updateParams({ view: 'channel' }),
|
||||
icon: <Megaphone />,
|
||||
},
|
||||
{
|
||||
id: 'domain',
|
||||
label: formatMessage(labels.domain),
|
||||
label: t(labels.domain),
|
||||
path: updateParams({ view: 'domain' }),
|
||||
icon: <Globe />,
|
||||
},
|
||||
].filter(filterExcluded),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.location),
|
||||
label: t(labels.location),
|
||||
items: [
|
||||
{
|
||||
id: 'country',
|
||||
label: formatMessage(labels.country),
|
||||
label: t(labels.country),
|
||||
path: updateParams({ view: 'country' }),
|
||||
icon: <Earth />,
|
||||
},
|
||||
{
|
||||
id: 'region',
|
||||
label: formatMessage(labels.region),
|
||||
label: t(labels.region),
|
||||
path: updateParams({ view: 'region' }),
|
||||
icon: <MapPin />,
|
||||
},
|
||||
{
|
||||
id: 'city',
|
||||
label: formatMessage(labels.city),
|
||||
label: t(labels.city),
|
||||
path: updateParams({ view: 'city' }),
|
||||
icon: <Landmark />,
|
||||
},
|
||||
].filter(filterExcluded),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.environment),
|
||||
label: t(labels.environment),
|
||||
items: [
|
||||
{
|
||||
id: 'browser',
|
||||
label: formatMessage(labels.browser),
|
||||
label: t(labels.browser),
|
||||
path: updateParams({ view: 'browser' }),
|
||||
icon: <AppWindow />,
|
||||
},
|
||||
{
|
||||
id: 'os',
|
||||
label: formatMessage(labels.os),
|
||||
label: t(labels.os),
|
||||
path: updateParams({ view: 'os' }),
|
||||
icon: <Cpu />,
|
||||
},
|
||||
{
|
||||
id: 'device',
|
||||
label: formatMessage(labels.device),
|
||||
label: t(labels.device),
|
||||
path: updateParams({ view: 'device' }),
|
||||
icon: <Laptop />,
|
||||
},
|
||||
{
|
||||
id: 'language',
|
||||
label: formatMessage(labels.language),
|
||||
label: t(labels.language),
|
||||
path: updateParams({ view: 'language' }),
|
||||
icon: <Languages />,
|
||||
},
|
||||
{
|
||||
id: 'screen',
|
||||
label: formatMessage(labels.screen),
|
||||
label: t(labels.screen),
|
||||
path: updateParams({ view: 'screen' }),
|
||||
icon: <Monitor />,
|
||||
},
|
||||
].filter(filterExcluded),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.utm),
|
||||
label: t(labels.utm),
|
||||
items: [
|
||||
{
|
||||
id: 'utmSource',
|
||||
label: formatMessage(labels.source),
|
||||
label: t(labels.source),
|
||||
path: updateParams({ view: 'utmSource' }),
|
||||
icon: <Link2 />,
|
||||
},
|
||||
{
|
||||
id: 'utmMedium',
|
||||
label: formatMessage(labels.medium),
|
||||
label: t(labels.medium),
|
||||
path: updateParams({ view: 'utmMedium' }),
|
||||
icon: <Send />,
|
||||
},
|
||||
{
|
||||
id: 'utmCampaign',
|
||||
label: formatMessage(labels.campaign),
|
||||
label: t(labels.campaign),
|
||||
path: updateParams({ view: 'utmCampaign' }),
|
||||
icon: <Target />,
|
||||
},
|
||||
{
|
||||
id: 'utmContent',
|
||||
label: formatMessage(labels.content),
|
||||
label: t(labels.content),
|
||||
path: updateParams({ view: 'utmContent' }),
|
||||
icon: <Layers />,
|
||||
},
|
||||
{
|
||||
id: 'utmTerm',
|
||||
label: formatMessage(labels.term),
|
||||
label: t(labels.term),
|
||||
path: updateParams({ view: 'utmTerm' }),
|
||||
icon: <KeyRound />,
|
||||
},
|
||||
].filter(filterExcluded),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.other),
|
||||
label: t(labels.other),
|
||||
items: [
|
||||
{
|
||||
id: 'event',
|
||||
label: formatMessage(labels.event),
|
||||
label: t(labels.event),
|
||||
path: updateParams({ view: 'event' }),
|
||||
icon: <Lightning />,
|
||||
},
|
||||
{
|
||||
id: 'hostname',
|
||||
label: formatMessage(labels.hostname),
|
||||
label: t(labels.hostname),
|
||||
path: updateParams({ view: 'hostname' }),
|
||||
icon: <Network />,
|
||||
},
|
||||
{
|
||||
id: 'distinctId',
|
||||
label: formatMessage(labels.distinctId),
|
||||
label: t(labels.distinctId),
|
||||
path: updateParams({ view: 'distinctId' }),
|
||||
icon: <Fingerprint />,
|
||||
},
|
||||
{
|
||||
id: 'tag',
|
||||
label: formatMessage(labels.tag),
|
||||
label: t(labels.tag),
|
||||
path: updateParams({ view: 'tag' }),
|
||||
icon: <Tag />,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function WebsiteExpandedView({
|
|||
excludedIds?: string[];
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const {
|
||||
query: { view },
|
||||
} = useNavigation();
|
||||
|
|
@ -45,7 +45,7 @@ export function WebsiteExpandedView({
|
|||
</Column>
|
||||
<Column id="metrics-expanded-table" overflow="hidden">
|
||||
<MetricsExpandedTable
|
||||
title={formatMessage(labels[view])}
|
||||
title={t(labels[view])}
|
||||
type={view}
|
||||
websiteId={websiteId}
|
||||
onClose={onClose}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function WebsiteHeader({
|
|||
const { renderUrl, pathname } = useNavigation();
|
||||
const isSettings = pathname.endsWith('/settings');
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
|
||||
if (isSettings) {
|
||||
return null;
|
||||
|
|
@ -35,7 +35,7 @@ export function WebsiteHeader({
|
|||
|
||||
{showActions && (
|
||||
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
|
||||
<IconLabel icon={<Edit />}>{formatMessage(labels.edit)}</IconLabel>
|
||||
<IconLabel icon={<Edit />}>{t(labels.edit)}</IconLabel>
|
||||
</LinkButton>
|
||||
)}
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ import { useMessages, useNavigation } from '@/components/hooks';
|
|||
import { Edit, MoreHorizontal, Share } from '@/components/icons';
|
||||
|
||||
export function WebsiteMenu({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { t, labels } = useMessages();
|
||||
const { router, updateParams, renderUrl } = useNavigation();
|
||||
|
||||
const menuItems = [
|
||||
{ id: 'share', label: formatMessage(labels.share), icon: <Share /> },
|
||||
{ id: 'edit', label: formatMessage(labels.edit), icon: <Edit />, seperator: true },
|
||||
{ id: 'share', label: t(labels.share), icon: <Share /> },
|
||||
{ id: 'edit', label: t(labels.edit), icon: <Edit />, seperator: true },
|
||||
];
|
||||
|
||||
const handleAction = (id: any) => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue