mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 23:27:12 +01:00
Reworked settings screens.
This commit is contained in:
parent
c1d301ffdc
commit
0a16ab38e4
58 changed files with 362 additions and 365 deletions
|
|
@ -1,31 +1,49 @@
|
|||
'use client';
|
||||
import { ReactNode } from 'react';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { SideBar } from '@/components/common/SideBar';
|
||||
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'websites',
|
||||
id: 'profile',
|
||||
label: formatMessage(labels.profile),
|
||||
url: '/settings/profile',
|
||||
},
|
||||
{ id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||
user.isAdmin && {
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
url: '/settings/websites',
|
||||
},
|
||||
{ key: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||
user.isAdmin && {
|
||||
key: 'users',
|
||||
id: 'users',
|
||||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
},
|
||||
].filter(n => n);
|
||||
|
||||
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<SideBar items={items} />
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.settings)} />
|
||||
|
||||
<Grid columns="160px 1fr" gap="6">
|
||||
<Column marginTop="6">
|
||||
<SideMenu items={items} selectedKey={value} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Panel>{children}</Panel>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
26
src/app/(main)/settings/profile/DateRangeSetting.tsx
Normal file
26
src/app/(main)/settings/profile/DateRangeSetting.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { DateFilter } from '@/components/input/DateFilter';
|
||||
import { Button, Row } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_DATE_RANGE } from '@/lib/constants';
|
||||
import { DateRange } from '@/lib/types';
|
||||
|
||||
export function DateRangeSetting() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { dateRange, saveDateRange } = useDateRange();
|
||||
const { value } = dateRange;
|
||||
|
||||
const handleChange = (value: string | DateRange) => saveDateRange(value);
|
||||
const handleReset = () => saveDateRange(DEFAULT_DATE_RANGE);
|
||||
|
||||
return (
|
||||
<Row gap="3">
|
||||
<DateFilter
|
||||
value={value}
|
||||
startDate={dateRange.startDate}
|
||||
endDate={dateRange.endDate}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
48
src/app/(main)/settings/profile/LanguageSetting.tsx
Normal file
48
src/app/(main)/settings/profile/LanguageSetting.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Select, ListItem, Row } from '@umami/react-zen';
|
||||
import { useLocale, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_LOCALE } from '@/lib/constants';
|
||||
import { languages } from '@/lib/lang';
|
||||
|
||||
export function LanguageSetting() {
|
||||
const [search, setSearch] = useState('');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const items = search
|
||||
? Object.keys(languages).filter(n => {
|
||||
return (
|
||||
n.toLowerCase().includes(search.toLowerCase()) ||
|
||||
languages[n].label.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
})
|
||||
: Object.keys(languages);
|
||||
|
||||
const handleReset = () => saveLocale(DEFAULT_LOCALE);
|
||||
|
||||
const handleOpen = isOpen => {
|
||||
if (isOpen) {
|
||||
setSearch('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gap="3">
|
||||
<Select
|
||||
selectedKey={locale}
|
||||
onChange={val => saveLocale(val as string)}
|
||||
allowSearch
|
||||
onSearch={setSearch}
|
||||
onOpenChange={handleOpen}
|
||||
listProps={{ style: { maxHeight: '300px' } }}
|
||||
>
|
||||
{items.map(item => (
|
||||
<ListItem key={item} id={item}>
|
||||
{languages[item].label}
|
||||
</ListItem>
|
||||
))}
|
||||
{!items.length && <ListItem></ListItem>}
|
||||
</Select>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
29
src/app/(main)/settings/profile/PasswordChangeButton.tsx
Normal file
29
src/app/(main)/settings/profile/PasswordChangeButton.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen';
|
||||
import { PasswordEditForm } from './PasswordEditForm';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function PasswordChangeButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Lock />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.changePassword)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.changePassword)}>
|
||||
{({ close }) => <PasswordEditForm onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
70
src/app/(main)/settings/profile/PasswordEditForm.tsx
Normal file
70
src/app/(main)/settings/profile/PasswordEditForm.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormButtons,
|
||||
PasswordField,
|
||||
Button,
|
||||
FormSubmitButton,
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
|
||||
export function PasswordEditForm({ onSave, onClose }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (data: any) => post('/me/password', data),
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave();
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const samePassword = (value: string, values: { [key: string]: any }) => {
|
||||
if (value !== values.newPassword) {
|
||||
return formatMessage(messages.noMatchPassword);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<FormField
|
||||
label={formatMessage(labels.currentPassword)}
|
||||
name="currentPassword"
|
||||
rules={{ required: 'Required' }}
|
||||
>
|
||||
<PasswordField autoComplete="current-password" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="newPassword"
|
||||
label={formatMessage(labels.newPassword)}
|
||||
rules={{
|
||||
required: 'Required',
|
||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
||||
}}
|
||||
>
|
||||
<PasswordField autoComplete="new-password" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="confirmPassword"
|
||||
label={formatMessage(labels.confirmPassword)}
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
minLength: { value: 8, message: formatMessage(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>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
8
src/app/(main)/settings/profile/ProfileHeader.tsx
Normal file
8
src/app/(main)/settings/profile/ProfileHeader.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ProfileHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return <SectionHeader title={formatMessage(labels.profile)}></SectionHeader>;
|
||||
}
|
||||
16
src/app/(main)/settings/profile/ProfilePage.tsx
Normal file
16
src/app/(main)/settings/profile/ProfilePage.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
'use client';
|
||||
import { ProfileSettings } from './ProfileSettings';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
|
||||
export function ProfilePage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={formatMessage(labels.profile)} />
|
||||
|
||||
<ProfileSettings />
|
||||
</>
|
||||
);
|
||||
}
|
||||
77
src/app/(main)/settings/profile/ProfileSettings.tsx
Normal file
77
src/app/(main)/settings/profile/ProfileSettings.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Row, Column, Label } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { TimezoneSetting } from './TimezoneSetting';
|
||||
import { DateRangeSetting } from './DateRangeSetting';
|
||||
import { LanguageSetting } from './LanguageSetting';
|
||||
import { ThemeSetting } from './ThemeSetting';
|
||||
import { PasswordChangeButton } from './PasswordChangeButton';
|
||||
|
||||
export function ProfileSettings() {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { username, role } = user;
|
||||
|
||||
const renderRole = (value: string) => {
|
||||
if (value === ROLES.user) {
|
||||
return formatMessage(labels.user);
|
||||
}
|
||||
if (value === ROLES.admin) {
|
||||
return formatMessage(labels.admin);
|
||||
}
|
||||
if (value === ROLES.viewOnly) {
|
||||
return formatMessage(labels.viewOnly);
|
||||
}
|
||||
|
||||
return formatMessage(labels.unknown);
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap="6">
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.username)}</Label>
|
||||
{username}
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.role)}</Label>
|
||||
{renderRole(role)}
|
||||
</Column>
|
||||
|
||||
{!cloudMode && (
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.password)}</Label>
|
||||
<Row>
|
||||
<PasswordChangeButton />
|
||||
</Row>
|
||||
</Column>
|
||||
)}
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||
<DateRangeSetting />
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.language)}</Label>
|
||||
<LanguageSetting />
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.timezone)}</Label>
|
||||
<TimezoneSetting />
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.theme)}</Label>
|
||||
<ThemeSetting />
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
24
src/app/(main)/settings/profile/ThemeSetting.tsx
Normal file
24
src/app/(main)/settings/profile/ThemeSetting.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Row, Button, Icon, useTheme } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
|
||||
export function ThemeSetting() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Row gap>
|
||||
<Button
|
||||
variant={theme === 'light' ? 'primary' : 'secondary'}
|
||||
onPress={() => setTheme('light')}
|
||||
>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Sun />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button variant={theme === 'dark' ? 'primary' : 'secondary'} onPress={() => setTheme('dark')}>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Moon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
44
src/app/(main)/settings/profile/TimezoneSetting.tsx
Normal file
44
src/app/(main)/settings/profile/TimezoneSetting.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { useState } from 'react';
|
||||
import { Row, Select, ListItem, Button } from '@umami/react-zen';
|
||||
import { useTimezone, useMessages } from '@/components/hooks';
|
||||
import { getTimezone } from '@/lib/date';
|
||||
|
||||
const timezones = Intl.supportedValuesOf('timeZone');
|
||||
|
||||
export function TimezoneSetting() {
|
||||
const [search, setSearch] = useState('');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { timezone, saveTimezone } = useTimezone();
|
||||
const items = search
|
||||
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
||||
: timezones;
|
||||
|
||||
const handleReset = () => saveTimezone(getTimezone());
|
||||
|
||||
const handleOpen = isOpen => {
|
||||
if (isOpen) {
|
||||
setSearch('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gap="3">
|
||||
<Select
|
||||
selectedKey={timezone}
|
||||
onChange={(value: any) => saveTimezone(value)}
|
||||
allowSearch={true}
|
||||
onSearch={setSearch}
|
||||
onOpenChange={handleOpen}
|
||||
listProps={{ style: { maxHeight: '300px' } }}
|
||||
>
|
||||
{items.map((item: any) => (
|
||||
<ListItem key={item} id={item}>
|
||||
{item}
|
||||
</ListItem>
|
||||
))}
|
||||
{!items.length && <ListItem></ListItem>}
|
||||
</Select>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
10
src/app/(main)/settings/profile/page.tsx
Normal file
10
src/app/(main)/settings/profile/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Metadata } from 'next';
|
||||
import { ProfilePage } from './ProfilePage';
|
||||
|
||||
export default function () {
|
||||
return <ProfilePage />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Profile',
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Row } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { TeamsJoinButton } from './TeamsJoinButton';
|
||||
|
|
@ -11,11 +11,11 @@ export function TeamsHeader({ allowCreate = true }: { allowCreate?: boolean }) {
|
|||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<SectionHeader title={formatMessage(labels.teams)}>
|
||||
<Row gap="3">
|
||||
{!cloudMode && <TeamsJoinButton />}
|
||||
{allowCreate && user.role !== ROLES.viewOnly && <TeamsAddButton />}
|
||||
</Row>
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function TeamsJoinButton() {
|
|||
return (
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.AddUser />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
'use client';
|
||||
import { TeamsDataTable } from './TeamsDataTable';
|
||||
import { TeamsHeader } from './TeamsHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function TeamsSettingsPage() {
|
||||
return (
|
||||
<>
|
||||
<Column gap>
|
||||
<TeamsHeader />
|
||||
<TeamsDataTable />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { UserAddButton } from './UserAddButton';
|
||||
|
||||
export function UsersHeader({ onAdd }: { onAdd?: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.users)}>
|
||||
<UserAddButton onSave={onAdd} />
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,21 @@
|
|||
'use client';
|
||||
import { UsersDataTable } from './UsersDataTable';
|
||||
import { UsersHeader } from './UsersHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { UserAddButton } from '@/app/(main)/settings/users/UserAddButton';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function UsersSettingsPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UsersHeader />
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.users)}>
|
||||
<UserAddButton onSave={handleSave} />
|
||||
</SectionHeader>
|
||||
<UsersDataTable />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
|||
import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { UserEditForm } from './UserEditForm';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { UserWebsites } from './UserWebsites';
|
||||
import { UserContext } from './UserProvider';
|
||||
|
|
@ -13,7 +13,7 @@ export function UserSettings({ userId }: { userId: string }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={user?.username} icon={<Icons.User />} />
|
||||
<SectionHeader title={user?.username} icon={<Icons.User />} />
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { WebsiteAddButton } from './WebsiteAddButton';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
|
||||
export interface WebsitesHeaderProps {
|
||||
allowCreate?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
'use client';
|
||||
import { useLoginQuery } from '@/components/hooks';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { WebsitesDataTable } from './WebsitesDataTable';
|
||||
import { WebsitesHeader } from './WebsitesHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function WebsitesSettingsPage({ teamId }: { teamId: string }) {
|
||||
const { user } = useLoginQuery();
|
||||
const canCreate = user.role !== ROLES.viewOnly;
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsitesHeader allowCreate={canCreate} />
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.websites)}>
|
||||
{canCreate && <WebsiteAddButton teamId={teamId} />}
|
||||
</SectionHeader>
|
||||
<WebsitesDataTable teamId={teamId} />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { useContext } from 'react';
|
||||
import { Button, Icon, Tabs, TabList, Tab, TabPanel, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { Icon, Tabs, TabList, Tab, TabPanel, Text } from '@umami/react-zen';
|
||||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ShareUrl } from './ShareUrl';
|
||||
import { TrackingCode } from './TrackingCode';
|
||||
import { WebsiteData } from './WebsiteData';
|
||||
import { WebsiteEditForm } from './WebsiteEditForm';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function WebsiteSettings({
|
||||
websiteId,
|
||||
|
|
@ -22,16 +22,18 @@ export function WebsiteSettings({
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={website?.name} icon={<Icons.Globe />}>
|
||||
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<SectionHeader title={website?.name} icon={<Icons.Globe />}>
|
||||
<LinkButton
|
||||
variant="primary"
|
||||
href={`/websites/${websiteId}`}
|
||||
target={openExternal ? '_blank' : null}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</LinkButton>
|
||||
</SectionHeader>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue