diff --git a/.changeset/gentle-lamps-drop.md b/.changeset/gentle-lamps-drop.md new file mode 100644 index 000000000..4c663efa2 --- /dev/null +++ b/.changeset/gentle-lamps-drop.md @@ -0,0 +1,6 @@ +--- +'@hyperdx/api': patch +'@hyperdx/app': patch +--- + +feat: Allow to update team name diff --git a/packages/api/src/controllers/team.ts b/packages/api/src/controllers/team.ts index 7ef947d43..e25b8a058 100644 --- a/packages/api/src/controllers/team.ts +++ b/packages/api/src/controllers/team.ts @@ -61,6 +61,10 @@ export function rotateTeamApiKey(teamId: ObjectId) { return Team.findByIdAndUpdate(teamId, { apiKey: uuidv4() }, { new: true }); } +export function setTeamName(teamId: ObjectId, name: string) { + return Team.findByIdAndUpdate(teamId, { name }, { new: true }); +} + export async function getTags(teamId: ObjectId) { const [dashboardTags, logViewTags] = await Promise.all([ Dashboard.aggregate([ diff --git a/packages/api/src/routers/api/team.ts b/packages/api/src/routers/api/team.ts index 08b9f3c09..adda9268e 100644 --- a/packages/api/src/routers/api/team.ts +++ b/packages/api/src/routers/api/team.ts @@ -6,7 +6,12 @@ import { z } from 'zod'; import { validateRequest } from 'zod-express-middleware'; import * as config from '@/config'; -import { getTags, getTeam, rotateTeamApiKey } from '@/controllers/team'; +import { + getTags, + getTeam, + rotateTeamApiKey, + setTeamName, +} from '@/controllers/team'; import { deleteTeamMember, findUserByEmail, @@ -78,6 +83,28 @@ router.patch('/apiKey', async (req, res, next) => { } }); +router.patch( + '/name', + validateRequest({ + body: z.object({ + name: z.string().min(1).max(100), + }), + }), + async (req, res, next) => { + try { + const teamId = req.user?.team; + if (teamId == null) { + throw new Error(`User ${req.user?._id} not associated with a team`); + } + const { name } = req.body; + const team = await setTeamName(teamId, name); + res.json({ name: team?.name }); + } catch (e) { + next(e); + } + }, +); + router.post( '/invitation', validateRequest({ diff --git a/packages/app/src/TeamPage.tsx b/packages/app/src/TeamPage.tsx index 36f3f62a1..3e64b702a 100644 --- a/packages/app/src/TeamPage.tsx +++ b/packages/app/src/TeamPage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { FormEventHandler, useCallback, useState } from 'react'; import Head from 'next/head'; import { Button, @@ -166,6 +166,7 @@ export default function TeamPage() { const deleteTeamInvitation = api.useDeleteTeamInvitation(); const saveWebhook = api.useSaveWebhook(); const deleteWebhook = api.useDeleteWebhook(); + const setTeamName = api.useSetTeamName(); const hasAdminAccess = true; @@ -492,13 +493,43 @@ export default function TeamPage() { ], }); + const [isEditingTeamName, setIsEditingTeamName] = useState(false); + const [editingTeamNameValue, setEditingTeamNameValue] = useState(''); + const handleSetTeamName = useCallback>( + e => { + e.stopPropagation(); + e.preventDefault(); + setTeamName.mutate( + { name: editingTeamNameValue }, + { + onError: e => { + notifications.show({ + color: 'red', + message: 'Failed to update team name', + }); + }, + onSuccess: () => { + notifications.show({ + color: 'green', + message: 'Updated team name', + }); + refetchTeam(); + setIsEditingTeamName(false); + setEditingTeamNameValue(team.name); + }, + }, + ); + }, + [editingTeamNameValue, refetchTeam, setTeamName, team?.name], + ); + return (
My Team - HyperDX
-
{team?.name || 'My team'}
+
Team Settings
@@ -509,6 +540,69 @@ export default function TeamPage() { )} {!isLoading && team != null && ( +
Team Name
+ + {isEditingTeamName ? ( +
+ + { + setEditingTeamNameValue(e.target.value); + }} + placeholder="My Team" + miw={300} + required + min={1} + max={100} + autoFocus + onKeyDown={e => { + if (e.key === 'Escape') { + setIsEditingTeamName(false); + } + }} + /> + + Save + + setIsEditingTeamName(false)} + > + Cancel + + +
+ ) : ( + +
{team.name}
+ + } + onClick={() => { + setIsEditingTeamName(true); + setEditingTeamNameValue(team.name); + }} + > + Change + +
+ )} +
+
API Keys
diff --git a/packages/app/src/api.ts b/packages/app/src/api.ts index a405b3a55..266b8a91c 100644 --- a/packages/app/src/api.ts +++ b/packages/app/src/api.ts @@ -777,6 +777,14 @@ const api = { hdxServer(`team/members`).json(), ); }, + useSetTeamName() { + return useMutation(async ({ name }) => + hdxServer(`team/name`, { + method: 'PATCH', + json: { name }, + }).json(), + ); + }, useTags() { return useQuery<{ data: string[] }, HTTPError>(`team/tags`, () => hdxServer(`team/tags`).json<{ data: string[] }>(),