mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Admin section updates.
This commit is contained in:
parent
87449ece9e
commit
1b81074752
20 changed files with 274 additions and 647 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -19,22 +19,22 @@ export function SideNav(props: any) {
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.websites),
|
label: formatMessage(labels.websites),
|
||||||
href: '/websites',
|
href: renderUrl('/websites', false),
|
||||||
icon: <Globe />,
|
icon: <Globe />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.boards),
|
label: formatMessage(labels.boards),
|
||||||
href: '/boards',
|
href: renderUrl('/boards', false),
|
||||||
icon: <LayoutDashboard />,
|
icon: <LayoutDashboard />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.links),
|
label: formatMessage(labels.links),
|
||||||
href: '/links',
|
href: renderUrl('/links', false),
|
||||||
icon: <LinkIcon />,
|
icon: <LinkIcon />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.pixels),
|
label: formatMessage(labels.pixels),
|
||||||
href: '/pixels',
|
href: renderUrl('/pixels', false),
|
||||||
icon: <Grid2X2 />,
|
icon: <Grid2X2 />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -57,7 +57,7 @@ export function SideNav(props: any) {
|
||||||
<SidebarSection>
|
<SidebarSection>
|
||||||
{links.map(({ href, label, icon }) => {
|
{links.map(({ href, label, icon }) => {
|
||||||
return (
|
return (
|
||||||
<Link key={href} href={renderUrl(href, false)} role="button">
|
<Link key={href} href={href} role="button">
|
||||||
<SidebarItem label={label} icon={icon} isSelected={pathname.startsWith(href)} />
|
<SidebarItem label={label} icon={icon} isSelected={pathname.startsWith(href)} />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { TeamDetails } from '@/app/(main)/teams/[teamId]/settings/team/TeamDetails';
|
import { TeamDetails } from '@/app/(main)/teams/[teamId]/settings/team/TeamDetails';
|
||||||
|
import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
|
|
||||||
export function AdminTeamPage({ teamId }: { teamId: string }) {
|
export function AdminTeamPage({ teamId }: { teamId: string }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<TeamProvider teamId={teamId}>
|
||||||
<TeamDetails teamId={teamId} />
|
<TeamDetails teamId={teamId} />
|
||||||
</>
|
</TeamProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import { AdminTeamPage } from './AdminTeamPage';
|
import { AdminTeamPage } from './AdminTeamPage';
|
||||||
import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export default async function ({ params }: { params: Promise<{ teamId: string }> }) {
|
export default async function ({ params }: { params: Promise<{ teamId: string }> }) {
|
||||||
const { teamId } = await params;
|
const { teamId } = await params;
|
||||||
|
|
||||||
return (
|
return <AdminTeamPage teamId={teamId} />;
|
||||||
<TeamProvider teamId={teamId}>
|
|
||||||
<AdminTeamPage teamId={teamId} />
|
|
||||||
</TeamProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ export default async function ({ params }: { params: Promise<{ userId: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Users',
|
title: 'User',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use client';
|
||||||
|
import { WebsiteSettings } from '@/app/(main)/settings/websites/[websiteId]/WebsiteSettings';
|
||||||
|
import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||||
|
|
||||||
|
export function AdminWebsitePage({ websiteId }: { websiteId: string }) {
|
||||||
|
return (
|
||||||
|
<WebsiteProvider websiteId={websiteId}>
|
||||||
|
<WebsiteSettings websiteId={websiteId} />
|
||||||
|
</WebsiteProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/app/(main)/admin/websites/[websiteId]/page.tsx
Normal file
12
src/app/(main)/admin/websites/[websiteId]/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { AdminWebsitePage } from './AdminWebsitePage';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||||
|
const { websiteId } = await params;
|
||||||
|
|
||||||
|
return <AdminWebsitePage websiteId={websiteId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Website',
|
||||||
|
};
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.filters {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-inline-end: 20px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { Button, Icon, Text } from '@umami/react-zen';
|
|
||||||
import { Close } from '@/components/icons';
|
|
||||||
import styles from './WebsiteTags.module.css';
|
|
||||||
|
|
||||||
export function WebsiteTags({
|
|
||||||
items = [],
|
|
||||||
websites = [],
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
items: any[];
|
|
||||||
websites: any[];
|
|
||||||
onClick: (e: Event) => void;
|
|
||||||
}) {
|
|
||||||
if (websites.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.filters}>
|
|
||||||
{websites.map(websiteId => {
|
|
||||||
const website = items.find(a => a.id === websiteId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={websiteId} className={styles.tag}>
|
|
||||||
<Button onPress={() => onClick(websiteId)} variant="primary" size="sm">
|
|
||||||
<Text>
|
|
||||||
<b>{`${website.name}`}</b>
|
|
||||||
</Text>
|
|
||||||
<Icon>
|
|
||||||
<Close />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -19,19 +19,21 @@ export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?:
|
||||||
const { touch } = useModified();
|
const { touch } = useModified();
|
||||||
const { teamId, renderUrl } = useNavigation();
|
const { teamId, renderUrl } = useNavigation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data } = useUserTeamsQuery(user.id);
|
const { data: teams } = useUserTeamsQuery(user.id);
|
||||||
|
|
||||||
const canTransferWebsite =
|
const canTransferWebsite =
|
||||||
(
|
(
|
||||||
!teamId &&
|
(!teamId &&
|
||||||
data.filter(({ teamUser }) =>
|
teams?.data?.filter(({ teamUser }) =>
|
||||||
teamUser.find(
|
teamUser.find(
|
||||||
({ role, userId }) =>
|
({ role, userId }) =>
|
||||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||||
),
|
),
|
||||||
)
|
)) ||
|
||||||
|
[]
|
||||||
).length > 0 ||
|
).length > 0 ||
|
||||||
(teamId &&
|
(teamId &&
|
||||||
!!data
|
!!teams?.data
|
||||||
?.find(({ id }) => id === teamId)
|
?.find(({ id }) => id === teamId)
|
||||||
?.teamUser.find(({ role, userId }) => role === ROLES.teamOwner && userId === user.id));
|
?.teamUser.find(({ role, userId }) => role === ROLES.teamOwner && userId === user.id));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@ import {
|
||||||
Switch,
|
Switch,
|
||||||
FormSubmitButton,
|
FormSubmitButton,
|
||||||
Column,
|
Column,
|
||||||
Icon,
|
|
||||||
Grid,
|
|
||||||
Label,
|
Label,
|
||||||
useToast,
|
useToast,
|
||||||
TooltipTrigger,
|
Row,
|
||||||
Tooltip,
|
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { getRandomChars } from '@/lib/crypto';
|
import { getRandomChars } from '@/lib/crypto';
|
||||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||||
import { Refresh } from '@/components/icons';
|
|
||||||
|
|
||||||
const generateId = () => getRandomChars(16);
|
const generateId = () => getRandomChars(16);
|
||||||
|
|
||||||
|
|
@ -70,24 +66,19 @@ export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: Websit
|
||||||
{id && (
|
{id && (
|
||||||
<Column>
|
<Column>
|
||||||
<Label>{formatMessage(labels.shareUrl)}</Label>
|
<Label>{formatMessage(labels.shareUrl)}</Label>
|
||||||
<Grid columns="1fr auto" gap>
|
<TextField value={url} isReadOnly allowCopy />
|
||||||
<TextField value={url} isReadOnly allowCopy />
|
|
||||||
<TooltipTrigger>
|
|
||||||
<Button onPress={handleGenerate} variant="quiet" size="sm">
|
|
||||||
<Icon>
|
|
||||||
<Refresh />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
<Tooltip>{formatMessage(labels.regenerate)}</Tooltip>
|
|
||||||
</TooltipTrigger>
|
|
||||||
</Grid>
|
|
||||||
</Column>
|
</Column>
|
||||||
)}
|
)}
|
||||||
<FormButtons>
|
<FormButtons justifyContent="space-between">
|
||||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
<Row>
|
||||||
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
{id && <Button onPress={handleGenerate}>{formatMessage(labels.regenerate)}</Button>}
|
||||||
{formatMessage(labels.save)}
|
</Row>
|
||||||
</FormSubmitButton>
|
<Row>
|
||||||
|
{onClose && <Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>}
|
||||||
|
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
||||||
|
{formatMessage(labels.save)}
|
||||||
|
</FormSubmitButton>
|
||||||
|
</Row>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Column>
|
</Column>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,16 @@ export function WebsiteTransferForm({
|
||||||
const { mutate, error } = useMutation({
|
const { mutate, error } = useMutation({
|
||||||
mutationFn: (data: any) => post(`/websites/${websiteId}/transfer`, data),
|
mutationFn: (data: any) => post(`/websites/${websiteId}/transfer`, data),
|
||||||
});
|
});
|
||||||
const { result, query } = useUserTeamsQuery(user.id);
|
const { data: teams, isLoading } = useUserTeamsQuery(user.id);
|
||||||
const isTeamWebsite = !!website?.teamId;
|
const isTeamWebsite = !!website?.teamId;
|
||||||
|
|
||||||
const items = result.data.filter(({ teamUser }) =>
|
const items =
|
||||||
teamUser.find(
|
teams?.data?.filter(({ teamUser }) =>
|
||||||
({ role, userId }) =>
|
teamUser.find(
|
||||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
({ role, userId }) =>
|
||||||
),
|
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||||
);
|
),
|
||||||
|
) || [];
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
mutate(
|
mutate(
|
||||||
|
|
@ -60,7 +61,7 @@ export function WebsiteTransferForm({
|
||||||
setTeamId(key as string);
|
setTeamId(key as string);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (query.isLoading) {
|
if (isLoading) {
|
||||||
return <Loading icon="dots" position="center" />;
|
return <Loading icon="dots" position="center" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
|
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { useContext, useState } from 'react';
|
import { Users } from '@/components/icons';
|
||||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
|
||||||
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
||||||
import { TeamManage } from './TeamManage';
|
import { TeamManage } from './TeamManage';
|
||||||
import { TeamEditForm } from './TeamEditForm';
|
import { TeamEditForm } from './TeamEditForm';
|
||||||
|
import { TeamWebsitesDataTable } from '@/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable';
|
||||||
|
import { TeamMembersDataTable } from '@/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable';
|
||||||
|
|
||||||
export function TeamDetails({ teamId }: { teamId: string }) {
|
export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
const team = useContext(TeamContext);
|
const team = useContext(TeamContext);
|
||||||
|
|
@ -26,17 +29,25 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<SectionHeader title={team?.name}>
|
<SectionHeader title={team?.name} icon={<Users />}>
|
||||||
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||||
|
<Tab id="members">{formatMessage(labels.members)}</Tab>
|
||||||
|
<Tab id="websites">{formatMessage(labels.websites)}</Tab>
|
||||||
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel id="details">
|
<TabPanel id="details">
|
||||||
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel id="members">
|
||||||
|
<TeamMembersDataTable teamId={teamId} allowEdit />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel id="websites">
|
||||||
|
<TeamWebsitesDataTable teamId={teamId} allowEdit />
|
||||||
|
</TabPanel>
|
||||||
<TabPanel id="manage">
|
<TabPanel id="manage">
|
||||||
<TeamManage teamId={teamId} />
|
<TeamManage teamId={teamId} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
Button,
|
||||||
useToast,
|
useToast,
|
||||||
|
Text,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { getRandomChars } from '@/lib/crypto';
|
import { getRandomChars } from '@/lib/crypto';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
@ -47,8 +48,7 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
|
||||||
label={formatMessage(labels.name)}
|
label={formatMessage(labels.name)}
|
||||||
rules={{ required: formatMessage(labels.required) }}
|
rules={{ required: formatMessage(labels.required) }}
|
||||||
>
|
>
|
||||||
{allowEdit && <TextField />}
|
{allowEdit ? <TextField /> : <Text>{team?.name}</Text>}
|
||||||
{!allowEdit && team?.name}
|
|
||||||
</FormField>
|
</FormField>
|
||||||
{!cloudMode && allowEdit && (
|
{!cloudMode && allowEdit && (
|
||||||
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ export async function GET(request: Request) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
omit: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function SectionHeader({
|
||||||
return (
|
return (
|
||||||
<Row {...props} justifyContent="space-between" alignItems="center" height="60px">
|
<Row {...props} justifyContent="space-between" alignItems="center" height="60px">
|
||||||
<Row gap="3" alignItems="center">
|
<Row gap="3" alignItems="center">
|
||||||
{icon && <Icon>{icon}</Icon>}
|
{icon && <Icon size="md">{icon}</Icon>}
|
||||||
{title && <Heading size="3">{title}</Heading>}
|
{title && <Heading size="3">{title}</Heading>}
|
||||||
{description && <Text color="muted">{description}</Text>}
|
{description && <Text color="muted">{description}</Text>}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
import { useModified } from '../useModified';
|
import { useModified } from '../useModified';
|
||||||
import { usePagedQuery } from '@/components/hooks';
|
import { usePagedQuery } from '../usePagedQuery';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useTeamsQuery(params?: Record<string, any>, options?: ReactQueryOptions) {
|
export function useTeamsQuery(params?: Record<string, any>, options?: ReactQueryOptions) {
|
||||||
|
|
@ -8,7 +8,7 @@ export function useTeamsQuery(params?: Record<string, any>, options?: ReactQuery
|
||||||
const { modified } = useModified(`teams`);
|
const { modified } = useModified(`teams`);
|
||||||
|
|
||||||
return usePagedQuery({
|
return usePagedQuery({
|
||||||
queryKey: ['websites', { modified, ...params }],
|
queryKey: ['teams:admin', { modified, ...params }],
|
||||||
queryFn: pageParams => {
|
queryFn: pageParams => {
|
||||||
return get(`/admin/teams`, {
|
return get(`/admin/teams`, {
|
||||||
...params,
|
...params,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export function useUsersQuery() {
|
||||||
const { modified } = useModified(`users`);
|
const { modified } = useModified(`users`);
|
||||||
|
|
||||||
return usePagedQuery({
|
return usePagedQuery({
|
||||||
queryKey: ['users', { modified }],
|
queryKey: ['users:admin', { modified }],
|
||||||
queryFn: (pageParams: any) => {
|
queryFn: (pageParams: any) => {
|
||||||
return get('/admin/users', {
|
return get('/admin/users', {
|
||||||
...pageParams,
|
...pageParams,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export function useWebsitesQuery(params?: Record<string, any>, options?: ReactQu
|
||||||
const { modified } = useModified(`websites`);
|
const { modified } = useModified(`websites`);
|
||||||
|
|
||||||
return usePagedQuery({
|
return usePagedQuery({
|
||||||
queryKey: ['websites', { modified, ...params }],
|
queryKey: ['websites:admin', { modified, ...params }],
|
||||||
queryFn: pageParams => {
|
queryFn: pageParams => {
|
||||||
return get(`/admin/websites`, {
|
return get(`/admin/websites`, {
|
||||||
...pageParams,
|
...pageParams,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ export * from '@/app/(main)/settings/teams/TeamsDataTable';
|
||||||
export * from '@/app/(main)/settings/teams/TeamsHeader';
|
export * from '@/app/(main)/settings/teams/TeamsHeader';
|
||||||
export * from '@/app/(main)/settings/teams/TeamsJoinButton';
|
export * from '@/app/(main)/settings/teams/TeamsJoinButton';
|
||||||
export * from '@/app/(main)/settings/teams/TeamsTable';
|
export * from '@/app/(main)/settings/teams/TeamsTable';
|
||||||
export * from '@/app/(main)/settings/teams/WebsiteTags';
|
|
||||||
|
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
||||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteTrackingCode';
|
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteTrackingCode';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue