Make merge user step automatic after email verification

This commit is contained in:
Tuan Dang
2024-04-30 21:33:27 -07:00
parent ce2a9c8640
commit 0482424a1c
13 changed files with 159 additions and 398 deletions
+28 -18
View File
@@ -1,12 +1,7 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import jwt_decode from "jwt-decode";
import {
BackupPDFStep,
EmailConfirmationStep,
MergeUsersStep,
UserInfoSSOStep
} from "./components";
import { BackupPDFStep, EmailConfirmationStep, UserInfoSSOStep } from "./components";
type Props = {
providerAuthToken: string;
@@ -27,13 +22,30 @@ export const SignupSSO = ({ providerAuthToken }: Props) => {
isEmailVerified
} = jwt_decode(providerAuthToken) as any;
useEffect(() => {
if (!isEmailVerified) {
setStep(0);
} else {
setStep(1);
}
}, []);
const renderView = () => {
switch (step) {
case 0:
return (
<EmailConfirmationStep
authType={authType}
username={username}
email={email}
organizationSlug={organizationSlug}
setStep={setStep}
/>
);
case 1:
return (
<UserInfoSSOStep
username={username}
isEmailVerified={isEmailVerified}
name={`${firstName} ${lastName}`}
providerOrganizationName={organizationName}
password={password}
@@ -42,17 +54,15 @@ export const SignupSSO = ({ providerAuthToken }: Props) => {
providerAuthToken={providerAuthToken}
/>
);
case 1:
return <EmailConfirmationStep email={email} setStep={setStep} />;
// case 2:
// return (
// <MergeUsersStep
// username={username}
// authType={authType}
// organizationSlug={organizationSlug}
// />
// );
case 2:
return (
<MergeUsersStep
username={username}
authType={authType}
organizationSlug={organizationSlug}
/>
);
case 3:
return (
<BackupPDFStep email={username} password={password} name={`${firstName} ${lastName}`} />
);
@@ -2,18 +2,19 @@
// if same email exists, then trigger fn to merge automatically
import { useState } from "react";
import ReactCodeInput from "react-code-input";
import { useRouter } from "next/router";
import Error from "@app/components/basic/Error";
import { createNotification } from "@app/components/notifications";
import { Button } from "@app/components/v2";
import {
fetchUsersWithMyEmail,
useSendEmailVerificationCode,
useVerifyEmailVerificationCode
} from "@app/hooks/api";
import { useSendEmailVerificationCode, useVerifyEmailVerificationCode } from "@app/hooks/api";
import { UserAliasType } from "@app/hooks/api/users/types";
type Props = {
authType?: UserAliasType;
username: string;
email: string;
organizationSlug: string;
setStep: (step: number) => void;
};
@@ -55,7 +56,14 @@ const propsPhone = {
}
} as const;
export const EmailConfirmationStep = ({ email, setStep }: Props) => {
export const EmailConfirmationStep = ({
authType,
username,
email,
organizationSlug,
setStep
}: Props) => {
const router = useRouter();
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(false);
const [isResendingVerificationEmail] = useState(false);
@@ -66,19 +74,29 @@ export const EmailConfirmationStep = ({ email, setStep }: Props) => {
const checkCode = async () => {
try {
await verifyEmailVerificationCode({ code });
await verifyEmailVerificationCode({ username, code });
setCodeError(false);
const usersWithSameEmail = await fetchUsersWithMyEmail();
if (usersWithSameEmail.length > 1) {
setStep(2);
}
createNotification({
text: "Successfully verified code",
type: "success"
});
switch (authType) {
case UserAliasType.SAML: {
window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`);
window.close();
break;
}
case UserAliasType.LDAP: {
router.push(`/login/ldap?organizationSlug=${organizationSlug}`);
break;
}
default: {
setStep(1);
break;
}
}
} catch (err) {
createNotification({
text: "Failed to verify code",
@@ -91,7 +109,11 @@ export const EmailConfirmationStep = ({ email, setStep }: Props) => {
const resendCode = async () => {
try {
await sendEmailVerificationCode();
await sendEmailVerificationCode(username);
createNotification({
text: "Successfully resent code",
type: "success"
});
} catch (err) {
createNotification({
text: "Failed to resend code",
@@ -1,157 +0,0 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { faUsers } from "@fortawesome/free-solid-svg-icons";
import { createNotification } from "@app/components/notifications";
import {
Button,
EmptyState,
Modal,
ModalContent,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useListUsersWithMyEmail, useMergeUsers } from "@app/hooks/api";
import { UserAliasType } from "@app/hooks/api/users/types";
type Props = {
username: string;
authType?: UserAliasType;
organizationSlug: string;
};
export const MergeUsersStep = ({ username, authType, organizationSlug }: Props) => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const [targetUsername, setTargetUsername] = useState("");
const { data: users, isLoading: isLoadingUsers } = useListUsersWithMyEmail();
const { mutateAsync: mergeUser, isLoading: isLoadingMerge } = useMergeUsers();
const handleMergeUser = async (mergeWithUsername: string) => {
try {
if (!mergeWithUsername) return;
await mergeUser({ username: mergeWithUsername });
createNotification({
text: "Successfully merged user",
type: "success"
});
setIsOpen(false);
switch (authType) {
case UserAliasType.SAML: {
window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`);
window.close();
break;
}
case UserAliasType.LDAP: {
router.push(`/login/ldap?organizationSlug=${organizationSlug}`);
break;
}
default: {
router.push("/login");
break;
}
}
setTargetUsername("");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to merge user",
type: "error"
});
}
};
return (
<div className="mx-auto h-full max-w-xl">
<p className="text-md flex justify-center text-bunker-200">
We found an account with the same verified email.
</p>
<p className="text-md mb-8 flex justify-center text-bunker-200">
Select the account to merge with it.
</p>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Username</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoadingUsers && <TableSkeleton columns={3} innerKey="same-email-users" />}
{!isLoadingUsers &&
users
?.filter((user) => user.username !== username)
?.map((user) => {
return (
<Tr className="h-10 items-center" key={`same-email-user-${user.id}`}>
<Td>{`${user.firstName ?? ""} ${user.lastName ?? ""}`}</Td>
<Td>{user.username}</Td>
<Td>
<Button
colorSchema="primary"
variant="outline_bg"
type="submit"
onClick={() => {
setIsOpen(true);
setTargetUsername(user.username);
}}
>
Merge
</Button>
</Td>
</Tr>
);
})}
{!isLoadingUsers && !users?.length && (
<Tr>
<Td colSpan={3}>
<EmptyState title="No users found with the same email" icon={faUsers} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
<Modal isOpen={isOpen} onOpenChange={setIsOpen}>
<ModalContent title="Merge User Confirmation">
<p className="mb-4 text-bunker-300">
The merge operation will transfer / consolidate your existing organization membership to
the target user you&apos;re merging with.
</p>
<p className="mb-4 text-bunker-300">
If the target user is not yet part of the same organization, then they will be added to
it under your current organization membership. Conversely, if the target user is already
part of the organization, then their existing organization membership will remain.
</p>
<p className="text-bunker-300">
Once the merge operation is complete, you&apos;ll be prompted to re-login.
</p>
<div className="mt-8 flex items-center">
<Button
isLoading={isLoadingMerge}
colorSchema="primary"
onClick={async () => handleMergeUser(targetUsername)}
className="mr-4"
>
Confirm
</Button>
<Button colorSchema="secondary" variant="plain" onClick={() => setIsOpen(false)}>
Cancel
</Button>
</div>
</ModalContent>
</Modal>
</div>
);
};
@@ -1 +0,0 @@
export { MergeUsersStep } from "./MergeUsersStep";
@@ -17,7 +17,6 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, Input } from "@app/components/v2";
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { sendEmailVerificationCode } from "@app/hooks/api/users/mutation";
import ProjectService from "@app/services/ProjectService";
// eslint-disable-next-line new-cap
@@ -26,7 +25,6 @@ const client = new jsrp.client();
type Props = {
setStep: (step: number) => void;
username: string;
isEmailVerified?: boolean;
password: string;
setPassword: (value: string) => void;
name: string;
@@ -60,7 +58,6 @@ type Errors = {
*/
export const UserInfoSSOStep = ({
username,
isEmailVerified,
name,
providerOrganizationName,
password,
@@ -204,14 +201,7 @@ export const UserInfoSSOStep = ({
localStorage.setItem("orgData.id", orgId);
localStorage.setItem("projectData.id", project.id);
if (isEmailVerified) {
// move to backup PDF step
setStep(3);
} else {
// move to verify email
await sendEmailVerificationCode();
setStep(1);
}
setStep(2);
} catch (error) {
setIsLoading(false);
console.error(error);
@@ -1,4 +1,3 @@
export { BackupPDFStep } from "./BackupPDFStep";
export { EmailConfirmationStep } from "./EmailConfirmationStep";
export { MergeUsersStep } from "./MergeUsersStep";
export { UserInfoSSOStep } from "./UserInfoSSOStep";