diff --git a/client/src/Hooks/useInviteForm.ts b/client/src/Hooks/useInviteForm.ts
new file mode 100644
index 000000000..664cc96ab
--- /dev/null
+++ b/client/src/Hooks/useInviteForm.ts
@@ -0,0 +1,14 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import { inviteSchema, type InviteFormData } from "@/Validation/invite";
+
+export const useInviteForm = () => {
+ const defaults: InviteFormData = {
+ email: "",
+ role: ["user"],
+ };
+
+ return {
+ resolver: zodResolver(inviteSchema),
+ defaults,
+ };
+};
diff --git a/client/src/Pages/Account/components/HeaderTeamControls.tsx b/client/src/Pages/Account/components/HeaderTeamControls.tsx
index 405e175c0..328deb411 100644
--- a/client/src/Pages/Account/components/HeaderTeamControls.tsx
+++ b/client/src/Pages/Account/components/HeaderTeamControls.tsx
@@ -60,7 +60,7 @@ export const HeaderTeamControls = ({
startIcon={}
onClick={onInviteClick}
>
- {t("pages.account.team.inviteMember")}
+ {t("common.buttons.inviteMember")}
)}
{isSuperAdmin && (
@@ -69,7 +69,7 @@ export const HeaderTeamControls = ({
color="primary"
startIcon={}
>
- {t("pages.account.team.addMember")}
+ {t("common.buttons.addMember")}
)}
diff --git a/client/src/Pages/Account/components/InviteTeamMemberDialog.tsx b/client/src/Pages/Account/components/InviteTeamMemberDialog.tsx
index 5029a5800..24f1f58f9 100644
--- a/client/src/Pages/Account/components/InviteTeamMemberDialog.tsx
+++ b/client/src/Pages/Account/components/InviteTeamMemberDialog.tsx
@@ -1,9 +1,20 @@
import { Stack } from "@mui/material";
import MenuItem from "@mui/material/MenuItem";
-import { useTheme } from "@mui/material";
+import { useTheme, FormHelperText, Typography } from "@mui/material";
+import { useState } from "react";
import { useTranslation } from "react-i18next";
+import { useForm, Controller } from "react-hook-form";
import { Dialog, TextField, Select, Button } from "@/Components/v2/inputs";
import type { UserRole } from "@/Types/User";
+import { useInviteForm } from "@/Hooks/useInviteForm";
+import type { InviteFormData } from "@/Validation/invite";
+import { usePost } from "@/Hooks/UseApi";
+
+const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
+
+interface InviteResponse {
+ token: string;
+}
interface InviteTeamMemberDialogProps {
open: boolean;
@@ -16,29 +27,70 @@ export const InviteTeamMemberDialog = ({
}: InviteTeamMemberDialogProps) => {
const theme = useTheme();
const { t } = useTranslation();
+ const { resolver, defaults } = useInviteForm();
+
+ const { control, handleSubmit, reset } = useForm({
+ resolver,
+ defaultValues: defaults,
+ values: defaults,
+ });
+
+ const { post: generateToken, loading: generateLoading } = usePost<
+ InviteFormData,
+ InviteResponse
+ >();
+ const { post: sendInvite, loading: sendLoading } = usePost<
+ InviteFormData,
+ InviteResponse
+ >();
+ const [inviteLink, setInviteLink] = useState(null);
const roleOptions: { value: UserRole; label: string }[] = [
{ value: "admin", label: t("common.auth.roles.admin") },
{ value: "user", label: t("common.auth.roles.user") },
];
+ const handleGenerateToken = async (data: InviteFormData) => {
+ const result = await generateToken("/invite", data);
+ if (result?.data?.token) {
+ const token = result.data.token;
+ const link = CLIENT_HOST ? `${CLIENT_HOST}/register/${token}` : token;
+ setInviteLink(link);
+ }
+ };
+
+ const handleSendInvite = async (data: InviteFormData) => {
+ const result = await sendInvite("/invite/send", data);
+ if (result?.success) {
+ handleClose();
+ }
+ };
+
+ const handleClose = () => {
+ reset();
+ setInviteLink(null);
+ onClose();
+ };
+
return (
);
diff --git a/client/src/Validation/invite.ts b/client/src/Validation/invite.ts
new file mode 100644
index 000000000..fecbc0158
--- /dev/null
+++ b/client/src/Validation/invite.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+import { UserRoles } from "@/Types/User";
+
+export const inviteSchema = z.object({
+ email: z.email("Please enter a valid email address"),
+ role: z.array(z.enum(UserRoles)).min(1, "Please select a role"),
+});
+
+export type InviteFormData = z.infer;
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 5aba9ab6d..36b89762c 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -167,6 +167,7 @@
"uploadSuccess": "Monitors created successfully!"
},
"bytesSent": "Bytes Sent",
+ "addMember": "Add member",
"cancel": "Cancel",
"checkHooks": {
"failureResolveOne": "Failed to resolve incident."
@@ -195,16 +196,20 @@
"home": "Home"
},
"buttons": {
+ "addMember": "Add member",
"cancel": "Cancel",
"close": "Close",
"configure": "Configure",
"confirm": "Confirm",
"create": "Create",
"delete": "Delete",
+ "generateToken": "Generate token",
"incidents": "Incidents",
+ "inviteMember": "Invite member",
"pause": "Pause",
"resume": "Resume",
"save": "Save",
+ "sendInvite": "Send invite",
"test": "Test",
"testNotifications": "Test notifications",
"toggleTheme": "Toggles light & dark",
@@ -299,6 +304,7 @@
"createMonitor": "Create monitor",
"createNew": "Create new",
"delete": "Delete",
+ "generateToken": "Generate token",
"DeleteAccountButton": "Remove account",
"DeleteAccountTitle": "Remove account",
"DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
@@ -459,6 +465,7 @@
"discussions": "Discussions",
"docs": "Docs",
"incidents": "Incidents",
+ "inviteMember": "Invite member",
"infrastructure": "Infrastructure",
"logOut": "Log out",
"logs": "Logs",
@@ -559,8 +566,6 @@
}
},
"team": {
- "addMember": "Add member",
- "inviteMember": "Invite member",
"filter": {
"placeholder": "Filter by role",
"all": "All roles",
@@ -584,8 +589,7 @@
"label": "Role",
"placeholder": "Select a role"
},
- "sendInvite": "Send invite",
- "generateToken": "Generate token"
+ "linkLabel": "Invite link"
}
}
},
@@ -675,8 +679,10 @@
"actions": {
"configure": "Configure",
"delete": "Delete",
+ "generateToken": "Generate token",
"details": "Details",
"incidents": "Incidents",
+ "inviteMember": "Invite member",
"openSite": "Open site",
"pause": "Pause",
"resume": "Resume"
@@ -1341,6 +1347,7 @@
"teamMember": "Team member"
},
"save": "Save",
+ "sendInvite": "Send invite",
"selectAll": "Select all",
"settingsFailedToClearStats": "Failed to clear stats",
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
@@ -1467,6 +1474,7 @@
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
"title": "Register new team member"
},
+ "addMember": "Add member",
"cancel": "Cancel",
"changeTeamPassword": {
"changePasswordMenu": "Reset Password",