From d7b17fd034f4c0dec8da0ff669a89e5715e88995 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 10 Apr 2024 15:25:58 +0530 Subject: [PATCH] API updates --- src/api/tenants/index.ts | 21 +++++---- src/ui/components/inputField/InputField.tsx | 2 + .../creatNewTenant/CreateNewTenant.tsx | 5 ++- .../tenantDetail/CoreConfigSection.tsx | 29 ++++++++---- .../tenantDetail/LoginMethodsSection.tsx | 45 ++++++++++--------- .../tenants/tenantDetail/TenantDetail.tsx | 4 +- 6 files changed, 61 insertions(+), 45 deletions(-) diff --git a/src/api/tenants/index.ts b/src/api/tenants/index.ts index 26eb8f72..49746b17 100644 --- a/src/api/tenants/index.ts +++ b/src/api/tenants/index.ts @@ -26,10 +26,10 @@ export const useTenantCreateService = () => { createdNew: boolean; } | { - status: "MULTITENANCY_NOT_ENABLED_IN_CORE"; + status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR"; } | { - status: "INVALID_TENANT_ID"; + status: "INVALID_TENANT_ID_ERROR"; message: string; } | undefined @@ -195,15 +195,14 @@ export const useUpdateFirstFactorsService = () => { enable: boolean ): Promise< | { status: "OK" } - | { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK"; message: string } + | { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; message: string } | { status: "UNKNOWN_TENANT_ERROR" } > => { // TODO: Temporary mock data await new Promise((resolve) => setTimeout(resolve, 1000)); return { - status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK", - message: "Recipe not initialized", + status: "OK", }; const response = await fetchData({ @@ -235,10 +234,9 @@ export const useUpdateSecondaryFactorsService = () => { factorId: string, enable: boolean ): Promise< - | { status: "OK" } - | { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK"; message: string } - | { status: "MFA_NOT_INITIALIZED" } - | { status: "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN" } + | { status: "OK"; isMFARequirementsForAuthOverridden: boolean } + | { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; message: string } + | { status: "MFA_NOT_INITIALIZED_ERROR" } | { status: "UNKNOWN_TENANT_ERROR" } > => { // TODO: Temporary mock data @@ -246,6 +244,7 @@ export const useUpdateSecondaryFactorsService = () => { return { status: "OK", + isMFARequirementsForAuthOverridden: true, }; const response = await fetchData({ @@ -277,13 +276,13 @@ export const useUpdateCoreConfigService = () => { name: string, value: string | number | boolean | null ): Promise< - { status: "OK" } | { status: "UNKNOWN_TENANT_ERROR" } | { status: "INVALID_CONFIG"; message: string } + { status: "OK" } | { status: "UNKNOWN_TENANT_ERROR" } | { status: "INVALID_CONFIG_ERROR"; message: string } > => { // TODO: Temporary mock data await new Promise((resolve) => setTimeout(resolve, 1000)); return { - status: "INVALID_CONFIG", + status: "INVALID_CONFIG_ERROR", message: "Invalid config", }; diff --git a/src/ui/components/inputField/InputField.tsx b/src/ui/components/inputField/InputField.tsx index bb377631..b7dc7938 100644 --- a/src/ui/components/inputField/InputField.tsx +++ b/src/ui/components/inputField/InputField.tsx @@ -31,6 +31,7 @@ export type InputFieldPropTypes = { forceShowError?: boolean; disabled?: boolean; prefix?: string; + autofocus?: boolean; handleChange: React.ChangeEventHandler; /** @default "bottom" */ errorPlacement?: "bottom" | "prefix-tooltip"; @@ -83,6 +84,7 @@ const InputField: React.FC = (props) => { onChange={onChange} onKeyUp={onChange} value={props.value} + autoFocus={props.autofocus} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} disabled={props.disabled} diff --git a/src/ui/components/tenants/creatNewTenant/CreateNewTenant.tsx b/src/ui/components/tenants/creatNewTenant/CreateNewTenant.tsx index 9ec4ef79..127dbc7e 100644 --- a/src/ui/components/tenants/creatNewTenant/CreateNewTenant.tsx +++ b/src/ui/components/tenants/creatNewTenant/CreateNewTenant.tsx @@ -46,11 +46,11 @@ export const CreateNewTenantDialog = ({ onCloseDialog }: { onCloseDialog: () => } else { setTenantCreationError("Tenant already exists"); } - } else if (resp?.status === "MULTITENANCY_NOT_ENABLED_IN_CORE") { + } else if (resp?.status === "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR") { setTenantCreationError( "Multitenancy is not enabled for your SuperTokens instance. Please add a license key to enable it." ); - } else if (resp?.status === "INVALID_TENANT_ID") { + } else if (resp?.status === "INVALID_TENANT_ID_ERROR") { setTenantCreationError(resp.message); } else { throw new Error("Failed to create tenant"); @@ -73,6 +73,7 @@ export const CreateNewTenantDialog = ({ onCloseDialog }: { onCloseDialog: () => error={tenantCreationError} forceShowError={true} label="Tenant Id" + autofocus name="tenantId" type="text" value={tenantId} diff --git a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx index 6f58c325..9c25abec 100644 --- a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx +++ b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx @@ -12,12 +12,14 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { useUpdateCoreConfigService } from "../../../../api/tenants"; import { ReactComponent as PencilIcon } from "../../../../assets/edit.svg"; import { ReactComponent as InfoIcon } from "../../../../assets/info-icon.svg"; import { ReactComponent as QuestionMarkIcon } from "../../../../assets/question-mark.svg"; import { PUBLIC_TENANT_ID } from "../../../../constants"; +import { getImageUrl } from "../../../../utils"; +import { PopupContentContext } from "../../../contexts/PopupContentContext"; import Button from "../../button"; import { Checkbox } from "../../checkbox/Checkbox"; import InputField from "../../inputField/InputField"; @@ -133,13 +135,13 @@ const CoreConfigTableRow = ({ }: CoreConfigTableRowProps) => { const [isEditing, setIsEditing] = useState(false); const [currentValue, setCurrentValue] = useState(value); - const [error, setError] = useState(null); const { tenantInfo, refetchTenant } = useTenantDetailContext(); const updateCoreConfig = useUpdateCoreConfigService(); const [isLoading, setIsLoading] = useState(false); const [isUneditablePropertyDialogVisible, setIsUneditablePropertyDialogVisible] = useState(false); const isMultiValue = Array.isArray(possibleValues) && possibleValues.length > 0; const isPublicTenant = tenantInfo.tenantId === PUBLIC_TENANT_ID; + const { showToast } = useContext(PopupContentContext); const isUneditable = isPublicTenant || @@ -164,26 +166,36 @@ const CoreConfigTableRow = ({ const handleCancelEdit = () => { setIsEditing(false); setCurrentValue(value); - setError(null); }; const handleSaveProperty = async () => { try { setIsLoading(true); - setError(null); const res = await updateCoreConfig(tenantInfo.tenantId, name, currentValue); if (res.status !== "OK") { if (res.status === "UNKNOWN_TENANT_ERROR") { - setError("Tenant not found."); + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>Tenant not found., + }); } else { - setError(res.message); + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>{res.message}, + }); } return; } await refetchTenant(); setIsEditing(false); } catch (e) { - setError("Something went wrong. Please try again."); + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>Something went wrong please try again., + }); } finally { setIsLoading(false); } @@ -305,12 +317,11 @@ const CoreConfigTableRow = ({ type="text" size="small" name={name} + autofocus disabled={!isEditing || currentValue === null} handleChange={(e) => { setCurrentValue(e.target.value); }} - error={error ?? undefined} - forceShowError value={currentValue === null ? "[null]" : `${currentValue}`} /> )} diff --git a/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx b/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx index f7a4d920..2fab397f 100644 --- a/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx +++ b/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx @@ -12,7 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { useUpdateFirstFactorsService, useUpdateSecondaryFactorsService } from "../../../../api/tenants"; import { ReactComponent as ErrorIcon } from "../../../../assets/form-field-error-icon.svg"; import { ReactComponent as InfoIcon } from "../../../../assets/info-icon.svg"; @@ -26,7 +26,7 @@ import { useTenantDetailContext } from "./TenantDetailContext"; import { PanelHeader, PanelHeaderTitleWithTooltip, PanelRoot } from "./tenantDetailPanel/TenantDetailPanel"; export const LoginMethodsSection = () => { - const { tenantInfo, setTenantInfo } = useTenantDetailContext(); + const { tenantInfo, setTenantInfo, refetchTenant } = useTenantDetailContext(); const updateFirstFactors = useUpdateFirstFactorsService(); const updateSecondaryFactors = useUpdateSecondaryFactorsService(); const [selectedFactors, setSelectedFactors] = useState<{ @@ -50,6 +50,13 @@ export const LoginMethodsSection = () => { requiredSecondaryFactors: {}, }); + useEffect(() => { + setSelectedFactors({ + firstFactors: tenantInfo.firstFactors ?? [], + requiredSecondaryFactors: tenantInfo.requiredSecondaryFactors ?? [], + }); + }, [tenantInfo]); + const { showToast } = useContext(PopupContentContext); const doesTenantHasEmailPasswordAndPasswordlessEnabled = @@ -77,7 +84,7 @@ export const LoginMethodsSection = () => { // have also added an aritificial delay so that the toggle can finish its animation and // has good UX in case API responds too quickly setTimeout(() => setSelectedFactors(prevFactors), 200); - if (res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK") { + if (res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR") { setFactorErrors((prev) => ({ ...prev, firstFactors: { ...prev.firstFactors, [id]: res.message }, @@ -100,32 +107,22 @@ export const LoginMethodsSection = () => { setIsSecondaryFactorsLoading(true); const res = await updateSecondaryFactors(tenantInfo.tenantId, id, !doesFactorExist); if (res.status !== "OK") { - if (res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK") { + if (res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR") { setFactorErrors((prev) => ({ ...prev, requiredSecondaryFactors: { ...prev.requiredSecondaryFactors, [id]: res.message }, })); - } else if ( - res.status === "MFA_NOT_INITIALIZED" || - res.status === "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN" - ) { - setSecondaryFactorsError(res.status); + } else if (res.status === "MFA_NOT_INITIALIZED_ERROR") { + setSecondaryFactorsError("MFA_NOT_INITIALIZED"); } else { throw new Error(res.status); } - // We allow users to update secondary factors even if - // getMFARequirementsForAuth is overridden, for rest of - // cases we revert the state - if (res.status !== "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN") { - // If the API returns a non success status, revert the state - // have also added an aritificial delay so that the toggle can finish its animation and - // has good UX in case API responds too quickly - setTimeout(() => setSelectedFactors(prevFactors), 200); - } - } - - if (res.status === "OK" || res.status === "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN") { + // If the API returns a non success status, revert the state + // have also added an aritificial delay so that the toggle can finish its animation and + // has good UX in case API responds too quickly + setTimeout(() => setSelectedFactors(prevFactors), 200); + } else if (res.status === "OK") { setTenantInfo((prev) => prev ? { @@ -135,6 +132,10 @@ export const LoginMethodsSection = () => { : undefined ); } + + if (res.status === "OK" && res.isMFARequirementsForAuthOverridden) { + setSecondaryFactorsError("MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN"); + } } } catch (error) { showToast({ @@ -150,6 +151,8 @@ export const LoginMethodsSection = () => { } finally { setIsFirstFactorsLoading(false); setIsSecondaryFactorsLoading(false); + // TODO: Enable this when the API is ready + // void refetchTenant(); } }; diff --git a/src/ui/components/tenants/tenantDetail/TenantDetail.tsx b/src/ui/components/tenants/tenantDetail/TenantDetail.tsx index c405b27e..899c2b84 100644 --- a/src/ui/components/tenants/tenantDetail/TenantDetail.tsx +++ b/src/ui/components/tenants/tenantDetail/TenantDetail.tsx @@ -19,7 +19,7 @@ import { ReactComponent as NoTenantFound } from "../../../../assets/no-tenants.s import { FactorIds, PUBLIC_TENANT_ID } from "../../../../constants"; import { getImageUrl } from "../../../../utils"; import Button from "../../button"; -import { Loader, LoaderOverlay } from "../../loader/Loader"; +import { Loader } from "../../loader/Loader"; import { AddNewProviderDialog } from "./addNewProviderDialog/AddNewProviderDialog"; import { CoreConfigSection } from "./CoreConfigSection"; import { DeleteTenantDialog } from "./deleteTenant/DeleteTenant"; @@ -118,7 +118,7 @@ export const TenantDetail = ({ return (
- {showLoadingOverlay && } + {/* {showLoadingOverlay && } */}