auth
This commit is contained in:
1 parent
c9dea5c0ae
commit
901b5a43c8
20 files changed
+393
-137
No files matched your search
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
"toasts": {
|
||||
"recoveryEmailSent": "If the email exists, a recovery link has been sent",
|
||||
"passwordResetSuccessful": "Password reset successful!"
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
@@ -19,6 +23,14 @@
|
||||
"settings": "Settings"
|
||||
},
|
||||
"buttons": {
|
||||
"acceptInvitation": "Accept invitation",
|
||||
"register": "Register",
|
||||
"submitNewPassword": "Submit new password",
|
||||
"requestRecovery": "Request recovery email",
|
||||
"sendInvite": "Send invite",
|
||||
"testTransport": "Test Transport config",
|
||||
"saveEmailSettings": "Save Email Settings",
|
||||
"saveRetentionSettings": "Save Retention Settings",
|
||||
"incidents": "Incidents",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
@@ -32,7 +44,11 @@
|
||||
"configure": "Configure",
|
||||
"createNew": "Create new",
|
||||
"testNotification": "Send test notification",
|
||||
"updateProfile": "Update profile",
|
||||
"removeAllMonitors": "Remove all monitors",
|
||||
"resolve": "Resolve",
|
||||
"exportJson": "Export to JSON",
|
||||
"importJson": "Import from JSON",
|
||||
"range": {
|
||||
"all": "All",
|
||||
"recent": "Recent",
|
||||
@@ -87,6 +103,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"menu": {
|
||||
"uptime": "Uptime",
|
||||
"pagespeed": "PageSpeed",
|
||||
@@ -96,11 +113,59 @@
|
||||
"checks": "Checks",
|
||||
"statusPages": "Status Pages",
|
||||
"maintenance": "Maintenance",
|
||||
"diagnostics": "Diagnostics",
|
||||
"support": "Support",
|
||||
"discussions": "Discussions",
|
||||
"docs": "Documentation",
|
||||
"changelog": "Changelog"
|
||||
},
|
||||
"auth": {
|
||||
"register": {
|
||||
"header": {
|
||||
"title": "Welcome to Checkmate!",
|
||||
"description": "Create an account to get started"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"header": {
|
||||
"title": "Welcome back to Checkmate",
|
||||
"description": "Log in to continue"
|
||||
},
|
||||
"links": {
|
||||
"forgotPassword": {
|
||||
"text": "Forgot password?",
|
||||
"linkText": "Reset password"
|
||||
},
|
||||
"register": {
|
||||
"text": "Don't have an account?",
|
||||
"linkText": "Register"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recovery": {
|
||||
"header": {
|
||||
"title": "Enter your email to recover your account"
|
||||
}
|
||||
},
|
||||
"reset": {
|
||||
"header": {
|
||||
"title": "Set a new password"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"form": {
|
||||
"optionEmail": "Email",
|
||||
"optionEmailPlaceholder": "test@test.com",
|
||||
"optionFirstName": "First Name",
|
||||
"optionFirstNamePlaceholder": "Your first Name",
|
||||
"optionLastName": "Last Name",
|
||||
"optionLastNamePlaceholder": "Your last Name",
|
||||
"optionPassword": "Password",
|
||||
"optionConfirmPassword": "Confirm Password",
|
||||
"optionConfirmPasswordPlaceholder": "Re-enter password to confirm"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"monitors": {
|
||||
"common": {
|
||||
@@ -478,5 +543,145 @@
|
||||
],
|
||||
"title": "A maintenance window is used to:"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"form": {
|
||||
"exportMonitors": {
|
||||
"title": "Export current team's monitors",
|
||||
"description": "This will only export the basic monitor settings, not the check history, incidents history, or notifications",
|
||||
"optionExport": "Click here to export your monitors to JSON"
|
||||
},
|
||||
"importMonitors": {
|
||||
"title": "Import monitors",
|
||||
"description": "Import monitors from a JSON file. Sample:",
|
||||
"optionImport": "Click here to import your monitors from JSON"
|
||||
},
|
||||
"exportIncidents": {
|
||||
"title": "Export incidents",
|
||||
"description": "Export current team's incidents as JSON",
|
||||
"optionExport": "Click here to export your incidents to JSON"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"infoBox": {
|
||||
"title": "View and edit your profile",
|
||||
"description": "You can set your name and password here"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"form": {
|
||||
"ui": {
|
||||
"title": "UI Settings",
|
||||
"description": "Configure time zone, UI mode, and language preferences",
|
||||
"optionTimezone": "Time zone",
|
||||
"optionUIMode": "UI Mode",
|
||||
"valueUIModeLight": "Light",
|
||||
"valueUIModeDark": "Dark",
|
||||
"optionLanguage": "Language"
|
||||
},
|
||||
"chart": {
|
||||
"title": "Chart settings",
|
||||
"description": "Choose what type of Uptime chart you want to see on the dashboard and status page",
|
||||
"optionChartType": "Chart Type",
|
||||
"valueChartTypeHeatmap": "Heatmap",
|
||||
"descriptionChartTypeHeatmap": "Compact tiles; color shows response speed, top strip shows availability.",
|
||||
"valueChartTypeHistogram": "Histogram",
|
||||
"descriptionChartTypeHistogram": "One bar per check; height shows response time, color shows status."
|
||||
},
|
||||
"monitors": {
|
||||
"title": "Monitors",
|
||||
"description": "Monitor related settings",
|
||||
"descriptionRemoveAllMonitors": "Remove all monitors from your organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"adminSettings": {
|
||||
"form": {
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "Configure the email settings for your system. This is used to send notifications and alerts.",
|
||||
"descriptionTransport": "This builds an SMTP transport for NodeMailer",
|
||||
"linkTransport": "See specifications here",
|
||||
"optionHost": "Email host - Hostname or IP address to connect to",
|
||||
"optionPort": "Email port - Port to connect to",
|
||||
"optionUser": "Email user - Username for authentication, overrides email address if specified",
|
||||
"optionAddress": "Email address - Used for authentication",
|
||||
"optionPassword": "Email password - Password for authentication",
|
||||
"optionTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP",
|
||||
"optionConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting",
|
||||
"optionSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS",
|
||||
"optionPool": "Enable connection pooling: Reuse existing connections to improve performance",
|
||||
"optionIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it",
|
||||
"optionRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported",
|
||||
"optionRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates"
|
||||
},
|
||||
"retention": {
|
||||
"title": "Global data retention settings",
|
||||
"description": "Set data retention policy. This affects all organizations and teams",
|
||||
"optionRetentionDays": "Days to retain data"
|
||||
}
|
||||
}
|
||||
},
|
||||
"teams": {
|
||||
"infoBox": {
|
||||
"title": "Team Collaboration",
|
||||
"description": "Organize your monitoring workspace by creating teams. Control who can view and manage monitors, set up role-based permissions, and collaborate effectively."
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"teamDescription": "Team description"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"infoHeader": {
|
||||
"name": "Name",
|
||||
"description": "Description"
|
||||
},
|
||||
"table": {
|
||||
"title": "Team members",
|
||||
"headers": {
|
||||
"role": "Role",
|
||||
"permissions": "Permissions"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"teamMember": {
|
||||
"breadcrumbOverrideConfigTeams": "Teams",
|
||||
"breadcrumbOverrideConfigTeamMember": "Configure team member",
|
||||
"breadcrumbOverrideCreateTeamMember": "Create team member",
|
||||
"form": {
|
||||
"teamMember": {
|
||||
"title": "Team member",
|
||||
"description": "Team member selection",
|
||||
"optionTeamMember": "Team member"
|
||||
},
|
||||
"role": {
|
||||
"title": "Role",
|
||||
"description": "Choose your role for this new team. You will be the first member",
|
||||
"optionRole": "Role",
|
||||
"optionIncludedPermissions": "Included permissions"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invite": {
|
||||
"form": {
|
||||
"required": {
|
||||
"title": "Required",
|
||||
"description": "These fields are required",
|
||||
"optionTeam": "Team",
|
||||
"optionTeamRole": "Team role",
|
||||
"optionPermissions": "Permissions"
|
||||
},
|
||||
"optional": {
|
||||
"title": "Optional",
|
||||
"description": "You can also assign an organization role",
|
||||
"optionOrganizationRole": "Organization role"
|
||||
},
|
||||
"inviteLink": {
|
||||
"title": "Invite link"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,8 +71,8 @@ const Invite = () => {
|
||||
return (
|
||||
<BasePage component="form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<ConfigBox
|
||||
title="Required"
|
||||
subtitle="These fields are required"
|
||||
title={t("invite.form.required.title")}
|
||||
subtitle={t("invite.form.required.description")}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)} maxWidth={400}>
|
||||
<Controller
|
||||
@@ -82,9 +82,9 @@ const Invite = () => {
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.email.label")}
|
||||
fieldLabel={t("auth.common.form.optionEmail")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.email.placeholder")}
|
||||
placeholder={t("auth.common.form.optionEmailPlaceholder")}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email ? errors.email.message : ""}
|
||||
/>
|
||||
@@ -98,7 +98,7 @@ const Invite = () => {
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
<Select
|
||||
value={field.value}
|
||||
fieldLabel="Team"
|
||||
fieldLabel={t("invite.form.required.optionTeam")}
|
||||
error={!!errors.teamId}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
@@ -125,7 +125,7 @@ const Invite = () => {
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
<Select
|
||||
value={field.value}
|
||||
fieldLabel="Team role"
|
||||
fieldLabel={t("invite.form.required.optionTeamRole")}
|
||||
error={!!errors.teamRoleId}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
@@ -139,7 +139,8 @@ const Invite = () => {
|
||||
</Select>
|
||||
<Stack>
|
||||
<Typography mb={theme.spacing(4)}>
|
||||
{currentRole && `Permissions:`}
|
||||
{currentRole &&
|
||||
`${t("invite.form.required.optionPermissions")}:`}
|
||||
</Typography>
|
||||
{currentRole?.permissions?.map((perm: string) => (
|
||||
<Typography variant="body2" key={perm}>
|
||||
@@ -156,8 +157,8 @@ const Invite = () => {
|
||||
/>
|
||||
|
||||
<ConfigBox
|
||||
title="Optional"
|
||||
subtitle="You can also assign an organization role"
|
||||
title={t("invite.form.optional.title")}
|
||||
subtitle={t("invite.form.optional.description")}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)} maxWidth={400}>
|
||||
<Controller
|
||||
@@ -171,7 +172,9 @@ const Invite = () => {
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
<Select
|
||||
value={field.value}
|
||||
fieldLabel="Organization role"
|
||||
fieldLabel={t(
|
||||
"invite.form.optional.optionOrganizationRole"
|
||||
)}
|
||||
error={!!errors.orgRoleId}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
@@ -202,7 +205,7 @@ const Invite = () => {
|
||||
/>
|
||||
{token && (
|
||||
<Stack>
|
||||
<Typography variant="h2">Invite link:</Typography>
|
||||
<Typography variant="h2">{`${t("invite.form.inviteLink.title")}:`}</Typography>
|
||||
<Typography>{`${HOST}/register/${token}`}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -213,7 +216,7 @@ const Invite = () => {
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
Send Invite
|
||||
{t("common.buttons.sendInvite")}
|
||||
</Button>
|
||||
</Box>
|
||||
</BasePage>
|
||||
|
||||
@@ -72,8 +72,8 @@ const Login = () => {
|
||||
|
||||
return (
|
||||
<AuthBasePage
|
||||
title={t("auth.login.welcome")}
|
||||
subtitle={t("auth.login.heading")}
|
||||
title={t("auth.login.header.title")}
|
||||
subtitle={t("auth.login.header.description")}
|
||||
>
|
||||
<Stack
|
||||
width={"100%"}
|
||||
@@ -103,9 +103,9 @@ const Login = () => {
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.email.label")}
|
||||
fieldLabel={t("auth.common.form.optionEmail")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.email.placeholder")}
|
||||
placeholder={t("auth.common.form.optionEmailPlaceholder")}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email ? errors.email.message : ""}
|
||||
/>
|
||||
@@ -119,7 +119,7 @@ const Login = () => {
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.password.label")}
|
||||
fieldLabel={t("auth.common.form.optionPassword")}
|
||||
fullWidth
|
||||
placeholder="••••••••••"
|
||||
error={!!errors.password}
|
||||
@@ -138,13 +138,13 @@ const Login = () => {
|
||||
</Button>
|
||||
</Stack>
|
||||
<TextLink
|
||||
text={t("auth.login.links.forgotPassword")}
|
||||
linkText={t("auth.login.links.forgotPasswordLink")}
|
||||
text={t("auth.login.links.forgotPassword.text")}
|
||||
linkText={t("auth.login.links.forgotPassword.linkText")}
|
||||
href="/recovery"
|
||||
/>
|
||||
<TextLink
|
||||
text={t("auth.login.links.register")}
|
||||
linkText={t("auth.login.links.registerLink")}
|
||||
text={t("auth.login.links.register.text")}
|
||||
linkText={t("auth.login.links.register.linkText")}
|
||||
href="/register"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -52,8 +52,8 @@ const Register = () => {
|
||||
|
||||
return (
|
||||
<AuthBasePage
|
||||
title={t("auth.registration.welcome")}
|
||||
subtitle={t("auth.registration.heading.user")}
|
||||
title={t("auth.register.header.title")}
|
||||
subtitle={t("auth.register.header.description")}
|
||||
>
|
||||
<Stack alignItems={"center"} width={"100%"}>
|
||||
<RegisterForm onSubmit={onSubmit} loading={loading} error={error} />
|
||||
|
||||
@@ -88,9 +88,9 @@ export const RegisterForm = ({
|
||||
<TextInput
|
||||
disabled={mode === "invite"}
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.email.label")}
|
||||
fieldLabel={t("auth.common.form.optionEmail")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.email.placeholder")}
|
||||
placeholder={t("auth.common.form.optionEmailPlaceholder")}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email ? errors.email.message : ""}
|
||||
/>
|
||||
@@ -104,9 +104,9 @@ export const RegisterForm = ({
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.firstName.label")}
|
||||
fieldLabel={t("auth.common.form.optionFirstName")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.firstName.placeholder")}
|
||||
placeholder={t("auth.common.form.optionFirstNamePlaceholder")}
|
||||
error={!!errors.firstName}
|
||||
helperText={errors.firstName ? errors.firstName.message : ""}
|
||||
/>
|
||||
@@ -120,9 +120,9 @@ export const RegisterForm = ({
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.lastName.label")}
|
||||
fieldLabel={t("auth.common.form.optionLastName")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.lastName.placeholder")}
|
||||
placeholder={t("auth.common.form.optionLastNamePlaceholder")}
|
||||
error={!!errors.lastName}
|
||||
helperText={errors.lastName ? errors.lastName.message : ""}
|
||||
/>
|
||||
@@ -136,7 +136,7 @@ export const RegisterForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.password.label")}
|
||||
fieldLabel={t("auth.common.form.optionPassword")}
|
||||
fullWidth
|
||||
placeholder="••••••••••"
|
||||
error={!!errors.password}
|
||||
@@ -152,9 +152,9 @@ export const RegisterForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.passwordConfirm.label")}
|
||||
fieldLabel={t("auth.common.form.optionConfirmPassword")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
|
||||
placeholder={t("auth.common.form.optionConfirmPasswordPlaceholder")}
|
||||
error={!!errors.confirmPassword}
|
||||
helperText={
|
||||
errors.confirmPassword ? errors.confirmPassword.message : ""
|
||||
@@ -169,7 +169,9 @@ export const RegisterForm = ({
|
||||
type="submit"
|
||||
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
|
||||
>
|
||||
{mode === "invite" ? "Accept invitation" : "Register"}
|
||||
{mode === "invite"
|
||||
? t("common.buttons.acceptInvitation")
|
||||
: t("common.buttons.register")}
|
||||
</Button>
|
||||
{error && <Typography color="error">{error}</Typography>}
|
||||
</Stack>
|
||||
|
||||
@@ -67,8 +67,8 @@ const RegisterInvite = () => {
|
||||
|
||||
return (
|
||||
<AuthBasePage
|
||||
title={t("auth.registration.welcome")}
|
||||
subtitle={t("auth.registration.heading.user")}
|
||||
title={t("auth.register.header.title")}
|
||||
subtitle={t("auth.register.header.description")}
|
||||
>
|
||||
<Stack alignItems={"center"} width={"100%"}>
|
||||
<RegisterForm
|
||||
|
||||
@@ -6,10 +6,11 @@ import { Typography } from "@mui/material";
|
||||
import { useGetOnDemand, usePost } from "@/hooks/UseApi";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
const ExportPage = () => {
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { get, loading } = useGetOnDemand();
|
||||
const { post, loading: isPosting } = usePost();
|
||||
@@ -67,11 +68,13 @@ const ExportPage = () => {
|
||||
return (
|
||||
<BasePage>
|
||||
<ConfigBox
|
||||
title="Export current team's monitors"
|
||||
subtitle="This will only export the basic monitor settings, not the check history, incidents history, or notifications"
|
||||
title={t("export.form.exportMonitors.title")}
|
||||
subtitle={t("export.form.exportMonitors.description")}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Typography>Click here to export your monitors to JSON</Typography>
|
||||
<Typography>
|
||||
{t("export.form.exportMonitors.optionExport")}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
loading={loading}
|
||||
@@ -79,15 +82,15 @@ const ExportPage = () => {
|
||||
color="primary"
|
||||
onClick={() => exportJson("/monitors/export", "monitors")}
|
||||
>
|
||||
Export to JSON
|
||||
{t("common.buttons.exportJson")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<ConfigBox
|
||||
title="Import monitors"
|
||||
subtitle="Import monitors from a JSON file. Sample:"
|
||||
title={t("export.form.importMonitors.title")}
|
||||
subtitle={t("export.form.importMonitors.description")}
|
||||
leftContent={
|
||||
<Box
|
||||
component={"pre"}
|
||||
@@ -125,7 +128,7 @@ const ExportPage = () => {
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Typography>
|
||||
Click here to import your monitors from JSON
|
||||
{t("export.form.importMonitors.optionImport")}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
@@ -134,7 +137,7 @@ const ExportPage = () => {
|
||||
color="primary"
|
||||
onClick={handleImport}
|
||||
>
|
||||
Import from JSON
|
||||
{t("common.buttons.importJson")}
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
@@ -148,11 +151,13 @@ const ExportPage = () => {
|
||||
/>
|
||||
|
||||
<ConfigBox
|
||||
title="Export incidents"
|
||||
subtitle="Export current team's incidents as JSON"
|
||||
title={t("export.form.exportIncidents.title")}
|
||||
subtitle={t("export.form.exportIncidents.description")}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Typography>Click here to export your incidents to JSON</Typography>
|
||||
<Typography>
|
||||
{t("export.form.exportIncidents.optionExport")}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
loading={loading}
|
||||
@@ -160,7 +165,7 @@ const ExportPage = () => {
|
||||
color="primary"
|
||||
onClick={() => exportJson("/incidents/export", "incidents")}
|
||||
>
|
||||
Export to JSON
|
||||
{t("common.buttons.exportJson")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -7,9 +7,11 @@ import type { ApiResponse } from "@/hooks/UseApi";
|
||||
import { useGet, usePatch } from "@/hooks/UseApi";
|
||||
import { z } from "zod";
|
||||
import { profileSchema } from "@/validation/zod";
|
||||
import { useTranslation } from "react-i18next";
|
||||
type FormValues = z.infer<typeof profileSchema>;
|
||||
|
||||
const Profile = () => {
|
||||
const { t } = useTranslation();
|
||||
const [user, setUser] = useState<Partial<IUser> | null>(null);
|
||||
const { response, loading } = useGet<ApiResponse<any>>(`/profile`, {}, {});
|
||||
useEffect(() => {
|
||||
@@ -32,8 +34,8 @@ const Profile = () => {
|
||||
return (
|
||||
<BasePage>
|
||||
<InfoBox
|
||||
title="View and edit your profile"
|
||||
description="You can set your name and password here"
|
||||
title={t("profile.infoBox.title")}
|
||||
description={t("profile.infoBox.description")}
|
||||
/>
|
||||
<ProfileForm
|
||||
user={user}
|
||||
|
||||
@@ -71,9 +71,9 @@ export const ProfileForm = ({
|
||||
<TextInput
|
||||
value={user?.email || ""}
|
||||
disabled
|
||||
fieldLabel={t("auth.common.inputs.email.label")}
|
||||
fieldLabel={t("auth.common.form.optionEmail")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.email.placeholder")}
|
||||
placeholder={t("auth.common.form.optionEmailPlaceholder")}
|
||||
/>
|
||||
<Controller
|
||||
name="firstName"
|
||||
@@ -82,9 +82,9 @@ export const ProfileForm = ({
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.firstName.label")}
|
||||
fieldLabel={t("auth.common.form.optionFirstName")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.firstName.placeholder")}
|
||||
placeholder={t("auth.common.form.optionFirstNamePlaceholder")}
|
||||
error={!!errors.firstName}
|
||||
helperText={errors.firstName ? errors.firstName.message : ""}
|
||||
/>
|
||||
@@ -97,9 +97,9 @@ export const ProfileForm = ({
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.lastName.label")}
|
||||
fieldLabel={t("auth.common.form.optionLastName")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.lastName.placeholder")}
|
||||
placeholder={t("auth.common.form.optionLastNamePlaceholder")}
|
||||
error={!!errors.lastName}
|
||||
helperText={errors.lastName ? errors.lastName.message : ""}
|
||||
/>
|
||||
@@ -113,7 +113,7 @@ export const ProfileForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.password.label")}
|
||||
fieldLabel={t("auth.common.form.optionPassword")}
|
||||
fullWidth
|
||||
error={!!errors.password}
|
||||
helperText={errors.password ? errors.password.message : ""}
|
||||
@@ -128,7 +128,7 @@ export const ProfileForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.passwordConfirm.label")}
|
||||
fieldLabel={t("auth.common.form.optionConfirmPassword")}
|
||||
fullWidth
|
||||
error={!!errors.confirmPassword}
|
||||
helperText={
|
||||
@@ -144,7 +144,7 @@ export const ProfileForm = ({
|
||||
type="submit"
|
||||
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
|
||||
>
|
||||
Update profile
|
||||
{t("common.buttons.updateProfile")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AuthBasePage } from "@/components/auth";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RecoveryForm } from "./RecoveryForm";
|
||||
import { recoverySchema } from "@/validation/zod";
|
||||
import { z } from "zod";
|
||||
@@ -9,16 +10,17 @@ import { useToast } from "@/hooks/UseToast";
|
||||
type FormData = z.infer<typeof recoverySchema>;
|
||||
|
||||
const Recovery = () => {
|
||||
const { t } = useTranslation();
|
||||
const { post, loading } = usePost<FormData>();
|
||||
const { showToast } = useToast();
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
await post("/recovery", data);
|
||||
showToast("If the email exists, a recovery link has been sent");
|
||||
showToast(t("common.toasts.recoveryEmailSent"));
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthBasePage title="Enter your email to recover your account">
|
||||
<AuthBasePage title={t("auth.recovery.header.title")}>
|
||||
<RecoveryForm onSubmit={onSubmit} loading={loading} />
|
||||
</AuthBasePage>
|
||||
);
|
||||
|
||||
@@ -66,9 +66,9 @@ export const RecoveryForm = ({
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
fieldLabel={t("auth.common.inputs.email.label")}
|
||||
fieldLabel={t("auth.common.form.optionEmail")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.email.placeholder")}
|
||||
placeholder={t("auth.common.form.optionEmailPlaceholder")}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email ? errors.email.message : ""}
|
||||
/>
|
||||
@@ -82,7 +82,7 @@ export const RecoveryForm = ({
|
||||
type="submit"
|
||||
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
|
||||
>
|
||||
Request recovery email
|
||||
{t("common.buttons.requestRecovery")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AuthBasePage } from "@/components/auth";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router";
|
||||
import { ResetForm } from "@/pages/recovery/ResetForm";
|
||||
import { resetSchema } from "@/validation/zod";
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
type FormData = z.infer<typeof resetSchema>;
|
||||
|
||||
const Reset = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { post, loading } = usePost<{ password: string; token: string }>();
|
||||
@@ -37,7 +39,7 @@ const Reset = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
toastSuccess("Password reset successful!");
|
||||
toastSuccess(t("common.toasts.passwordResetSuccessful"));
|
||||
dispatch(setAuthenticated(true));
|
||||
dispatch(setUser(user));
|
||||
dispatch(setSelectedTeamId(user.teams[0]?.id || null));
|
||||
@@ -45,7 +47,7 @@ const Reset = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthBasePage title="Set a new password">
|
||||
<AuthBasePage title={t("auth.reset.header.title")}>
|
||||
<ResetForm onSubmit={onSubmit} loading={loading} />
|
||||
</AuthBasePage>
|
||||
);
|
||||
|
||||
@@ -69,7 +69,7 @@ export const ResetForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.password.label")}
|
||||
fieldLabel={t("auth.common.form.optionPassword")}
|
||||
fullWidth
|
||||
placeholder="••••••••••"
|
||||
error={!!errors.password}
|
||||
@@ -85,9 +85,9 @@ export const ResetForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("auth.common.inputs.passwordConfirm.label")}
|
||||
fieldLabel={t("auth.common.form.optionConfirmPassword")}
|
||||
fullWidth
|
||||
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
|
||||
placeholder={t("auth.common.form.optionConfirmPasswordPlaceholder")}
|
||||
error={!!errors.confirmPassword}
|
||||
helperText={
|
||||
errors.confirmPassword ? errors.confirmPassword.message : ""
|
||||
@@ -102,7 +102,7 @@ export const ResetForm = ({
|
||||
type="submit"
|
||||
sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }}
|
||||
>
|
||||
Submit new password
|
||||
{t("common.buttons.submitNewPassword")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -61,12 +61,12 @@ const SettingsPage = () => {
|
||||
return (
|
||||
<BasePage>
|
||||
<ConfigBox
|
||||
title="UI Settings"
|
||||
subtitle="Configure time zone, UI mode, and language preferences"
|
||||
title={t("settings.form.ui.title")}
|
||||
subtitle={t("settings.form.ui.description")}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Stack spacing={theme.spacing(2)}>
|
||||
<Typography>Time zone</Typography>
|
||||
<Typography>{t("settings.form.ui.optionTimezone")}</Typography>
|
||||
|
||||
<AutoComplete
|
||||
value={selectedTimezone}
|
||||
@@ -78,47 +78,55 @@ const SettingsPage = () => {
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing={theme.spacing(2)}>
|
||||
<Typography>UI Mode</Typography>
|
||||
<Typography>{t("settings.form.ui.optionUIMode")}</Typography>
|
||||
<Select
|
||||
value={mode}
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
dispatch(setMode(e.target.value));
|
||||
}}
|
||||
>
|
||||
<MenuItem value="light">Light</MenuItem>
|
||||
<MenuItem value="dark">Dark</MenuItem>
|
||||
<MenuItem value="light">
|
||||
{t("settings.form.ui.valueUIModeLight")}
|
||||
</MenuItem>
|
||||
<MenuItem value="dark">
|
||||
{t("settings.form.ui.valueUIModeDark")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={theme.spacing(2)}>
|
||||
<Typography>Language</Typography>
|
||||
<Typography>{t("settings.form.ui.optionLanguage")}</Typography>
|
||||
<LanguageSelector />
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<ConfigBox
|
||||
title="Chart settings"
|
||||
subtitle="Choose what type of Uptime chart you want to see on the dashboard and status page"
|
||||
title={t("settings.form.chart.title")}
|
||||
subtitle={t("settings.form.chart.description")}
|
||||
leftContent={<DummyChart type={chartType} />}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Typography>Chart Type</Typography>
|
||||
<Typography>{t("settings.form.chart.optionChartType")}</Typography>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<RadioWithDescription
|
||||
checked={chartType === "heatmap"}
|
||||
onChange={() => dispatch(setChartType("heatmap"))}
|
||||
value="heatmap"
|
||||
label="Heatmap"
|
||||
description="Compact tiles; color shows response speed, top strip shows availability."
|
||||
label={t("settings.form.chart.valueChartTypeHeatmap")}
|
||||
description={t(
|
||||
"settings.form.chart.descriptionChartTypeHeatmap"
|
||||
)}
|
||||
name="chart-type"
|
||||
/>
|
||||
<RadioWithDescription
|
||||
checked={chartType === "histogram"}
|
||||
onChange={() => dispatch(setChartType("histogram"))}
|
||||
value="histogram"
|
||||
label="Histogram"
|
||||
description="One bar per check; height shows response time, color shows status."
|
||||
label={t("settings.form.chart.valueChartTypeHistogram")}
|
||||
description={t(
|
||||
"settings.form.chart.descriptionChartTypeHistogram"
|
||||
)}
|
||||
name="chart-type"
|
||||
/>
|
||||
</Stack>
|
||||
@@ -127,17 +135,17 @@ const SettingsPage = () => {
|
||||
/>
|
||||
{hasDelete && (
|
||||
<ConfigBox
|
||||
title="Monitors"
|
||||
subtitle="Monitor related settings"
|
||||
title={t("settings.form.monitors.title")}
|
||||
subtitle={t("settings.form.monitors.description")}
|
||||
rightContent={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Box>
|
||||
<Button variant="contained" color="error" onClick={handleOpen}>
|
||||
Remove all monitors
|
||||
{t("common.buttons.removeAllMonitors")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography>
|
||||
Removes all monitors from your organization.
|
||||
{t("settings.form.monitors.descriptionRemoveAllMonitors")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ export const SettingsForm = ({
|
||||
onSubmit={handleSubmit(onSubmitEmail)}
|
||||
>
|
||||
<ConfigBox
|
||||
title={t("settingsPage.emailSettings.title")}
|
||||
subtitle={t("settingsPage.emailSettings.description")}
|
||||
title={t("adminSettings.form.email.title")}
|
||||
subtitle={t("adminSettings.form.email.description")}
|
||||
leftContent={
|
||||
<Stack>
|
||||
<TextLink
|
||||
text={t("settingsPage.emailSettings.descriptionTransport")}
|
||||
linkText={t("settingsPage.emailSettings.linkTransport")}
|
||||
text={t("adminSettings.form.email.descriptionTransport")}
|
||||
linkText={t("adminSettings.form.email.linkTransport")}
|
||||
href="https://nodemailer.com/smtp"
|
||||
target="_blank"
|
||||
/>
|
||||
@@ -114,7 +114,7 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelHost")}
|
||||
fieldLabel={t("adminSettings.form.email.optionHost")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailHost}
|
||||
helperText={
|
||||
@@ -132,7 +132,7 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="number"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPort")}
|
||||
fieldLabel={t("adminSettings.form.email.optionPort")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPort}
|
||||
helperText={
|
||||
@@ -150,7 +150,7 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelUser")}
|
||||
fieldLabel={t("adminSettings.form.email.optionUser")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailUser}
|
||||
helperText={
|
||||
@@ -168,7 +168,7 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelAddress")}
|
||||
fieldLabel={t("adminSettings.form.email.optionAddress")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailAddress}
|
||||
helperText={
|
||||
@@ -186,7 +186,7 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPassword")}
|
||||
fieldLabel={t("adminSettings.form.email.optionPassword")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPassword}
|
||||
helperText={
|
||||
@@ -205,7 +205,7 @@ export const SettingsForm = ({
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelTLSServername"
|
||||
"adminSettings.form.email.optionTLSServername"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailTLSServername}
|
||||
@@ -225,7 +225,7 @@ export const SettingsForm = ({
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelConnectionHost"
|
||||
"adminSettings.form.email.optionConnectionHost"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailConnectionHost}
|
||||
@@ -244,7 +244,7 @@ export const SettingsForm = ({
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelSecure")}
|
||||
{t("adminSettings.form.email.optionSecure")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -257,7 +257,7 @@ export const SettingsForm = ({
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelPool")}
|
||||
{t("adminSettings.form.email.optionPool")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -270,7 +270,7 @@ export const SettingsForm = ({
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelIgnoreTLS")}
|
||||
{t("adminSettings.form.email.optionIgnoreTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -283,7 +283,7 @@ export const SettingsForm = ({
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRequireTLS")}
|
||||
{t("adminSettings.form.email.optionRequireTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -296,7 +296,7 @@ export const SettingsForm = ({
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRejectUnauthorized")}
|
||||
{t("adminSettings.form.email.optionRejectUnauthorized")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -314,7 +314,7 @@ export const SettingsForm = ({
|
||||
onTest(formValues);
|
||||
}}
|
||||
>
|
||||
Test transport config
|
||||
{t("common.buttons.testTransport")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
@@ -322,7 +322,7 @@ export const SettingsForm = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save e-mail settings
|
||||
{t("common.buttons.saveEmailSettings")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -335,8 +335,8 @@ export const SettingsForm = ({
|
||||
spacing={theme.spacing(10)}
|
||||
>
|
||||
<ConfigBox
|
||||
title="Global Data retention"
|
||||
subtitle="Set data retention policy. This affects all organizations and teams"
|
||||
title={t("adminSettings.form.retention.title")}
|
||||
subtitle={t("adminSettings.form.retention.description")}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Controller
|
||||
@@ -346,7 +346,9 @@ export const SettingsForm = ({
|
||||
<TextInput
|
||||
{...field}
|
||||
type="number"
|
||||
fieldLabel="Retention days"
|
||||
fieldLabel={t(
|
||||
"adminSettings.form.retention.optionRetentionDays"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.checksRetentionDays}
|
||||
helperText={
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Table } from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import type { Header } from "@/components/design-elements/Table";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useParams } from "react-router";
|
||||
@@ -13,6 +14,7 @@ import type { ApiResponse } from "@/hooks/UseApi";
|
||||
import type { ITeamMember } from "@/types/team-member";
|
||||
|
||||
const TeamDetailsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
@@ -29,7 +31,7 @@ const TeamDetailsPage = () => {
|
||||
const headers: Header<ITeamMember>[] = [
|
||||
{
|
||||
id: "name",
|
||||
content: "Name",
|
||||
content: t("common.table.headers.name"),
|
||||
render: (row) => {
|
||||
return row?.userId?.firstName + " " + row?.userId?.lastName;
|
||||
},
|
||||
@@ -37,14 +39,14 @@ const TeamDetailsPage = () => {
|
||||
|
||||
{
|
||||
id: "role",
|
||||
content: "Role",
|
||||
content: t("teams.details.table.headers.role"),
|
||||
render: (row) => {
|
||||
return row?.roleId?.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "permissions",
|
||||
content: "Permissions",
|
||||
content: t("teams.details.table.headers.permissions"),
|
||||
render: (row) => {
|
||||
return (
|
||||
<Stack>
|
||||
@@ -65,11 +67,15 @@ const TeamDetailsPage = () => {
|
||||
<BasePage>
|
||||
<Stack>
|
||||
<Stack alignItems="baseline" direction="row" gap={theme.spacing(2)}>
|
||||
<Typography variant="h2">{"Name:"} </Typography>
|
||||
<Typography variant="h2">
|
||||
{`${t("teams.details.infoHeader.name")}:`}{" "}
|
||||
</Typography>
|
||||
<Typography>{team?.name}</Typography>
|
||||
</Stack>
|
||||
<Stack alignItems="baseline" direction="row" gap={theme.spacing(2)}>
|
||||
<Typography variant="h2">{"Description:"} </Typography>
|
||||
<Typography variant="h2">
|
||||
{`${t("teams.details.infoHeader.description")}:`}{" "}
|
||||
</Typography>
|
||||
<Typography>{team?.description}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -81,7 +87,7 @@ const TeamDetailsPage = () => {
|
||||
entitlementCount={teamMembers?.length || 0}
|
||||
/>
|
||||
<Typography variant="h2" marginTop={theme.spacing(4)}>
|
||||
Team Members
|
||||
{t("teams.details.table.title")}
|
||||
</Typography>
|
||||
<Table
|
||||
headers={headers}
|
||||
|
||||
@@ -67,15 +67,19 @@ const TeamMemberConfig = () => {
|
||||
onClick={() => setDialogOpen(true)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{t("delete")}
|
||||
{t("common.buttons.delete")}
|
||||
</Button>
|
||||
}
|
||||
breadcrumbOverride={["teams", id || "", "configure team member"]}
|
||||
breadcrumbOverride={[
|
||||
t("teamMember.breadcrumbOverrideConfigTeams"),
|
||||
id || "",
|
||||
t("teamMember.breadcrumbOverrideConfigTeamMember"),
|
||||
]}
|
||||
/>
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
title={"Are you sure?"}
|
||||
content={"Delete team member"}
|
||||
title={t("common.dialog.delete.title")}
|
||||
content={t("common.dialog.delete.description")}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
loading={isDeleting}
|
||||
|
||||
@@ -4,8 +4,10 @@ import { useParams } from "react-router";
|
||||
import { useGet, usePost } from "@/hooks/UseApi";
|
||||
import type { ApiResponse } from "@/hooks/UseApi";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { mutate } from "swr";
|
||||
const TeamMemberCreate = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { response } = useGet<ApiResponse<any>>("/team-members?type=org");
|
||||
@@ -34,7 +36,11 @@ const TeamMemberCreate = () => {
|
||||
roles={roles}
|
||||
onSubmit={onSubmit}
|
||||
loading={false}
|
||||
breadcrumbOverride={["teams", id || "", "create team member"]}
|
||||
breadcrumbOverride={[
|
||||
t("teamMember.breadcrumbOverrideConfigTeams"),
|
||||
id || "",
|
||||
t("teamMember.breadcrumbOverrideCreateTeamMember"),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -61,8 +61,8 @@ export const TeamMemberForm = ({
|
||||
breadcrumbOverride={breadcrumbOverride}
|
||||
>
|
||||
<ConfigBox
|
||||
title="Team member"
|
||||
subtitle="Team member selection"
|
||||
title={t("teamMember.form.teamMember.title")}
|
||||
subtitle={t("teamMember.form.teamMember.description")}
|
||||
rightContent={
|
||||
<Controller
|
||||
name="userId"
|
||||
@@ -73,7 +73,9 @@ export const TeamMemberForm = ({
|
||||
<Select
|
||||
disabled={mode !== "create"}
|
||||
value={field.value}
|
||||
fieldLabel="Team member"
|
||||
fieldLabel={t(
|
||||
"teamMember.form.teamMember.optionTeamMember"
|
||||
)}
|
||||
error={!!errors.userId}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
@@ -92,8 +94,8 @@ export const TeamMemberForm = ({
|
||||
}
|
||||
/>
|
||||
<ConfigBox
|
||||
title="Role"
|
||||
subtitle="Choose your role for this new team. You will be the first member"
|
||||
title={t("teamMember.form.role.title")}
|
||||
subtitle={t("teamMember.form.role.description")}
|
||||
rightContent={
|
||||
<Controller
|
||||
name="roleId"
|
||||
@@ -106,7 +108,7 @@ export const TeamMemberForm = ({
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
<Select
|
||||
value={field.value}
|
||||
fieldLabel="Role"
|
||||
fieldLabel={t("teamMember.form.role.optionRole")}
|
||||
error={!!errors.roleId}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
@@ -120,7 +122,7 @@ export const TeamMemberForm = ({
|
||||
</Select>
|
||||
<Stack>
|
||||
<Typography variant={"h2"} mb={theme.spacing(4)}>
|
||||
Included permissions:
|
||||
{`${t("teamMember.form.role.optionIncludedPermissions")}:`}
|
||||
</Typography>
|
||||
{selectedRole?.permissions?.map((perm: string) => (
|
||||
<Typography key={perm}>- {perm}</Typography>
|
||||
@@ -139,7 +141,7 @@ export const TeamMemberForm = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
{t("settingsSave")}
|
||||
{t("common.buttons.save")}
|
||||
</Button>
|
||||
{deleteButton}
|
||||
</Stack>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Table } from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import { ActionsMenu } from "@/components/actions-menu";
|
||||
import { Dialog } from "@/components/inputs";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -13,8 +14,10 @@ import { useGet, useDelete } from "@/hooks/UseApi";
|
||||
import type { ITeam } from "@/types/team";
|
||||
import { useState } from "react";
|
||||
import { mutate } from "swr";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
const TeamsPage = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [selectedTeam, setSelectedTeam] = useState<ITeam | null>(null);
|
||||
const isDialogOpen = Boolean(selectedTeam);
|
||||
@@ -37,7 +40,7 @@ const TeamsPage = () => {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
label: "Configure",
|
||||
label: t("monitors.common.actions.configure"),
|
||||
action: () => {
|
||||
navigate(`/teams/${team._id}/configure`);
|
||||
},
|
||||
@@ -45,7 +48,11 @@ const TeamsPage = () => {
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: "Delete",
|
||||
label: (
|
||||
<Typography color={theme.palette.error.main}>
|
||||
{t("common.buttons.delete")}
|
||||
</Typography>
|
||||
),
|
||||
action: () => {
|
||||
setSelectedTeam(team);
|
||||
},
|
||||
@@ -58,21 +65,21 @@ const TeamsPage = () => {
|
||||
const headers: Header<ITeam>[] = [
|
||||
{
|
||||
id: "name",
|
||||
content: "Team name",
|
||||
content: t("common.table.headers.name"),
|
||||
render: (row) => {
|
||||
return row.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "description",
|
||||
content: "Team description",
|
||||
content: t("teams.table.headers.teamDescription"),
|
||||
render: (row) => {
|
||||
return row.description;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: "Team description",
|
||||
content: t("common.table.headers.actions"),
|
||||
render: (row) => {
|
||||
return <ActionsMenu items={getActions(row)} />;
|
||||
},
|
||||
@@ -86,8 +93,8 @@ const TeamsPage = () => {
|
||||
return (
|
||||
<BasePage>
|
||||
<InfoBox
|
||||
title="Team Collaboration"
|
||||
description="Organize your monitoring workspace by creating teams. Control who can view and manage monitors, set up role-based permissions, and collaborate effectively."
|
||||
title={t("teams.infoBox.title")}
|
||||
description={t("teams.infoBox.description")}
|
||||
/>
|
||||
<HeaderCreate
|
||||
label="Create new team"
|
||||
@@ -105,8 +112,8 @@ const TeamsPage = () => {
|
||||
/>
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
title={t("deleteDialogTitle")}
|
||||
content={t("deleteDialogDescription")}
|
||||
title={t("common.dialog.delete.title")}
|
||||
content={t("common.dialog.delete.description")}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
loading={isDeleting}
|
||||
|
||||
Reference in new issue
Block a user