diff --git a/src/client/routes/settings/workspace.tsx b/src/client/routes/settings/workspace.tsx index 27079d1..c2b868d 100644 --- a/src/client/routes/settings/workspace.tsx +++ b/src/client/routes/settings/workspace.tsx @@ -3,7 +3,11 @@ 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, useHasAdminPermission } from '../../store/user'; +import { + useCurrentWorkspace, + useHasAdminPermission, + useUserStore, +} from '../../store/user'; import { CommonHeader } from '@/components/CommonHeader'; import { Card, @@ -40,6 +44,7 @@ import { z } from 'zod'; import { AlertConfirm } from '@/components/AlertConfirm'; import { ROLES } from '@tianji/shared'; import { cn } from '@/utils/style'; +import { Separator } from '@/components/ui/separator'; export const Route = createFileRoute('/settings/workspace')({ beforeLoad: routeAuthBeforeLoad, @@ -63,6 +68,9 @@ function PageComponent() { trpc.workspace.members.useQuery({ workspaceId, }); + const updateCurrentWorkspaceName = useUserStore( + (state) => state.updateCurrentWorkspaceName + ); const form = useForm({ resolver: zodResolver(inviteFormSchema), defaultValues: { @@ -73,12 +81,26 @@ function PageComponent() { onSuccess: defaultSuccessHandler, onError: defaultErrorHandler, }); + const renameWorkspaceMutation = trpc.workspace.rename.useMutation({ + onSuccess: defaultSuccessHandler, + onError: defaultErrorHandler, + }); const deleteWorkspaceMutation = trpc.workspace.delete.useMutation({ onSuccess: defaultSuccessHandler, onError: defaultErrorHandler, }); - const [handleInvite, isLoading] = useEventWithLoading( + const [renameWorkspaceName, setRenameWorkspaceName] = useState(''); + const [handleRename, isRenameLoading] = useEventWithLoading(async () => { + await renameWorkspaceMutation.mutateAsync({ + workspaceId, + name: renameWorkspaceName, + }); + + updateCurrentWorkspaceName(renameWorkspaceName); + }); + + const [handleInvite, isInviteLoading] = useEventWithLoading( async (values: InviteFormValues) => { await inviteMutation.mutateAsync({ workspaceId, @@ -177,7 +199,7 @@ function PageComponent() { + + + + + ; interface UserState { info: UserLoginInfo | null; + updateCurrentWorkspaceName: (name: string) => void; } -export const useUserStore = createWithEqualityFn( - () => ({ +export const useUserStore = createWithEqualityFn()( + immer((set) => ({ info: null, - }), + updateCurrentWorkspaceName: (name) => { + const currentUserInfo = useUserStore.getState().info; + if (!currentUserInfo) { + return; + } + + set((state) => { + for (const workspace of state.info?.workspaces ?? []) { + workspace.workspace.name = name; + } + }); + }, + })), shallow ); diff --git a/src/server/trpc/routers/workspace.ts b/src/server/trpc/routers/workspace.ts index 822b2e8..f63876e 100644 --- a/src/server/trpc/routers/workspace.ts +++ b/src/server/trpc/routers/workspace.ts @@ -12,6 +12,7 @@ import { prisma } from '../../model/_client.js'; import { userInfoSchema, workspaceDashboardLayoutSchema, + workspaceSchema, } from '../../model/_schema/index.js'; import { Prisma } from '@prisma/client'; import { OPENAPI_TAG } from '../../utils/const.js'; @@ -124,6 +125,33 @@ export const workspaceRouter = router({ return userInfo; }), + rename: workspaceOwnerProcedure + .meta( + buildWorkspaceOpenapi({ + method: 'PATCH', + path: '/rename', + }) + ) + .input( + z.object({ + name: z.string().max(60).min(4), + }) + ) + .output(workspaceSchema) + .mutation(async ({ input }) => { + const { workspaceId, name } = input; + + const workspace = await prisma.workspace.update({ + where: { + id: workspaceId, + }, + data: { + name, + }, + }); + + return workspace; + }), delete: workspaceOwnerProcedure .meta( buildWorkspaceOpenapi({