feat: add workspace page
This commit is contained in:
parent
b862dd7427
commit
491807165c
@ -28,6 +28,7 @@ import { Route as TelemetryAddImport } from './routes/telemetry/add'
|
|||||||
import { Route as TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId'
|
import { Route as TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId'
|
||||||
import { Route as SurveyAddImport } from './routes/survey/add'
|
import { Route as SurveyAddImport } from './routes/survey/add'
|
||||||
import { Route as StatusSlugImport } from './routes/status/$slug'
|
import { Route as StatusSlugImport } from './routes/status/$slug'
|
||||||
|
import { Route as SettingsWorkspaceImport } from './routes/settings/workspace'
|
||||||
import { Route as SettingsUsageImport } from './routes/settings/usage'
|
import { Route as SettingsUsageImport } from './routes/settings/usage'
|
||||||
import { Route as SettingsProfileImport } from './routes/settings/profile'
|
import { Route as SettingsProfileImport } from './routes/settings/profile'
|
||||||
import { Route as SettingsNotificationsImport } from './routes/settings/notifications'
|
import { Route as SettingsNotificationsImport } from './routes/settings/notifications'
|
||||||
@ -132,6 +133,11 @@ const StatusSlugRoute = StatusSlugImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SettingsWorkspaceRoute = SettingsWorkspaceImport.update({
|
||||||
|
path: '/workspace',
|
||||||
|
getParentRoute: () => SettingsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const SettingsUsageRoute = SettingsUsageImport.update({
|
const SettingsUsageRoute = SettingsUsageImport.update({
|
||||||
path: '/usage',
|
path: '/usage',
|
||||||
getParentRoute: () => SettingsRoute,
|
getParentRoute: () => SettingsRoute,
|
||||||
@ -292,6 +298,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SettingsUsageImport
|
preLoaderRoute: typeof SettingsUsageImport
|
||||||
parentRoute: typeof SettingsImport
|
parentRoute: typeof SettingsImport
|
||||||
}
|
}
|
||||||
|
'/settings/workspace': {
|
||||||
|
preLoaderRoute: typeof SettingsWorkspaceImport
|
||||||
|
parentRoute: typeof SettingsImport
|
||||||
|
}
|
||||||
'/status/$slug': {
|
'/status/$slug': {
|
||||||
preLoaderRoute: typeof StatusSlugImport
|
preLoaderRoute: typeof StatusSlugImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
@ -374,6 +384,7 @@ export const routeTree = rootRoute.addChildren([
|
|||||||
SettingsNotificationsRoute,
|
SettingsNotificationsRoute,
|
||||||
SettingsProfileRoute,
|
SettingsProfileRoute,
|
||||||
SettingsUsageRoute,
|
SettingsUsageRoute,
|
||||||
|
SettingsWorkspaceRoute,
|
||||||
]),
|
]),
|
||||||
SurveyRoute.addChildren([
|
SurveyRoute.addChildren([
|
||||||
SurveyAddRoute,
|
SurveyAddRoute,
|
||||||
|
@ -34,6 +34,11 @@ function PageComponent() {
|
|||||||
title: t('Notifications'),
|
title: t('Notifications'),
|
||||||
href: '/settings/notifications',
|
href: '/settings/notifications',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace',
|
||||||
|
title: t('Workspace'),
|
||||||
|
href: '/settings/workspace',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'auditLog',
|
id: 'auditLog',
|
||||||
title: t('Audit Log'),
|
title: t('Audit Log'),
|
||||||
|
94
src/client/routes/settings/workspace.tsx
Normal file
94
src/client/routes/settings/workspace.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { useCurrentWorkspace } from '../../store/user';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import { AppRouterOutput, trpc } from '@/api/trpc';
|
||||||
|
import { createColumnHelper, DataTable } from '@/components/DataTable';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/settings/workspace')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
type MemberInfo = AppRouterOutput['workspace']['members'][number];
|
||||||
|
const columnHelper = createColumnHelper<MemberInfo>();
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { id, name } = useCurrentWorkspace();
|
||||||
|
const { data: members = [] } = trpc.workspace.members.useQuery({
|
||||||
|
workspaceId: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
columnHelper.accessor(
|
||||||
|
(data) =>
|
||||||
|
get(data, ['user', 'nickname']) || get(data, ['user', 'username']),
|
||||||
|
{
|
||||||
|
header: t('Name'),
|
||||||
|
size: 300,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
columnHelper.accessor('user.email', {
|
||||||
|
header: t('Email'),
|
||||||
|
size: 130,
|
||||||
|
cell: (props) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{props.getValue()}
|
||||||
|
{props.row.original.user.emailVerified && (
|
||||||
|
<Badge className="ml-1">{t('Verified')}</Badge>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('role', {
|
||||||
|
header: t('Role'),
|
||||||
|
size: 130,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper header={<CommonHeader title={t('Workspace')} />}>
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-lg font-bold">
|
||||||
|
{t('Current Workspace:')} {name}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div>
|
||||||
|
<span className="mr-2">{t('Workspace ID')}:</span>
|
||||||
|
<span>
|
||||||
|
<Typography.Text code={true} copyable={true}>
|
||||||
|
{id}
|
||||||
|
</Typography.Text>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-lg font-bold">
|
||||||
|
{t('Members')}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<DataTable columns={columns} data={members} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -18,6 +18,7 @@ import { OpenApiMeta } from 'trpc-openapi';
|
|||||||
import { getServerCount } from '../../model/serverStatus.js';
|
import { getServerCount } from '../../model/serverStatus.js';
|
||||||
import { ROLES, slugRegex } from '@tianji/shared';
|
import { ROLES, slugRegex } from '@tianji/shared';
|
||||||
import { createUserSelect } from '../../model/user.js';
|
import { createUserSelect } from '../../model/user.js';
|
||||||
|
import { WorkspacesOnUsersModelSchema } from '../../prisma/zod/workspacesonusers.js';
|
||||||
|
|
||||||
export const workspaceRouter = router({
|
export const workspaceRouter = router({
|
||||||
create: protectProedure
|
create: protectProedure
|
||||||
@ -150,6 +151,48 @@ export const workspaceRouter = router({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
members: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildWorkspaceOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{workspaceId}/members',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(
|
||||||
|
z.array(
|
||||||
|
WorkspacesOnUsersModelSchema.merge(
|
||||||
|
z.object({
|
||||||
|
user: z.object({
|
||||||
|
username: z.string(),
|
||||||
|
nickname: z.string().nullable(),
|
||||||
|
email: z.string().nullable(),
|
||||||
|
emailVerified: z.date().nullable(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
const list = await prisma.workspacesOnUsers.findMany({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
username: true,
|
||||||
|
nickname: true,
|
||||||
|
email: true,
|
||||||
|
emailVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}),
|
||||||
getUserWorkspaceRole: publicProcedure
|
getUserWorkspaceRole: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
Loading…
Reference in New Issue
Block a user