{t("section.incident.incident-contacts")}
diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx
new file mode 100644
index 0000000000..1d0ebeb65d
--- /dev/null
+++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/OrgTabGroup.tsx
@@ -0,0 +1,59 @@
+import { Fragment } from "react"
+import { Tab } from "@headlessui/react"
+
+import { useOrganization,useUser } from "@app/context";
+import {
+ useGetOrgUsers
+} from "@app/hooks/api";
+
+import { OrgAuthTab } from "../OrgAuthTab";
+import { OrgGeneralTab } from "../OrgGeneralTab";
+
+export const OrgTabGroup = () => {
+ const { currentOrg } = useOrganization();
+ const { user } = useUser();
+ const { data } = useGetOrgUsers(currentOrg?._id ?? "");
+
+ const isRoleSufficient = data?.some((orgUser) => {
+ return orgUser.role !== "member" && orgUser.user._id === user._id;
+ });
+
+ const tabs = [
+ { name: "General", key: "tab-org-general" },
+ ];
+
+ if (isRoleSufficient) {
+ tabs.push(
+ { name: "Authentication", key: "tab-org-auth" }
+ );
+ }
+
+ return (
+
+
+ {tabs.map((tab) => (
+
+ {({ selected }) => (
+
+ )}
+
+ ))}
+
+
+
+
+
+ {isRoleSufficient && (
+
+
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/index.tsx
new file mode 100644
index 0000000000..d8178d9111
--- /dev/null
+++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgTabGroup/index.tsx
@@ -0,0 +1 @@
+export { OrgTabGroup } from "./OrgTabGroup";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx
index 9d2819536c..d8178d9111 100644
--- a/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx
+++ b/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx
@@ -1,3 +1 @@
-export { OrgIncidentContactsSection } from "./OrgIncidentContactsSection";
-export { OrgNameChangeSection } from "./OrgNameChangeSection";
-export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
\ No newline at end of file
+export { OrgTabGroup } from "./OrgTabGroup";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx
new file mode 100644
index 0000000000..a3eec451d1
--- /dev/null
+++ b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx
@@ -0,0 +1,132 @@
+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,
+ Select,
+ SelectItem} from "@app/components/v2";
+import { useUser } from "@app/context";
+import {
+ useUpdateUserAuthProvider
+} from "@app/hooks/api";
+
+const authMethods = [
+ { label: "Email", value: "email" },
+ { label: "Google SSO", value: "google" },
+ { label: "Okta SAML 2.0", value: "okta-saml" },
+];
+
+const schema = yup.object({
+ authMethod: yup.string().required("Auth method is required")
+});
+
+export type FormData = yup.InferType;
+
+export const AuthMethodSection = () => {
+ const { createNotification } = useNotificationContext();
+ const { user } = useUser();
+ const { mutateAsync, isLoading } = useUpdateUserAuthProvider();
+
+ const {
+ reset,
+ control,
+ handleSubmit
+ } = useForm({
+ defaultValues: {
+ authMethod: user?.authProvider ?? "email"
+ },
+ resolver: yupResolver(schema)
+ });
+
+ useEffect(() => {
+ if (user) {
+ reset({
+ authMethod: user?.authProvider ?? "email"
+ });
+ }
+ }, [user]);
+
+ const onFormSubmit = async ({
+ authMethod
+ }: FormData) => {
+ try {
+ if (authMethod === "okta-saml") {
+ createNotification({
+ text: "Okta SAML 2.0 can only be configured in your organization settings",
+ type: "error"
+ });
+
+ return;
+ }
+
+ await mutateAsync({
+ authProvider: authMethod
+ });
+
+ createNotification({
+ text: "Successfully updated authentication method",
+ type: "success"
+ });
+ } catch (err) {
+ console.error(err);
+ createNotification({
+ text: "Failed to update authentication method",
+ type: "error"
+ });
+ }
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/index.tsx b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/index.tsx
new file mode 100644
index 0000000000..12540cbb40
--- /dev/null
+++ b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/index.tsx
@@ -0,0 +1 @@
+export { AuthMethodSection } from "./AuthMethodSection";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx
new file mode 100644
index 0000000000..73f04113ee
--- /dev/null
+++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx
@@ -0,0 +1,13 @@
+import { AuthMethodSection } from "../AuthMethodSection";
+import { ChangePasswordSection } from "../ChangePasswordSection";
+import { MFASection } from "../SecuritySection";
+
+export const PersonalAuthTab = () => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/index.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/index.tsx
new file mode 100644
index 0000000000..4ab3d0b6ef
--- /dev/null
+++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/index.tsx
@@ -0,0 +1 @@
+export { PersonalAuthTab } from "./PersonalAuthTab";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/PersonalSecurityTab.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/PersonalGeneralTab.tsx
similarity index 64%
rename from frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/PersonalSecurityTab.tsx
rename to frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/PersonalGeneralTab.tsx
index abfec566a0..531776751a 100644
--- a/frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/PersonalSecurityTab.tsx
+++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/PersonalGeneralTab.tsx
@@ -1,18 +1,14 @@
import { ChangeLanguageSection } from "../ChangeLanguageSection";
-import { ChangePasswordSection } from "../ChangePasswordSection";
import { EmergencyKitSection } from "../EmergencyKitSection";
-import { SecuritySection } from "../SecuritySection";
import { SessionsSection } from "../SessionsSection";
import { UserNameSection } from "../UserNameSection";
-export const PersonalSecurityTab = () => {
+export const PersonalGeneralTab = () => {
return (
-
-
);
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/index.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/index.tsx
new file mode 100644
index 0000000000..1a83f1b889
--- /dev/null
+++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalGeneralTab/index.tsx
@@ -0,0 +1 @@
+export { PersonalGeneralTab } from "./PersonalGeneralTab";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/index.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/index.tsx
deleted file mode 100644
index e9d5025cb5..0000000000
--- a/frontend/src/views/Settings/PersonalSettingsPage/PersonalSecurityTab/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { PersonalSecurityTab } from "./PersonalSecurityTab";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalTabGroup/PersonalTabGroup.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalTabGroup/PersonalTabGroup.tsx
index 6f3edef97a..f2e367b360 100644
--- a/frontend/src/views/Settings/PersonalSettingsPage/PersonalTabGroup/PersonalTabGroup.tsx
+++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalTabGroup/PersonalTabGroup.tsx
@@ -2,10 +2,12 @@ import { Fragment } from "react"
import { Tab } from "@headlessui/react"
import { PersonalAPIKeyTab } from "../PersonalAPIKeyTab";
-import { PersonalSecurityTab } from "../PersonalSecurityTab";
+import { PersonalAuthTab } from "../PersonalAuthTab";
+import { PersonalGeneralTab } from "../PersonalGeneralTab";
const tabs = [
- { name: "General", key: "tab-account-security" },
+ { name: "General", key: "tab-account-general" },
+ { name: "Authentication", key: "tab-account-auth" },
{ name: "API Keys", key: "tab-account-api-keys" }
];
@@ -28,7 +30,10 @@ export const PersonalTabGroup = () => {
-
+
+
+
+
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx
similarity index 98%
rename from frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx
rename to frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx
index 4f23cbf083..8f9b36db74 100644
--- a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx
+++ b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx
@@ -9,7 +9,7 @@ import { useGetUser } from "../../../../hooks/api";
import { User } from "../../../../hooks/api/types";
import updateMyMfaEnabled from "../../../../pages/api/user/updateMyMfaEnabled";
-export const SecuritySection = () => {
+export const MFASection = () => {
const [isMfaEnabled, setIsMfaEnabled] = useState(false);
const { data: user } = useGetUser();
const { createNotification } = useNotificationContext();
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/index.tsx b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/index.tsx
index b360c0b4b1..981ac6d5c7 100644
--- a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/index.tsx
+++ b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/index.tsx
@@ -1 +1 @@
-export { SecuritySection } from "./SecuritySection";
\ No newline at end of file
+export { MFASection } from "./MFASection";
\ No newline at end of file
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/UserNameSection/UserNameSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/UserNameSection/UserNameSection.tsx
index bf03877a8e..722112bf9b 100644
--- a/frontend/src/views/Settings/PersonalSettingsPage/UserNameSection/UserNameSection.tsx
+++ b/frontend/src/views/Settings/PersonalSettingsPage/UserNameSection/UserNameSection.tsx
@@ -55,7 +55,7 @@ export const UserNameSection = (): JSX.Element => {
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
- User name
+ Name
,
+ popUpName: keyof UsePopUpState<["updateEnv", "deleteEnv", "upgradePlan"]>,
{
name,
slug
diff --git a/frontend/src/views/Signup/SignupSSO.tsx b/frontend/src/views/Signup/SignupSSO.tsx
new file mode 100644
index 0000000000..0946076a48
--- /dev/null
+++ b/frontend/src/views/Signup/SignupSSO.tsx
@@ -0,0 +1,57 @@
+import { useState } from "react";
+import jwt_decode from "jwt-decode";
+
+import {
+ BackupPDFStep,
+ UserInfoSSOStep} from "./components";
+
+type Props = {
+ providerAuthToken: string;
+}
+
+export const SignupSSO = ({
+ providerAuthToken
+}: Props) => {
+ const [step, setStep] = useState(0);
+ const [password, setPassword] = useState("");
+
+ const {
+ email,
+ organizationName,
+ firstName,
+ lastName
+ } = jwt_decode(providerAuthToken) as any;
+
+ const renderView = () => {
+ switch (step) {
+ case 0:
+ return (
+
+ );
+ case 1:
+ return (
+
+ );
+ default:
+ return
+ }
+ }
+
+ return (
+
+ {renderView()}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/views/Signup/components/BackupPDFStep/BackupPDFStep.tsx b/frontend/src/views/Signup/components/BackupPDFStep/BackupPDFStep.tsx
new file mode 100644
index 0000000000..8e8b582c10
--- /dev/null
+++ b/frontend/src/views/Signup/components/BackupPDFStep/BackupPDFStep.tsx
@@ -0,0 +1,66 @@
+import { useTranslation } from "react-i18next";
+import { useRouter } from "next/router"
+import { faWarning } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
+import { Button } from "@app/components/v2";
+
+interface DownloadBackupPDFStepProps {
+ email: string;
+ password: string;
+ name: string;
+}
+
+/**
+ * This is the step of the signup flow where the user downloads the backup pdf
+ * @param {object} obj
+ * @param {function} obj.incrementStep - function that moves the user on to the next stage of signup
+ * @param {string} obj.email - user's email
+ * @param {string} obj.password - user's password
+ * @param {string} obj.name - user's name
+ * @returns
+ */
+export const BackupPDFStep = ({
+ email,
+ password,
+ name
+}: DownloadBackupPDFStepProps) => {
+ const { t } = useTranslation();
+ const router = useRouter();
+
+ return (
+
+
+ {t("signup.step4-message")}
+
+
+
+ {t("signup.step4-description1")} {t("signup.step4-description3")}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/views/Signup/components/BackupPDFStep/index.tsx b/frontend/src/views/Signup/components/BackupPDFStep/index.tsx
new file mode 100644
index 0000000000..244296d152
--- /dev/null
+++ b/frontend/src/views/Signup/components/BackupPDFStep/index.tsx
@@ -0,0 +1 @@
+export { BackupPDFStep } from "./BackupPDFStep";
\ No newline at end of file
diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx
new file mode 100644
index 0000000000..fed0394678
--- /dev/null
+++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx
@@ -0,0 +1,320 @@
+
+import crypto from "crypto";
+
+import React, { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { faXmark } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import jsrp from "jsrp";
+import nacl from "tweetnacl";
+import { encodeBase64 } from "tweetnacl-util";
+
+import InputField from "@app/components/basic/InputField";
+import checkPassword from "@app/components/utilities/checks/checkPassword";
+import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
+import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
+import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
+import SecurityClient from "@app/components/utilities/SecurityClient";
+import { Button, Input } from "@app/components/v2";
+import { useGetCommonPasswords } from "@app/hooks/api";
+import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
+import getOrganizations from "@app/pages/api/organization/getOrgs";
+import ProjectService from "@app/services/ProjectService";
+
+// eslint-disable-next-line new-cap
+const client = new jsrp.client();
+
+type Props = {
+ setStep: (step: number) => void;
+ email: string;
+ password: string;
+ setPassword: (value: string) => void;
+ name: string;
+ providerOrganizationName: string;
+ providerAuthToken?: string;
+}
+
+type Errors = {
+ length?: string,
+ upperCase?: string,
+ lowerCase?: string,
+ number?: string,
+ specialChar?: string,
+ repeatedChar?: string,
+};
+
+/**
+ * This is the step of the sign up flow where people provife their name/surname and password
+ * @param {object} obj
+ * @param {string} obj.verificationToken - the token which we use to verify the legitness of a user
+ * @param {string} obj.incrementStep - a function to move to the next signup step
+ * @param {string} obj.email - email of a user who is signing up
+ * @param {string} obj.password - user's password
+ * @param {string} obj.setPassword - function managing the state of user's password
+ * @param {string} obj.firstName - user's first name
+ * @param {string} obj.setFirstName - function managing the state of user's first name
+ * @param {string} obj.lastName - user's lastName
+ * @param {string} obj.setLastName - function managing the state of user's last name
+ */
+export const UserInfoSSOStep = ({
+ email,
+ name,
+ providerOrganizationName,
+ password,
+ setPassword,
+ setStep,
+ providerAuthToken,
+}: Props) => {
+ const { data: commonPasswords } = useGetCommonPasswords();
+ const [nameError, setNameError] = useState(false);
+ const [organizationName, setOrganizationName] = useState("");
+ const [organizationNameError, setOrganizationNameError] = useState(false);
+ const [attributionSource, setAttributionSource] = useState("");
+ const [errors, setErrors] = useState({});
+ const [isLoading, setIsLoading] = useState(false);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (providerOrganizationName !== undefined) {
+ setOrganizationName(providerOrganizationName);
+ }
+ }, []);
+
+ // Verifies if the information that the users entered (name, workspace)
+ // is there, and if the password matches the criteria.
+ const signupErrorCheck = async () => {
+ setIsLoading(true);
+ let errorCheck = false;
+ if (!name) {
+ setNameError(true);
+ errorCheck = true;
+ } else {
+ setNameError(false);
+ }
+ if (!organizationName) {
+ setOrganizationNameError(true);
+ errorCheck = true;
+ } else {
+ setOrganizationNameError(false);
+ }
+
+ errorCheck = checkPassword({
+ password,
+ commonPasswords,
+ setErrors
+ });
+
+ if (!errorCheck) {
+ // Generate a random pair of a public and a private key
+ const pair = nacl.box.keyPair();
+ const secretKeyUint8Array = pair.secretKey;
+ const publicKeyUint8Array = pair.publicKey;
+ const privateKey = encodeBase64(secretKeyUint8Array);
+ const publicKey = encodeBase64(publicKeyUint8Array);
+ localStorage.setItem("PRIVATE_KEY", privateKey);
+
+ client.init(
+ {
+ username: email,
+ password
+ },
+ async () => {
+ client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
+ try {
+ // TODO: moduralize into KeyService
+ const derivedKey = await deriveArgonKey({
+ password,
+ salt: result.salt,
+ mem: 65536,
+ time: 3,
+ parallelism: 1,
+ hashLen: 32
+ });
+
+ if (!derivedKey) throw new Error("Failed to derive key from password");
+
+ const key = crypto.randomBytes(32);
+
+ // create encrypted private key by encrypting the private
+ // key with the symmetric key [key]
+ const {
+ ciphertext: encryptedPrivateKey,
+ iv: encryptedPrivateKeyIV,
+ tag: encryptedPrivateKeyTag
+ } = Aes256Gcm.encrypt({
+ text: privateKey,
+ secret: key
+ });
+
+ // create the protected key by encrypting the symmetric key
+ // [key] with the derived key
+ const {
+ ciphertext: protectedKey,
+ iv: protectedKeyIV,
+ tag: protectedKeyTag
+ } = Aes256Gcm.encrypt({
+ text: key.toString("hex"),
+ secret: Buffer.from(derivedKey.hash)
+ });
+
+ const response = await completeAccountInformationSignup({
+ email,
+ firstName: name.split(" ")[0],
+ lastName: name.split(" ").slice(1).join(" "),
+ protectedKey,
+ protectedKeyIV,
+ protectedKeyTag,
+ publicKey,
+ encryptedPrivateKey,
+ encryptedPrivateKeyIV,
+ encryptedPrivateKeyTag,
+ providerAuthToken,
+ salt: result.salt,
+ verifier: result.verifier,
+ organizationName,
+ attributionSource
+ });
+
+ // unset signup JWT token and set JWT token
+ SecurityClient.setSignupToken("");
+ SecurityClient.setToken(response.token);
+ SecurityClient.setProviderAuthToken("");
+
+ saveTokenToLocalStorage({
+ publicKey,
+ encryptedPrivateKey,
+ iv: encryptedPrivateKeyIV,
+ tag: encryptedPrivateKeyTag,
+ privateKey
+ });
+
+ const userOrgs = await getOrganizations();
+ const orgId = userOrgs[0]?._id;
+ const project = await ProjectService.initProject({
+ organizationId: orgId,
+ projectName: "Example Project"
+ });
+
+ localStorage.setItem("orgData.id", orgId);
+ localStorage.setItem("projectData.id", project._id);
+
+ setStep(1);
+ } catch (error) {
+ setIsLoading(false);
+ console.error(error);
+ }
+ });
+ }
+ );
+ } else {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+ {t("signup.step3-message")}
+
+
+
+
Your Name
+
+ {nameError &&
Please, specify your name
}
+
+ {providerOrganizationName === undefined && (
+
+
Organization Name
+
setOrganizationName(e.target.value)}
+ isRequired
+ className="h-12"
+ disabled
+ />
+ {organizationNameError &&
Please, specify your organization name
}
+
+ )}
+ {providerOrganizationName === undefined && (
+
+
Where did you hear about us? (optional)
+
setAttributionSource(e.target.value)}
+ value={attributionSource}
+ className="h-12"
+ />
+
+ )}
+
+
{
+ setPassword(pass);
+ checkPassword({
+ password: pass,
+ commonPasswords,
+ setErrors
+ });
+ }}
+ type="password"
+ value={password}
+ isRequired
+ error={Object.keys(errors).length > 0}
+ autoComplete="new-password"
+ id="new-password"
+ />
+ {Object.keys(errors).length > 0 && (
+
+
{t("section.password.validate-base")}
+ {Object.keys(errors).map((key) => {
+ if (errors[key as keyof Errors]) {
+ return (
+
+
+
+
+
+ {errors[key as keyof Errors]}
+
+
+ );
+ }
+
+ return null;
+ })}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/index.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/index.tsx
new file mode 100644
index 0000000000..f1278a2cd4
--- /dev/null
+++ b/frontend/src/views/Signup/components/UserInfoSSOStep/index.tsx
@@ -0,0 +1 @@
+export { UserInfoSSOStep } from "./UserInfoSSOStep";
\ No newline at end of file
diff --git a/frontend/src/views/Signup/components/index.tsx b/frontend/src/views/Signup/components/index.tsx
new file mode 100644
index 0000000000..4ae3ec5269
--- /dev/null
+++ b/frontend/src/views/Signup/components/index.tsx
@@ -0,0 +1,2 @@
+export { BackupPDFStep } from "./BackupPDFStep";
+export { UserInfoSSOStep } from "./UserInfoSSOStep";
\ No newline at end of file
diff --git a/frontend/src/views/Signup/index.tsx b/frontend/src/views/Signup/index.tsx
new file mode 100644
index 0000000000..b5cb660acf
--- /dev/null
+++ b/frontend/src/views/Signup/index.tsx
@@ -0,0 +1 @@
+export { SignupSSO } from "./SignupSSO";
\ No newline at end of file