added the ability to change user name

This commit is contained in:
Vladyslav Matsiiako
2023-07-18 18:36:34 -07:00
parent 3b5bc151ba
commit 8ad851d4b0
11 changed files with 140 additions and 12 deletions
+23 -3
View File
@@ -3,10 +3,10 @@ import { Types } from "mongoose";
import crypto from "crypto"; import crypto from "crypto";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { import {
MembershipOrg,
User,
APIKeyData, APIKeyData,
TokenVersion MembershipOrg,
TokenVersion,
User
} from "../../models"; } from "../../models";
import { getSaltRounds } from "../../config"; import { getSaltRounds } from "../../config";
@@ -80,6 +80,26 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
}); });
} }
/**
* Update the current user's name [firstName, lastName].
* @param req
* @param res
* @returns
*/
export const updateName = async (req: Request, res: Response) => {
const { firstName, lastName }: { firstName: string; lastName: string; } = req.body;
req.user.firstName = firstName;
req.user.lastName = lastName || "";
await req.user.save();
const user = req.user;
return res.status(200).send({
user,
});
}
/** /**
* Return organizations that the current user is part of. * Return organizations that the current user is part of.
* @param req * @param req
+10
View File
@@ -29,6 +29,16 @@ router.patch(
usersController.updateMyMfaEnabled usersController.updateMyMfaEnabled
); );
router.patch(
"/me/name",
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
}),
body("firstName").exists(),
validateRequest,
usersController.updateName
);
router.get( router.get(
"/me/organizations", "/me/organizations",
requireAuth({ requireAuth({
+13
View File
@@ -16,6 +16,7 @@ import {
CreateAPIKeyRes, CreateAPIKeyRes,
DeletOrgMembershipDTO, DeletOrgMembershipDTO,
OrgUser, OrgUser,
RenameUserDTO,
TokenVersion, TokenVersion,
UpdateOrgUserRoleDTO, UpdateOrgUserRoleDTO,
User} from "./types"; User} from "./types";
@@ -45,6 +46,18 @@ const fetchUserAction = async (action: string) => {
return data.userAction; return data.userAction;
}; };
export const useRenameUser = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, RenameUserDTO>({
mutationFn: ({ newName }) =>
apiRequest.patch("/api/v2/users/me/name", { firstName: newName?.split(" ")[0], lastName: newName?.split(" ").slice(1).join(" ") }),
onSuccess: () => {
queryClient.invalidateQueries(userKeys.getUser);
}
});
};
export const useGetUserAction = (action: string) => export const useGetUserAction = (action: string) =>
useQuery({ useQuery({
queryKey: userKeys.userAction, queryKey: userKeys.userAction,
+4
View File
@@ -67,6 +67,10 @@ export type CreateAPIKeyRes = {
apiKeyData: APIKeyData; apiKeyData: APIKeyData;
} }
export type RenameUserDTO = {
newName: string;
};
export type APIKeyData = { export type APIKeyData = {
_id: string; _id: string;
name: string; name: string;
@@ -15,7 +15,7 @@ export const ChangeLanguageSection = () => {
}; };
return ( return (
<div className="p-4 bg-mineshaft-900 mb-6 max-w-screen-lg rounded-lg border border-mineshaft-600"> <div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
<p className="text-xl font-semibold text-mineshaft-100 mb-8"> <p className="text-xl font-semibold text-mineshaft-100 mb-8">
{t("settings.personal.change-language")} {t("settings.personal.change-language")}
</p> </p>
@@ -3,10 +3,12 @@ import { ChangePasswordSection } from "../ChangePasswordSection";
import { EmergencyKitSection } from "../EmergencyKitSection"; import { EmergencyKitSection } from "../EmergencyKitSection";
import { SecuritySection } from "../SecuritySection"; import { SecuritySection } from "../SecuritySection";
import { SessionsSection } from "../SessionsSection"; import { SessionsSection } from "../SessionsSection";
import { UserNameSection } from "../UserNameSection";
export const PersonalSecurityTab = () => { export const PersonalSecurityTab = () => {
return ( return (
<div> <div>
<UserNameSection />
<ChangeLanguageSection /> <ChangeLanguageSection />
<SecuritySection /> <SecuritySection />
<SessionsSection /> <SessionsSection />
@@ -12,7 +12,7 @@ const tabs = [
export const PersonalTabGroup = () => { export const PersonalTabGroup = () => {
return ( return (
<Tab.Group> <Tab.Group>
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full"> <Tab.List className="mb-4 border-b-2 border-mineshaft-800 w-full">
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}> <Tab as={Fragment} key={tab.key}>
{({ selected }) => ( {({ selected }) => (
@@ -0,0 +1,82 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Button, FormControl, Input } from "@app/components/v2";
import { useUser } from "@app/context";
import { useRenameUser } from "@app/hooks/api/users/queries";
const formSchema = yup.object({
name: yup.string().required().label("User Name"),
});
type FormData = yup.InferType<typeof formSchema>;
export const UserNameSection = (): JSX.Element => {
const { user } = useUser();
const { createNotification } = useNotificationContext();
const {
handleSubmit,
control,
reset
} = useForm<FormData>({ resolver: yupResolver(formSchema) });
const { mutateAsync, isLoading } = useRenameUser();
useEffect(() => {
if (user) {
reset({ name: `${user?.firstName}${user?.lastName && " "}${user?.lastName}` });
}
}, [user]);
const onFormSubmit = async ({ name }: FormData) => {
try {
if (!user?._id) return;
if (name === "") return;
await mutateAsync({ newName: name});
createNotification({
text: "Successfully renamed user",
type: "success"
});
} catch (error) {
console.error(error);
createNotification({
text: "Failed to rename user",
type: "error"
});
}
};
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
User name
</p>
<div className="mb-2 max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input placeholder={`${user?.firstName} ${user?.lastName}`} {...field} />
</FormControl>
)}
control={control}
name="name"
/>
</div>
<Button
isLoading={isLoading}
colorSchema="primary"
variant="outline_bg"
type="submit"
>
Save
</Button>
</form>
);
};
@@ -0,0 +1 @@
export { UserNameSection } from "./UserNameSection";
@@ -26,15 +26,13 @@ export const ProjectSettingsPage = () => {
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p> <p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div> </div>
<Tab.Group> <Tab.Group>
<Tab.List className="mb-6 w-full border-b-2 border-mineshaft-800"> <Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}> <Tab as={Fragment} key={tab.key}>
{({ selected }) => ( {({ selected }) => (
<button <button
type="button" type="button"
className={`w-30 p-4 font-semibold outline-none ${ className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"
}`}
> >
{tab.name} {tab.name}
</button> </button>
@@ -1,6 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup"; import * as yup from "yup";
@@ -27,7 +26,6 @@ export const ProjectNameChangeSection = () => {
control, control,
reset reset
} = useForm<FormData>({ resolver: yupResolver(formSchema) }); } = useForm<FormData>({ resolver: yupResolver(formSchema) });
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
if (currentWorkspace) { if (currentWorkspace) {
@@ -67,7 +65,7 @@ export const ProjectNameChangeSection = () => {
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600" className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
> >
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8"> <h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
{t("common.display-name")} Project Name
</h2> </h2>
<div className="max-w-md"> <div className="max-w-md">
<Controller <Controller