diff --git a/pages/api/proxyApiRequest.ts b/pages/api/connection.ts similarity index 88% rename from pages/api/proxyApiRequest.ts rename to pages/api/connection.ts index 90e56c0..ce3efa7 100644 --- a/pages/api/proxyApiRequest.ts +++ b/pages/api/connection.ts @@ -5,7 +5,7 @@ import { NextApiRequest, NextApiResponse } from 'next/types'; import { sendErrorToBugsnag } from '@/lib/bugsnag'; import { handleAxiosError } from '@/components/Error/ErrorUtils'; -const proxyApiRequestHandler = async (req: NextApiRequest, res: NextApiResponse) => { +const createOrUpdateConnectionHandler = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'POST') { // Process a POST request let { url, data } = req.body; @@ -38,4 +38,4 @@ const proxyApiRequestHandler = async (req: NextApiRequest, res: NextApiResponse) } }; -export default proxyApiRequestHandler; +export default createOrUpdateConnectionHandler; diff --git a/src/components/FormInput/FormFieldText.tsx b/src/components/FormInput/FormFieldText.tsx index f5f28f6..e32c967 100644 --- a/src/components/FormInput/FormFieldText.tsx +++ b/src/components/FormInput/FormFieldText.tsx @@ -38,8 +38,7 @@ const FormFieldText = (props: FormFieldTextProps) => { onChange } = props; - const inputProps = - type === 'number' ? { inputMode: 'numeric', pattern: '[0-9]*' } : {}; + const inputProps = type === 'number' ? { inputMode: 'numeric', pattern: '[0-9]*' } : {}; return ( diff --git a/src/components/FormInput/FormOneOfControl.tsx b/src/components/FormInput/FormOneOfControl.tsx index 27edf3b..970aa40 100644 --- a/src/components/FormInput/FormOneOfControl.tsx +++ b/src/components/FormInput/FormOneOfControl.tsx @@ -179,7 +179,7 @@ const MaterialOneOfEnumControl = (props: CombinatorRendererProps) => { handleOnConfigureButtonClick={handleOnConfigureButtonClick} oAuthProvider={getOAuthProviderName(selectedConnector)} oauth_error={oauth_error} - hasOAuthAuthorized={isIntegrationAuthorized(oauth_params, false)} + hasOAuthAuthorized={isIntegrationAuthorized(oauth_params, isEditableFlow)} sx={{ mt: 2 }} /> ); diff --git a/src/components/FormInput/FormSelectControl.tsx b/src/components/FormInput/FormSelectControl.tsx index ee3697d..7441e36 100644 --- a/src/components/FormInput/FormSelectControl.tsx +++ b/src/components/FormInput/FormSelectControl.tsx @@ -6,7 +6,7 @@ import { ControlProps } from '@jsonforms/core'; import { withJsonFormsControlProps } from '@jsonforms/react'; -import { Card, FormControl, Hidden, MenuItem, TextField, Tooltip, Typography } from '@mui/material'; +import { Card, FormControl, Hidden, MenuItem, TextField, Typography } from '@mui/material'; import { merge } from 'lodash'; import { useDebouncedChange, useFocus } from '@jsonforms/material-renderers'; @@ -41,40 +41,38 @@ export const FormSelectControl = (props: ControlProps) => { {visible && ( - - + - - {schema.enum?.map((item: string) => { - return ( - - {item} - - ); - })} - + {schema.enum?.map((item: string) => { + return ( + + {item} + + ); + })} + - - {formErrorHelperText?.toLowerCase() ?? ''} - - - + + {formErrorHelperText?.toLowerCase() ?? ''} + + )} diff --git a/src/components/Instructions/index.tsx b/src/components/Instructions/index.tsx index 3ad4a44..51aa6cf 100644 --- a/src/components/Instructions/index.tsx +++ b/src/components/Instructions/index.tsx @@ -1,9 +1,3 @@ -/* - * Copyright (c) 2024 valmi.io - * Created Date: Thursday, May 25th 2023, 6:11:37 pm - * Author: Nagendra S @ valmi.io - */ - import { Box, Link, Typography, styled } from '@mui/material'; const InstructionsBox = styled(Box)(({}) => ({ @@ -34,11 +28,11 @@ const Instructions = (props: InstructionsProps) => { - Refer to step-by-step instructions to setup a{' '} + Follow the step-by-step instructions to connect to{' '} {linkText} - {type && type === 'credential' && <> {'credential.'}} + {/* {type && type === 'credential' && <> {'credential.'}} */} ); diff --git a/src/content/ConnectionFlow/ConnectionConfig/ConnectionLoginButton.tsx b/src/content/ConnectionFlow/ConnectionConfig/ConnectionLoginButton.tsx index 2fc2ed7..9ad5145 100644 --- a/src/content/ConnectionFlow/ConnectionConfig/ConnectionLoginButton.tsx +++ b/src/content/ConnectionFlow/ConnectionConfig/ConnectionLoginButton.tsx @@ -260,13 +260,15 @@ const ConnectionLoginButton = (props: any) => { /> - + <> + + diff --git a/src/content/ConnectionFlow/ConnectionConfig/index.tsx b/src/content/ConnectionFlow/ConnectionConfig/index.tsx index f0e8abd..595ae71 100644 --- a/src/content/ConnectionFlow/ConnectionConfig/index.tsx +++ b/src/content/ConnectionFlow/ConnectionConfig/index.tsx @@ -23,10 +23,10 @@ import { getExtrasObjKey, filterStreamsBasedOnScope, initializeConnectionFlowState, - generateConfigFromSpec, + generateFormStateFromSpec, generateCredentialPayload } from '@/utils/connectionFlowUtils'; -import { isObjectEmpty } from '@/utils/lib'; +import { flattenObject, isObjectEmpty } from '@/utils/lib'; import { generateStreamObj } from '@/content/ConnectionFlow/ConnectionDiscover/streamsReducer'; import { useRouter } from 'next/router'; import { @@ -57,17 +57,11 @@ const ConnectionConfig = ({ params, isEditableFlow = false }: TConnectionUpsertP const selectedConnector = connectionDataFlow.entities[getSelectedConnectorKey()] ?? {}; - const { - type = '', - display_name: displayName = '', - oauth_keys: oauthKeys = '', - mode = '', - oauth_params: oAuthParams = {} - } = selectedConnector; + const { type = '', display_name: displayName = '', mode = '', oauth_params: oAuthParams = {} } = selectedConnector; const { config = {}, account = {} } = connectionDataFlow.entities[getCredentialObjKey(type)] ?? {}; - const { id: credentialId = '' } = config ?? {}; + const { id: credentialId = '', name: credentialName = '' } = config ?? {}; // Getting credential selectors for editing case specifically - not useful for create case const { selectCredentialById } = getCredentialsSelectors(wid as string); @@ -119,27 +113,27 @@ const ConnectionConfig = ({ params, isEditableFlow = false }: TConnectionUpsertP useEffect(() => { if (oAuthParams && !isObjectEmpty(oAuthParams)) { - oAuthConfiguredState().run(); + const currentFormState = connectionDataFlow.entities[getSelectedConnectorKey()]?.formValues || {}; + storeOAuthState(currentFormState, oAuthParams); } }, [oAuthParams]); - const oAuthConfiguredState = () => { - return { - run: () => { - const { spec = null } = connectionDataFlow.entities[getCredentialObjKey(type)]; - const formDataFromStore = connectionDataFlow.entities[getSelectedConnectorKey()]?.formValues || {}; - let combinedValues = { - ...formDataFromStore, - ...getOAuthParams(oAuthParams) - }; + /** + * Stores state consisting of current form state and OAuth response in connectionForm context. + * @param currentFormState The current state of the form. + * @param oauthResponse The response received from OAuth. + */ + const storeOAuthState = (currentFormState: any, oauthResponse: any) => { + const formAndOAuthState = flattenObject({ + ...currentFormState, + ...getOAuthParams(oauthResponse) + }); - let config = generateConfigFromSpec(spec, combinedValues); + const { spec = null } = connectionDataFlow.entities[getCredentialObjKey(type)]; - setFormState(config); + const updatedFormState = generateFormStateFromSpec(spec, formAndOAuthState, type); - return; - } - }; + setFormState(updatedFormState); }; // STATE: Trigger to get into Checking Credential State - BEGIN @@ -172,8 +166,11 @@ const ConnectionConfig = ({ params, isEditableFlow = false }: TConnectionUpsertP } else { if (isEditableFlow) { const userAccount = { ...user, id: account.id }; + + const updatedFormState = { ...formState, id: credentialId, name: credentialName }; + const payload = generateCredentialPayload({ - credentialConfig: formState, + credentialConfig: updatedFormState, type, user: userAccount, isEditableFlow: isEditableFlow @@ -206,7 +203,7 @@ const ConnectionConfig = ({ params, isEditableFlow = false }: TConnectionUpsertP const handleCredentialUpdate = async (workspaceId: string, payload: any) => { const credentialUpdateURL = `/workspaces/${workspaceId}/credentials/update`; await httpPostRequestHandler({ - route: apiRoutes['proxyURL'], + route: apiRoutes['connectionURL'], url: credentialUpdateURL, payload, errorCb: handleCredentialUpdateError, diff --git a/src/contexts/OAuthContext.tsx b/src/contexts/OAuthContext.tsx index 52abe2b..77b830c 100644 --- a/src/contexts/OAuthContext.tsx +++ b/src/contexts/OAuthContext.tsx @@ -115,6 +115,7 @@ function OAuthContextProvider({ children }: Props) { } } }; + dispatch(setEntities(entities)); router.push(`${oAuthRoute}?state=${state}`); diff --git a/src/utils/connectionFlowUtils.tsx b/src/utils/connectionFlowUtils.tsx index e64300e..edaae4e 100644 --- a/src/utils/connectionFlowUtils.tsx +++ b/src/utils/connectionFlowUtils.tsx @@ -432,35 +432,39 @@ export const isIntegrationAuthorized = (oAuthParams: any, isEditableFlow: boolea return !!((oAuthParams && !isObjectEmpty(oAuthParams)) || isEditableFlow); }; -export const generateConfigFromSpec = (spec: any, values: any) => { +export const generateFormStateFromSpec = (spec: any, values: any, type: string) => { return createJsonObject( spec.spec.connectionSpecification.required, spec.spec.connectionSpecification.properties, - values + values, + type ); }; -const createJsonObject = (required: any, properties_def: any, values: any) => { +const createJsonObject = (required: any, properties_def: any, values: any, type: string) => { let obj: any = {}; if (required) { for (const field of required) { if (properties_def[field].oneOf) { // Determine which oneOf schema to use based on the auth_method value - const method = values?.credentials?.auth_method ?? ''; + + const authMethodKey = getOAuthMethodKeyFromType(type); + + const authMethodValue = values?.[authMethodKey] ?? ''; + const selectedSchema = properties_def[field].oneOf.find( - (schema: any) => schema.properties.auth_method.const === method + (schema: any) => schema?.properties?.[authMethodKey]?.const === authMethodValue ); - // Merge nested credentials from top-level values - const nestedValues = { ...values.credentials, ...extractNestedCredentials(values) }; - obj[field] = createJsonObject(selectedSchema?.required, selectedSchema?.properties, nestedValues); + obj[field] = createJsonObject(selectedSchema?.required, selectedSchema?.properties, values, type); } else if (properties_def[field].type === 'object') { if (properties_def[field].required) { obj[field] = createJsonObject( properties_def[field].required, properties_def[field].properties, - values[field] + values[field], + type ); } else { obj[field] = values[field]; @@ -474,10 +478,20 @@ const createJsonObject = (required: any, properties_def: any, values: any) => { return obj; }; -const extractNestedCredentials = (values: any) => { - return { - client_id: values.client_id, - client_secret: values.client_secret, - access_token: values.access_token - }; +export const getOAuthMethodKeyFromType = (type: string): string => { + switch (type) { + case getShopifyIntegrationType(): + return 'auth_method'; + default: + return ''; + } +}; + +export const getOAuthMethodDefaultValueFromType = (type: string): string => { + switch (type) { + case getShopifyIntegrationType(): + return 'api_password'; + default: + return ''; + } }; diff --git a/src/utils/lib.tsx b/src/utils/lib.tsx index 5f18fba..6bfbc46 100644 --- a/src/utils/lib.tsx +++ b/src/utils/lib.tsx @@ -229,3 +229,15 @@ export function deepFlattenToObject(obj, prefix = '') { return acc; }, {}); } + +// Function to flatten nested objects +export function flattenObject(obj: any, parentKey = '') { + return Object.keys(obj).reduce((acc, key) => { + if (typeof obj[key] === 'object' && obj[key] !== null) { + Object.assign(acc, flattenObject(obj[key], key)); + } else { + acc[key] = obj[key]; + } + return acc; + }, {}); +} diff --git a/src/utils/login-utils.js b/src/utils/login-utils.js index 95c413c..ba1dcbf 100644 --- a/src/utils/login-utils.js +++ b/src/utils/login-utils.js @@ -23,12 +23,13 @@ export const loginFormSchema = { type: 'boolean', title: 'Check to receive latest product updates over email', const: true, - description: 'Select this checkbox to receive emails about new product features and announcements' + // description: 'Select this checkbox to receive emails about new product features and announcements' + description: '' }, role: { type: 'string', title: 'You are part of', - enum: ['Engineering', 'Marketing', 'Other'], + enum: ['Engineering', 'Marketing', 'Finance', 'Sales', 'Operations', 'Other'], description: "Select your role from the dropdown menu. If your role isn't listed, choose 'Other'" } }, diff --git a/src/utils/router-utils.tsx b/src/utils/router-utils.tsx index ff4b6cf..ff2d319 100644 --- a/src/utils/router-utils.tsx +++ b/src/utils/router-utils.tsx @@ -16,7 +16,7 @@ export const apiRoutes = { checkURL: `/api/checkConnection`, defaultURL: `/`, fbTokenURL: '/api/getFbLongLivedToken', - proxyURL: `/api/proxyApiRequest` + connectionURL: `/api/connection` }; export const redirectToHomePage = (wid: string, router: NextRouter) => {