Merge branch 'main' into AUT-1379
This commit is contained in:
@@ -9,7 +9,7 @@ import * as React from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { AppPropType } from 'propTypes/propTypes';
|
||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||
import AppOAuthClientsDialog from 'components/OAuthClientsDialog/index.ee';
|
||||
import InputCreator from 'components/InputCreator';
|
||||
import * as URLS from 'config/urls';
|
||||
import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
|
||||
@@ -31,12 +31,12 @@ function AddAppConnection(props) {
|
||||
const [inProgress, setInProgress] = React.useState(false);
|
||||
const hasConnection = Boolean(connectionId);
|
||||
const useShared = searchParams.get('shared') === 'true';
|
||||
const appAuthClientId = searchParams.get('appAuthClientId') || undefined;
|
||||
const oauthClientId = searchParams.get('oauthClientId') || undefined;
|
||||
const { authenticate } = useAuthenticateApp({
|
||||
appKey: key,
|
||||
connectionId,
|
||||
appAuthClientId,
|
||||
useShared: !!appAuthClientId,
|
||||
oauthClientId,
|
||||
useShared: !!oauthClientId,
|
||||
});
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -52,8 +52,8 @@ function AddAppConnection(props) {
|
||||
}, []);
|
||||
|
||||
React.useEffect(
|
||||
function initiateSharedAuthenticationForGivenAuthClient() {
|
||||
if (!appAuthClientId) return;
|
||||
function initiateSharedAuthenticationForGivenOAuthClient() {
|
||||
if (!oauthClientId) return;
|
||||
|
||||
if (!authenticate) return;
|
||||
|
||||
@@ -64,13 +64,13 @@ function AddAppConnection(props) {
|
||||
|
||||
asyncAuthenticate();
|
||||
},
|
||||
[appAuthClientId, authenticate],
|
||||
[oauthClientId, authenticate, key, navigate],
|
||||
);
|
||||
|
||||
const handleClientClick = (appAuthClientId) =>
|
||||
navigate(URLS.APP_ADD_CONNECTION_WITH_AUTH_CLIENT_ID(key, appAuthClientId));
|
||||
const handleClientClick = (oauthClientId) =>
|
||||
navigate(URLS.APP_ADD_CONNECTION_WITH_OAUTH_CLIENT_ID(key, oauthClientId));
|
||||
|
||||
const handleAuthClientsDialogClose = () =>
|
||||
const handleOAuthClientsDialogClose = () =>
|
||||
navigate(URLS.APP_CONNECTIONS(key));
|
||||
|
||||
const submitHandler = React.useCallback(
|
||||
@@ -104,14 +104,14 @@ function AddAppConnection(props) {
|
||||
|
||||
if (useShared)
|
||||
return (
|
||||
<AppAuthClientsDialog
|
||||
<AppOAuthClientsDialog
|
||||
appKey={key}
|
||||
onClose={handleAuthClientsDialogClose}
|
||||
onClose={handleOAuthClientsDialogClose}
|
||||
onClientClick={handleClientClick}
|
||||
/>
|
||||
);
|
||||
|
||||
if (appAuthClientId) return <React.Fragment />;
|
||||
if (oauthClientId) return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
||||
@@ -5,11 +5,11 @@ import { AppPropType } from 'propTypes/propTypes';
|
||||
import useAdminCreateAppConfig from 'hooks/useAdminCreateAppConfig';
|
||||
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useAdminCreateAppAuthClient from 'hooks/useAdminCreateAppAuthClient.ee';
|
||||
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
||||
import useAdminCreateOAuthClient from 'hooks/useAdminCreateOAuthClient.ee';
|
||||
import AdminApplicationOAuthClientDialog from 'components/AdminApplicationOAuthClientDialog';
|
||||
import useAppAuth from 'hooks/useAppAuth';
|
||||
|
||||
function AdminApplicationCreateAuthClient(props) {
|
||||
function AdminApplicationCreateOAuthClient(props) {
|
||||
const { appKey, onClose } = props;
|
||||
const { data: auth } = useAppAuth(appKey);
|
||||
const formatMessage = useFormatMessage();
|
||||
@@ -24,26 +24,26 @@ function AdminApplicationCreateAuthClient(props) {
|
||||
} = useAdminCreateAppConfig(props.appKey);
|
||||
|
||||
const {
|
||||
mutateAsync: createAppAuthClient,
|
||||
isPending: isCreateAppAuthClientPending,
|
||||
error: createAppAuthClientError,
|
||||
} = useAdminCreateAppAuthClient(appKey);
|
||||
mutateAsync: createOAuthClient,
|
||||
isPending: isCreateOAuthClientPending,
|
||||
error: createOAuthClientError,
|
||||
} = useAdminCreateOAuthClient(appKey);
|
||||
|
||||
const submitHandler = async (values) => {
|
||||
let appConfigKey = appConfig?.data?.key;
|
||||
|
||||
if (!appConfigKey) {
|
||||
const { data: appConfigData } = await createAppConfig({
|
||||
customConnectionAllowed: true,
|
||||
shared: false,
|
||||
useOnlyPredefinedAuthClients: false,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
appConfigKey = appConfigData.key;
|
||||
}
|
||||
|
||||
const { name, active, ...formattedAuthDefaults } = values;
|
||||
|
||||
await createAppAuthClient({
|
||||
await createOAuthClient({
|
||||
appKey,
|
||||
name,
|
||||
active,
|
||||
@@ -81,23 +81,23 @@ function AdminApplicationCreateAuthClient(props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<AdminApplicationAuthClientDialog
|
||||
<AdminApplicationOAuthClientDialog
|
||||
onClose={onClose}
|
||||
error={createAppConfigError || createAppAuthClientError}
|
||||
title={formatMessage('createAuthClient.title')}
|
||||
error={createAppConfigError || createOAuthClientError}
|
||||
title={formatMessage('createOAuthClient.title')}
|
||||
loading={isAppConfigLoading}
|
||||
submitHandler={submitHandler}
|
||||
authFields={auth?.data?.fields}
|
||||
submitting={isCreateAppConfigPending || isCreateAppAuthClientPending}
|
||||
submitting={isCreateAppConfigPending || isCreateOAuthClientPending}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
AdminApplicationCreateAuthClient.propTypes = {
|
||||
AdminApplicationCreateOAuthClient.propTypes = {
|
||||
appKey: PropTypes.string.isRequired,
|
||||
application: AppPropType.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AdminApplicationCreateAuthClient;
|
||||
export default AdminApplicationCreateOAuthClient;
|
||||
@@ -15,7 +15,7 @@ import Switch from 'components/Switch';
|
||||
import TextField from 'components/TextField';
|
||||
import { Form } from './style';
|
||||
|
||||
function AdminApplicationAuthClientDialog(props) {
|
||||
function AdminApplicationOAuthClientDialog(props) {
|
||||
const {
|
||||
error,
|
||||
onClose,
|
||||
@@ -52,12 +52,12 @@ function AdminApplicationAuthClientDialog(props) {
|
||||
<>
|
||||
<Switch
|
||||
name="active"
|
||||
label={formatMessage('authClient.inputActive')}
|
||||
label={formatMessage('oauthClient.inputActive')}
|
||||
/>
|
||||
<TextField
|
||||
required={true}
|
||||
name="name"
|
||||
label={formatMessage('authClient.inputName')}
|
||||
label={formatMessage('oauthClient.inputName')}
|
||||
fullWidth
|
||||
/>
|
||||
{authFields?.map((field) => (
|
||||
@@ -72,7 +72,7 @@ function AdminApplicationAuthClientDialog(props) {
|
||||
loading={submitting}
|
||||
disabled={disabled || !isDirty}
|
||||
>
|
||||
{formatMessage('authClient.buttonSubmit')}
|
||||
{formatMessage('oauthClient.buttonSubmit')}
|
||||
</LoadingButton>
|
||||
</>
|
||||
)}
|
||||
@@ -84,7 +84,7 @@ function AdminApplicationAuthClientDialog(props) {
|
||||
);
|
||||
}
|
||||
|
||||
AdminApplicationAuthClientDialog.propTypes = {
|
||||
AdminApplicationOAuthClientDialog.propTypes = {
|
||||
error: PropTypes.shape({
|
||||
message: PropTypes.string,
|
||||
}),
|
||||
@@ -98,4 +98,4 @@ AdminApplicationAuthClientDialog.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default AdminApplicationAuthClientDialog;
|
||||
export default AdminApplicationOAuthClientDialog;
|
||||
@@ -8,29 +8,30 @@ import CardContent from '@mui/material/CardContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
import * as URLS from 'config/urls';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useAdminAppAuthClients from 'hooks/useAdminAppAuthClients';
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
import useAdminOAuthClients from 'hooks/useAdminOAuthClients';
|
||||
|
||||
function AdminApplicationAuthClients(props) {
|
||||
function AdminApplicationOAuthClients(props) {
|
||||
const { appKey } = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const { data: appAuthClients, isLoading } = useAdminAppAuthClients(appKey);
|
||||
const { data: appOAuthClients, isLoading } = useAdminOAuthClients(appKey);
|
||||
|
||||
if (isLoading)
|
||||
return <CircularProgress sx={{ display: 'block', margin: '20px auto' }} />;
|
||||
|
||||
if (!appAuthClients?.data.length) {
|
||||
if (!appOAuthClients?.data.length) {
|
||||
return (
|
||||
<NoResultFound
|
||||
to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}
|
||||
text={formatMessage('adminAppsAuthClients.noAuthClients')}
|
||||
text={formatMessage('adminAppsOAuthClients.noOauthClients')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const sortedAuthClients = appAuthClients.data.slice().sort((a, b) => {
|
||||
const sortedOAuthClients = appOAuthClients.data.slice().sort((a, b) => {
|
||||
if (a.id < b.id) {
|
||||
return -1;
|
||||
}
|
||||
@@ -42,7 +43,7 @@ function AdminApplicationAuthClients(props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sortedAuthClients.map((client) => (
|
||||
{sortedOAuthClients.map((client) => (
|
||||
<Card sx={{ mb: 1 }} key={client.id} data-test="auth-client">
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
@@ -59,8 +60,8 @@ function AdminApplicationAuthClients(props) {
|
||||
variant={client?.active ? 'filled' : 'outlined'}
|
||||
label={formatMessage(
|
||||
client?.active
|
||||
? 'adminAppsAuthClients.statusActive'
|
||||
: 'adminAppsAuthClients.statusInactive',
|
||||
? 'adminAppsOAuthClients.statusActive'
|
||||
: 'adminAppsOAuthClients.statusInactive',
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -70,8 +71,13 @@ function AdminApplicationAuthClients(props) {
|
||||
))}
|
||||
<Stack justifyContent="flex-end" direction="row">
|
||||
<Link to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}>
|
||||
<Button variant="contained" sx={{ mt: 2 }} component="div" data-test="create-auth-client-button">
|
||||
{formatMessage('createAuthClient.button')}
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ mt: 2 }}
|
||||
component="div"
|
||||
data-test="create-auth-client-button"
|
||||
>
|
||||
{formatMessage('createOAuthClient.button')}
|
||||
</Button>
|
||||
</Link>
|
||||
</Stack>
|
||||
@@ -79,8 +85,8 @@ function AdminApplicationAuthClients(props) {
|
||||
);
|
||||
}
|
||||
|
||||
AdminApplicationAuthClients.propTypes = {
|
||||
AdminApplicationOAuthClients.propTypes = {
|
||||
appKey: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AdminApplicationAuthClients;
|
||||
export default AdminApplicationOAuthClients;
|
||||
@@ -46,9 +46,8 @@ function AdminApplicationSettings(props) {
|
||||
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
customConnectionAllowed:
|
||||
appConfig?.data?.customConnectionAllowed || false,
|
||||
shared: appConfig?.data?.shared || false,
|
||||
useOnlyPredefinedAuthClients:
|
||||
appConfig?.data?.useOnlyPredefinedAuthClients || false,
|
||||
disabled: appConfig?.data?.disabled || false,
|
||||
}),
|
||||
[appConfig?.data],
|
||||
@@ -62,21 +61,17 @@ function AdminApplicationSettings(props) {
|
||||
<Paper sx={{ p: 2, mt: 4 }}>
|
||||
<Stack spacing={2} direction="column">
|
||||
<Switch
|
||||
name="customConnectionAllowed"
|
||||
label={formatMessage('adminAppsSettings.customConnectionAllowed')}
|
||||
FormControlLabelProps={{
|
||||
labelPlacement: 'start',
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Switch
|
||||
name="shared"
|
||||
label={formatMessage('adminAppsSettings.shared')}
|
||||
name="useOnlyPredefinedAuthClients"
|
||||
label={formatMessage(
|
||||
'adminAppsSettings.useOnlyPredefinedAuthClients',
|
||||
)}
|
||||
FormControlLabelProps={{
|
||||
labelPlacement: 'start',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Switch
|
||||
name="disabled"
|
||||
label={formatMessage('adminAppsSettings.disabled')}
|
||||
@@ -86,6 +81,7 @@ function AdminApplicationSettings(props) {
|
||||
/>
|
||||
<Divider />
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<LoadingButton
|
||||
data-test="submit-button"
|
||||
|
||||
@@ -4,26 +4,26 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import { AppPropType } from 'propTypes/propTypes';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
||||
import useAdminAppAuthClient from 'hooks/useAdminAppAuthClient.ee';
|
||||
import useAdminUpdateAppAuthClient from 'hooks/useAdminUpdateAppAuthClient.ee';
|
||||
import AdminApplicationOAuthClientDialog from 'components/AdminApplicationOAuthClientDialog';
|
||||
import useAdminOAuthClient from 'hooks/useAdminOAuthClient.ee';
|
||||
import useAdminUpdateOAuthClient from 'hooks/useAdminUpdateOAuthClient.ee';
|
||||
import useAppAuth from 'hooks/useAppAuth';
|
||||
|
||||
function AdminApplicationUpdateAuthClient(props) {
|
||||
function AdminApplicationUpdateOAuthClient(props) {
|
||||
const { application, onClose } = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const { clientId } = useParams();
|
||||
|
||||
const { data: adminAppAuthClient, isLoading: isAdminAuthClientLoading } =
|
||||
useAdminAppAuthClient(application.key, clientId);
|
||||
const { data: adminOAuthClient, isLoading: isAdminOAuthClientLoading } =
|
||||
useAdminOAuthClient(application.key, clientId);
|
||||
|
||||
const { data: auth } = useAppAuth(application.key);
|
||||
|
||||
const {
|
||||
mutateAsync: updateAppAuthClient,
|
||||
isPending: isUpdateAppAuthClientPending,
|
||||
error: updateAppAuthClientError,
|
||||
} = useAdminUpdateAppAuthClient(application.key, clientId);
|
||||
mutateAsync: updateOAuthClient,
|
||||
isPending: isUpdateOAuthClientPending,
|
||||
error: updateOAuthClientError,
|
||||
} = useAdminUpdateOAuthClient(application.key, clientId);
|
||||
|
||||
const authFields = auth?.data?.fields?.map((field) => ({
|
||||
...field,
|
||||
@@ -31,13 +31,13 @@ function AdminApplicationUpdateAuthClient(props) {
|
||||
}));
|
||||
|
||||
const submitHandler = async (values) => {
|
||||
if (!adminAppAuthClient) {
|
||||
if (!adminOAuthClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, active, ...formattedAuthDefaults } = values;
|
||||
|
||||
await updateAppAuthClient({
|
||||
await updateOAuthClient({
|
||||
name,
|
||||
active,
|
||||
formattedAuthDefaults,
|
||||
@@ -64,31 +64,31 @@ function AdminApplicationUpdateAuthClient(props) {
|
||||
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
name: adminAppAuthClient?.data?.name || '',
|
||||
active: adminAppAuthClient?.data?.active || false,
|
||||
name: adminOAuthClient?.data?.name || '',
|
||||
active: adminOAuthClient?.data?.active || false,
|
||||
...getAuthFieldsDefaultValues(),
|
||||
}),
|
||||
[adminAppAuthClient, getAuthFieldsDefaultValues],
|
||||
[adminOAuthClient, getAuthFieldsDefaultValues],
|
||||
);
|
||||
|
||||
return (
|
||||
<AdminApplicationAuthClientDialog
|
||||
<AdminApplicationOAuthClientDialog
|
||||
onClose={onClose}
|
||||
error={updateAppAuthClientError}
|
||||
title={formatMessage('updateAuthClient.title')}
|
||||
loading={isAdminAuthClientLoading}
|
||||
error={updateOAuthClientError}
|
||||
title={formatMessage('updateOAuthClient.title')}
|
||||
loading={isAdminOAuthClientLoading}
|
||||
submitHandler={submitHandler}
|
||||
authFields={authFields}
|
||||
submitting={isUpdateAppAuthClientPending}
|
||||
submitting={isUpdateOAuthClientPending}
|
||||
defaultValues={defaultValues}
|
||||
disabled={!adminAppAuthClient}
|
||||
disabled={!adminOAuthClient}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
AdminApplicationUpdateAuthClient.propTypes = {
|
||||
AdminApplicationUpdateOAuthClient.propTypes = {
|
||||
application: AppPropType.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AdminApplicationUpdateAuthClient;
|
||||
export default AdminApplicationUpdateOAuthClient;
|
||||
@@ -1,53 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import * as React from 'react';
|
||||
import useAppAuthClients from 'hooks/useAppAuthClients';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
function AppAuthClientsDialog(props) {
|
||||
const { appKey, onClientClick, onClose } = props;
|
||||
const { data: appAuthClients } = useAppAuthClients(appKey);
|
||||
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
React.useEffect(
|
||||
function autoAuthenticateSingleClient() {
|
||||
if (appAuthClients?.data.length === 1) {
|
||||
onClientClick(appAuthClients.data[0].id);
|
||||
}
|
||||
},
|
||||
[appAuthClients?.data],
|
||||
);
|
||||
|
||||
if (!appAuthClients?.data.length || appAuthClients?.data.length === 1)
|
||||
return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true}>
|
||||
<DialogTitle>{formatMessage('appAuthClientsDialog.title')}</DialogTitle>
|
||||
|
||||
<List sx={{ pt: 0 }}>
|
||||
{appAuthClients.data.map((appAuthClient) => (
|
||||
<ListItem disableGutters key={appAuthClient.id}>
|
||||
<ListItemButton onClick={() => onClientClick(appAuthClient.id)}>
|
||||
<ListItemText primary={appAuthClient.name} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
AppAuthClientsDialog.propTypes = {
|
||||
appKey: PropTypes.string.isRequired,
|
||||
onClientClick: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AppAuthClientsDialog;
|
||||
@@ -11,14 +11,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||
import Can from 'components/Can';
|
||||
|
||||
function ContextMenu(props) {
|
||||
const {
|
||||
appKey,
|
||||
connection,
|
||||
onClose,
|
||||
onMenuItemClick,
|
||||
anchorEl,
|
||||
disableReconnection,
|
||||
} = props;
|
||||
const { appKey, connection, onClose, onMenuItemClick, anchorEl } = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -73,11 +66,11 @@ function ContextMenu(props) {
|
||||
{(allowed) => (
|
||||
<MenuItem
|
||||
component={Link}
|
||||
disabled={!allowed || disableReconnection}
|
||||
disabled={!allowed}
|
||||
to={URLS.APP_RECONNECT_CONNECTION(
|
||||
appKey,
|
||||
connection.id,
|
||||
connection.appAuthClientId,
|
||||
connection.oauthClientId,
|
||||
)}
|
||||
onClick={createActionHandler({ type: 'reconnect' })}
|
||||
>
|
||||
@@ -109,7 +102,6 @@ ContextMenu.propTypes = {
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]),
|
||||
disableReconnection: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
|
||||
@@ -30,8 +30,7 @@ const countTranslation = (value) => (
|
||||
function AppConnectionRow(props) {
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const { id, key, formattedData, verified, createdAt, reconnectable } =
|
||||
props.connection;
|
||||
const { id, key, formattedData, verified, createdAt } = props.connection;
|
||||
const [verificationVisible, setVerificationVisible] = React.useState(false);
|
||||
const contextButtonRef = React.useRef(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
@@ -174,7 +173,6 @@ function AppConnectionRow(props) {
|
||||
<ConnectionContextMenu
|
||||
appKey={key}
|
||||
connection={props.connection}
|
||||
disableReconnection={!reconnectable}
|
||||
onClose={handleClose}
|
||||
onMenuItemClick={onContextMenuAction}
|
||||
anchorEl={anchorEl}
|
||||
|
||||
@@ -91,15 +91,15 @@ function ChooseAppAndEventSubstep(props) {
|
||||
const onEventChange = React.useCallback(
|
||||
(event, selectedOption) => {
|
||||
if (typeof selectedOption === 'object') {
|
||||
// TODO: try to simplify type casting below.
|
||||
const typedSelectedOption = selectedOption;
|
||||
const option = typedSelectedOption;
|
||||
const eventKey = option?.value;
|
||||
const eventKey = selectedOption?.value;
|
||||
const eventLabel = selectedOption?.label;
|
||||
|
||||
if (step.key !== eventKey) {
|
||||
onChange({
|
||||
step: {
|
||||
...step,
|
||||
key: eventKey,
|
||||
keyLabel: eventLabel,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -111,10 +111,8 @@ function ChooseAppAndEventSubstep(props) {
|
||||
const onAppChange = React.useCallback(
|
||||
(event, selectedOption) => {
|
||||
if (typeof selectedOption === 'object') {
|
||||
// TODO: try to simplify type casting below.
|
||||
const typedSelectedOption = selectedOption;
|
||||
const option = typedSelectedOption;
|
||||
const appKey = option?.value;
|
||||
const appKey = selectedOption?.value;
|
||||
|
||||
if (step.appKey !== appKey) {
|
||||
onChange({
|
||||
step: {
|
||||
|
||||
@@ -7,7 +7,7 @@ import TextField from '@mui/material/TextField';
|
||||
import * as React from 'react';
|
||||
|
||||
import AddAppConnection from 'components/AddAppConnection';
|
||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||
import AppOAuthClientsDialog from 'components/OAuthClientsDialog/index.ee';
|
||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||
import { EditorContext } from 'contexts/Editor';
|
||||
@@ -22,6 +22,7 @@ import useStepConnection from 'hooks/useStepConnection';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import useAppConnections from 'hooks/useAppConnections';
|
||||
import useTestConnection from 'hooks/useTestConnection';
|
||||
import useOAuthClients from 'hooks/useOAuthClients';
|
||||
|
||||
const ADD_CONNECTION_VALUE = 'ADD_CONNECTION';
|
||||
const ADD_SHARED_CONNECTION_VALUE = 'ADD_SHARED_CONNECTION';
|
||||
@@ -53,6 +54,7 @@ function ChooseConnectionSubstep(props) {
|
||||
const [showAddSharedConnectionDialog, setShowAddSharedConnectionDialog] =
|
||||
React.useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const { data: appOAuthClients } = useOAuthClients(application.key);
|
||||
|
||||
const { authenticate } = useAuthenticateApp({
|
||||
appKey: application.key,
|
||||
@@ -93,30 +95,53 @@ function ChooseConnectionSubstep(props) {
|
||||
appWithConnections?.map((connection) => optionGenerator(connection)) ||
|
||||
[];
|
||||
|
||||
const addCustomConnection = {
|
||||
label: formatMessage('chooseConnectionSubstep.addNewConnection'),
|
||||
value: ADD_CONNECTION_VALUE,
|
||||
};
|
||||
|
||||
const addConnectionWithOAuthClient = {
|
||||
label: formatMessage(
|
||||
'chooseConnectionSubstep.addConnectionWithOAuthClient',
|
||||
),
|
||||
value: ADD_SHARED_CONNECTION_VALUE,
|
||||
};
|
||||
|
||||
// means there is no app config. defaulting to custom connections only
|
||||
if (!appConfig?.data) {
|
||||
return options.concat([addCustomConnection]);
|
||||
}
|
||||
|
||||
// app is disabled.
|
||||
if (appConfig.data.disabled) return options;
|
||||
|
||||
// means only OAuth clients are allowed for connection creation and there is OAuth client
|
||||
if (
|
||||
!appConfig?.data ||
|
||||
(!appConfig.data?.disabled && appConfig.data?.customConnectionAllowed)
|
||||
appConfig.data.useOnlyPredefinedAuthClients === true &&
|
||||
appOAuthClients.data.length > 0
|
||||
) {
|
||||
options.push({
|
||||
label: formatMessage('chooseConnectionSubstep.addNewConnection'),
|
||||
value: ADD_CONNECTION_VALUE,
|
||||
});
|
||||
return options.concat([addConnectionWithOAuthClient]);
|
||||
}
|
||||
|
||||
if (appConfig?.data?.connectionAllowed) {
|
||||
options.push({
|
||||
label: formatMessage('chooseConnectionSubstep.addNewSharedConnection'),
|
||||
value: ADD_SHARED_CONNECTION_VALUE,
|
||||
});
|
||||
// means there is no OAuth client. so we don't show the `addConnectionWithOAuthClient`
|
||||
if (
|
||||
appConfig.data.useOnlyPredefinedAuthClients === true &&
|
||||
appOAuthClients.data.length === 0
|
||||
) {
|
||||
return options;
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [data, formatMessage, appConfig?.data]);
|
||||
if (appOAuthClients.data.length === 0) {
|
||||
return options.concat([addCustomConnection]);
|
||||
}
|
||||
|
||||
const handleClientClick = async (appAuthClientId) => {
|
||||
return options.concat([addCustomConnection, addConnectionWithOAuthClient]);
|
||||
}, [data, formatMessage, appConfig, appOAuthClients]);
|
||||
|
||||
const handleClientClick = async (oauthClientId) => {
|
||||
try {
|
||||
const response = await authenticate?.({
|
||||
appAuthClientId,
|
||||
oauthClientId,
|
||||
});
|
||||
const connectionId = response?.createConnection.id;
|
||||
|
||||
@@ -162,10 +187,7 @@ function ChooseConnectionSubstep(props) {
|
||||
const handleChange = React.useCallback(
|
||||
async (event, selectedOption) => {
|
||||
if (typeof selectedOption === 'object') {
|
||||
// TODO: try to simplify type casting below.
|
||||
const typedSelectedOption = selectedOption;
|
||||
const option = typedSelectedOption;
|
||||
const connectionId = option?.value;
|
||||
const connectionId = selectedOption?.value;
|
||||
|
||||
if (connectionId === ADD_CONNECTION_VALUE) {
|
||||
setShowAddConnectionDialog(true);
|
||||
@@ -270,7 +292,7 @@ function ChooseConnectionSubstep(props) {
|
||||
)}
|
||||
|
||||
{application && showAddSharedConnectionDialog && (
|
||||
<AppAuthClientsDialog
|
||||
<AppOAuthClientsDialog
|
||||
appKey={application.key}
|
||||
onClose={() => setShowAddSharedConnectionDialog(false)}
|
||||
onClientClick={handleClientClick}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MuiContainer from '@mui/material/Container';
|
||||
|
||||
export default function Container(props) {
|
||||
return <MuiContainer {...props} />;
|
||||
export default function Container({ maxWidth = 'lg', ...props }) {
|
||||
return <MuiContainer maxWidth={maxWidth} {...props} />;
|
||||
}
|
||||
|
||||
Container.defaultProps = {
|
||||
maxWidth: 'lg',
|
||||
Container.propTypes = {
|
||||
maxWidth: PropTypes.oneOf([
|
||||
'xs',
|
||||
'sm',
|
||||
'md',
|
||||
'lg',
|
||||
'xl',
|
||||
false,
|
||||
PropTypes.string,
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -7,37 +7,68 @@ import { Box, TextField } from './style';
|
||||
const noop = () => null;
|
||||
|
||||
function EditableTypography(props) {
|
||||
const { children, onConfirm = noop, sx, ...typographyProps } = props;
|
||||
const {
|
||||
children,
|
||||
onConfirm = noop,
|
||||
sx,
|
||||
iconColor = 'inherit',
|
||||
disabled = false,
|
||||
prefixValue = '',
|
||||
...typographyProps
|
||||
} = props;
|
||||
|
||||
const [editing, setEditing] = React.useState(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (disabled) return;
|
||||
|
||||
setEditing((editing) => !editing);
|
||||
}, []);
|
||||
}, [disabled]);
|
||||
|
||||
const handleTextFieldClick = React.useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const handleTextFieldKeyDown = React.useCallback(
|
||||
async (event) => {
|
||||
const target = event.target;
|
||||
if (event.key === 'Enter') {
|
||||
const eventKey = event.key;
|
||||
|
||||
if (eventKey === 'Enter') {
|
||||
if (target.value !== children) {
|
||||
await onConfirm(target.value);
|
||||
}
|
||||
|
||||
setEditing(false);
|
||||
}
|
||||
|
||||
if (eventKey === 'Escape') {
|
||||
setEditing(false);
|
||||
}
|
||||
},
|
||||
[children],
|
||||
[children, onConfirm],
|
||||
);
|
||||
|
||||
const handleTextFieldBlur = React.useCallback(
|
||||
async (event) => {
|
||||
const value = event.target.value;
|
||||
|
||||
if (value !== children) {
|
||||
await onConfirm(value);
|
||||
}
|
||||
|
||||
setEditing(false);
|
||||
},
|
||||
[onConfirm, children],
|
||||
);
|
||||
let component = <Typography {...typographyProps}>{children}</Typography>;
|
||||
|
||||
let component = (
|
||||
<Typography {...typographyProps}>
|
||||
{prefixValue}
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
if (editing) {
|
||||
component = (
|
||||
<TextField
|
||||
@@ -51,18 +82,24 @@ function EditableTypography(props) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box sx={sx} onClick={handleClick} editing={editing}>
|
||||
<EditIcon sx={{ mr: 1 }} />
|
||||
|
||||
return (
|
||||
<Box sx={sx} onClick={handleClick} editing={editing} disabled={disabled}>
|
||||
{component}
|
||||
|
||||
{!disabled && editing === false && (
|
||||
<EditIcon fontSize="small" color={iconColor} sx={{ ml: 1 }} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
EditableTypography.propTypes = {
|
||||
children: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
iconColor: PropTypes.oneOf(['action', 'inherit']),
|
||||
onConfirm: PropTypes.func,
|
||||
prefixValue: PropTypes.string,
|
||||
sx: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,17 +2,22 @@ import { styled } from '@mui/material/styles';
|
||||
import MuiBox from '@mui/material/Box';
|
||||
import MuiTextField from '@mui/material/TextField';
|
||||
import { inputClasses } from '@mui/material/Input';
|
||||
const boxShouldForwardProp = (prop) => !['editing'].includes(prop);
|
||||
|
||||
const boxShouldForwardProp = (prop) => !['editing', 'disabled'].includes(prop);
|
||||
|
||||
export const Box = styled(MuiBox, {
|
||||
shouldForwardProp: boxShouldForwardProp,
|
||||
})`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 300px;
|
||||
min-width: 300px;
|
||||
max-width: 90%;
|
||||
height: 33px;
|
||||
align-items: center;
|
||||
${({ disabled }) => !disabled && 'cursor: pointer;'}
|
||||
${({ editing }) => editing && 'border-bottom: 1px dashed #000;'}
|
||||
`;
|
||||
|
||||
export const TextField = styled(MuiTextField)({
|
||||
width: '100%',
|
||||
[`.${inputClasses.root}:before, .${inputClasses.root}:after, .${inputClasses.root}:hover`]:
|
||||
|
||||
@@ -27,6 +27,10 @@ function Editor(props) {
|
||||
connectionId: step.connection?.id,
|
||||
};
|
||||
|
||||
if (step.name || step.keyLabel) {
|
||||
payload.name = step.name || step.keyLabel;
|
||||
}
|
||||
|
||||
if (step.appKey) {
|
||||
payload.appKey = step.appKey;
|
||||
}
|
||||
|
||||
@@ -6,29 +6,36 @@ import Button from '@mui/material/Button';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import { EditorProvider } from 'contexts/Editor';
|
||||
import EditableTypography from 'components/EditableTypography';
|
||||
import Container from 'components/Container';
|
||||
import Editor from 'components/Editor';
|
||||
import Can from 'components/Can';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import * as URLS from 'config/urls';
|
||||
import { TopBar } from './style';
|
||||
import * as URLS from 'config/urls';
|
||||
import Can from 'components/Can';
|
||||
import Container from 'components/Container';
|
||||
import EditableTypography from 'components/EditableTypography';
|
||||
import Editor from 'components/Editor';
|
||||
import EditorNew from 'components/EditorNew/EditorNew';
|
||||
import useFlow from 'hooks/useFlow';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useUpdateFlow from 'hooks/useUpdateFlow';
|
||||
import useUpdateFlowStatus from 'hooks/useUpdateFlowStatus';
|
||||
import EditorNew from 'components/EditorNew/EditorNew';
|
||||
import useExportFlow from 'hooks/useExportFlow';
|
||||
import useDownloadJsonAsFile from 'hooks/useDownloadJsonAsFile';
|
||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
|
||||
const useNewFlowEditor = process.env.REACT_APP_USE_NEW_FLOW_EDITOR === 'true';
|
||||
|
||||
export default function EditorLayout() {
|
||||
const { flowId } = useParams();
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const { mutateAsync: updateFlow } = useUpdateFlow(flowId);
|
||||
const { mutateAsync: updateFlowStatus } = useUpdateFlowStatus(flowId);
|
||||
const { mutateAsync: exportFlow } = useExportFlow(flowId);
|
||||
const downloadJsonAsFile = useDownloadJsonAsFile();
|
||||
const { data, isLoading: isFlowLoading } = useFlow(flowId);
|
||||
const flow = data?.data;
|
||||
|
||||
@@ -38,6 +45,19 @@ export default function EditorLayout() {
|
||||
});
|
||||
};
|
||||
|
||||
const onExportFlow = async (name) => {
|
||||
const flowExport = await exportFlow();
|
||||
|
||||
downloadJsonAsFile({
|
||||
contents: flowExport.data,
|
||||
name: flowExport.data.name,
|
||||
});
|
||||
|
||||
enqueueSnackbar(formatMessage('flowEditor.flowSuccessfullyExported'), {
|
||||
variant: 'success',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBar
|
||||
@@ -72,6 +92,7 @@ export default function EditorLayout() {
|
||||
variant="body1"
|
||||
onConfirm={onFlowNameUpdate}
|
||||
noWrap
|
||||
iconColor="action"
|
||||
sx={{ display: 'flex', flex: 1, maxWidth: '50vw', ml: 2 }}
|
||||
>
|
||||
{flow?.name}
|
||||
@@ -79,7 +100,23 @@ export default function EditorLayout() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box pr={1}>
|
||||
<Box pr={1} display="flex" gap={1}>
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<Button
|
||||
disabled={!allowed || !flow}
|
||||
variant="outlined"
|
||||
color="info"
|
||||
size="small"
|
||||
onClick={onExportFlow}
|
||||
data-test="export-flow-button"
|
||||
startIcon={<DownloadIcon />}
|
||||
>
|
||||
{formatMessage('flowEditor.export')}
|
||||
</Button>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="publish" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<Button
|
||||
|
||||
@@ -90,6 +90,10 @@ const EditorNew = ({ flow }) => {
|
||||
connectionId: step.connection?.id,
|
||||
};
|
||||
|
||||
if (step.name || step.keyLabel) {
|
||||
payload.name = step.name || step.keyLabel;
|
||||
}
|
||||
|
||||
if (step.appKey) {
|
||||
payload.appKey = step.appKey;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import Tab from '@mui/material/Tab';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
|
||||
import TabPanel from 'components/TabPanel';
|
||||
import SearchableJSONViewer from 'components/SearchableJSONViewer';
|
||||
@@ -100,6 +101,10 @@ function ExecutionStep(props) {
|
||||
|
||||
const hasError = !!executionStep.errorDetails;
|
||||
|
||||
const stepTypeName = isTrigger
|
||||
? formatMessage('flowStep.triggerType')
|
||||
: formatMessage('flowStep.actionType');
|
||||
|
||||
return (
|
||||
<Wrapper elevation={1} data-test="execution-step">
|
||||
<Header>
|
||||
@@ -119,13 +124,20 @@ function ExecutionStep(props) {
|
||||
<ExecutionStepId id={executionStep.step.id} />
|
||||
|
||||
<Box flex="1" gridArea="step">
|
||||
<Typography variant="caption">
|
||||
{isTrigger && formatMessage('flowStep.triggerType')}
|
||||
{isAction && formatMessage('flowStep.actionType')}
|
||||
<Typography
|
||||
component={Stack}
|
||||
direction="row"
|
||||
variant="stepApp"
|
||||
alignItems="center"
|
||||
gap={0.5}
|
||||
>
|
||||
<Chip label={stepTypeName} variant="stepType" size="small" />
|
||||
|
||||
{app?.name}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
{step.position}. {app?.name}
|
||||
{step.position}. {step.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import * as URLS from 'config/urls';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useDuplicateFlow from 'hooks/useDuplicateFlow';
|
||||
import useDeleteFlow from 'hooks/useDeleteFlow';
|
||||
import useExportFlow from 'hooks/useExportFlow';
|
||||
import useDownloadJsonAsFile from 'hooks/useDownloadJsonAsFile';
|
||||
|
||||
function ContextMenu(props) {
|
||||
const { flowId, onClose, anchorEl, onDuplicateFlow, onDeleteFlow, appKey } =
|
||||
@@ -20,7 +22,9 @@ function ContextMenu(props) {
|
||||
const formatMessage = useFormatMessage();
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: duplicateFlow } = useDuplicateFlow(flowId);
|
||||
const { mutateAsync: deleteFlow } = useDeleteFlow();
|
||||
const { mutateAsync: deleteFlow } = useDeleteFlow(flowId);
|
||||
const { mutateAsync: exportFlow } = useExportFlow(flowId);
|
||||
const downloadJsonAsFile = useDownloadJsonAsFile();
|
||||
|
||||
const onFlowDuplicate = React.useCallback(async () => {
|
||||
await duplicateFlow();
|
||||
@@ -51,7 +55,7 @@ function ContextMenu(props) {
|
||||
]);
|
||||
|
||||
const onFlowDelete = React.useCallback(async () => {
|
||||
await deleteFlow(flowId);
|
||||
await deleteFlow();
|
||||
|
||||
if (appKey) {
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -65,7 +69,30 @@ function ContextMenu(props) {
|
||||
|
||||
onDeleteFlow?.();
|
||||
onClose();
|
||||
}, [flowId, onClose, deleteFlow, queryClient, onDeleteFlow]);
|
||||
}, [
|
||||
deleteFlow,
|
||||
appKey,
|
||||
enqueueSnackbar,
|
||||
formatMessage,
|
||||
onDeleteFlow,
|
||||
onClose,
|
||||
queryClient,
|
||||
]);
|
||||
|
||||
const onFlowExport = React.useCallback(async () => {
|
||||
const flowExport = await exportFlow();
|
||||
|
||||
downloadJsonAsFile({
|
||||
contents: flowExport.data,
|
||||
name: flowExport.data.name,
|
||||
});
|
||||
|
||||
enqueueSnackbar(formatMessage('flow.successfullyExported'), {
|
||||
variant: 'success',
|
||||
});
|
||||
|
||||
onClose();
|
||||
}, [exportFlow, downloadJsonAsFile, enqueueSnackbar, formatMessage, onClose]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
@@ -90,6 +117,14 @@ function ContextMenu(props) {
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowExport}>
|
||||
{formatMessage('flow.export')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="delete" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowDelete}>
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as React from 'react';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Button from '@mui/material/Button';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import List from '@mui/material/List';
|
||||
@@ -18,6 +19,7 @@ import { isEqual } from 'lodash';
|
||||
import { EditorContext } from 'contexts/Editor';
|
||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||
import TestSubstep from 'components/TestSubstep';
|
||||
import EditableTypography from 'components/EditableTypography';
|
||||
import FlowSubstep from 'components/FlowSubstep';
|
||||
import ChooseAppAndEventSubstep from 'components/ChooseAppAndEventSubstep';
|
||||
import ChooseConnectionSubstep from 'components/ChooseConnectionSubstep';
|
||||
@@ -106,10 +108,9 @@ function generateValidationSchema(substeps) {
|
||||
}
|
||||
|
||||
function FlowStep(props) {
|
||||
const { collapsed, onChange, onContinue, flowId } = props;
|
||||
const { collapsed, onChange, onContinue, flowId, step } = props;
|
||||
const editorContext = React.useContext(EditorContext);
|
||||
const contextButtonRef = React.useRef(null);
|
||||
const step = props.step;
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const isTrigger = step.type === 'trigger';
|
||||
const isAction = step.type === 'action';
|
||||
@@ -117,6 +118,10 @@ function FlowStep(props) {
|
||||
const [currentSubstep, setCurrentSubstep] = React.useState(0);
|
||||
const useAppsOptions = {};
|
||||
|
||||
const stepTypeName = isTrigger
|
||||
? formatMessage('flowStep.triggerType')
|
||||
: formatMessage('flowStep.actionType');
|
||||
|
||||
if (isTrigger) {
|
||||
useAppsOptions.onlyWithTriggers = true;
|
||||
}
|
||||
@@ -183,6 +188,13 @@ function FlowStep(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleStepNameChange = async (name) => {
|
||||
await onChange({
|
||||
...step,
|
||||
name,
|
||||
});
|
||||
};
|
||||
|
||||
const stepValidationSchema = React.useMemo(
|
||||
() => generateValidationSchema(substeps),
|
||||
[substeps],
|
||||
@@ -226,7 +238,7 @@ function FlowStep(props) {
|
||||
data-test="flow-step"
|
||||
>
|
||||
<Header collapsed={collapsed}>
|
||||
<Stack direction="row" alignItems="center" gap={2}>
|
||||
<Stack direction="row" alignItems="center" gap={3}>
|
||||
<AppIconWrapper>
|
||||
<AppIcon
|
||||
url={app?.iconUrl}
|
||||
@@ -239,17 +251,30 @@ function FlowStep(props) {
|
||||
</AppIconStatusIconWrapper>
|
||||
</AppIconWrapper>
|
||||
|
||||
<div>
|
||||
<Typography variant="caption">
|
||||
{isTrigger
|
||||
? formatMessage('flowStep.triggerType')
|
||||
: formatMessage('flowStep.actionType')}
|
||||
<Stack direction="column" gap={0.5} sx={{ width: '100%' }}>
|
||||
<Typography
|
||||
component={Stack}
|
||||
direction="row"
|
||||
variant="stepApp"
|
||||
alignItems="center"
|
||||
gap={0.5}
|
||||
>
|
||||
<Chip label={stepTypeName} variant="stepType" size="small" />
|
||||
|
||||
{app?.name}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
{step.position}. {app?.name}
|
||||
</Typography>
|
||||
</div>
|
||||
<EditableTypography
|
||||
iconPosition="end"
|
||||
iconSize="small"
|
||||
variant="body2"
|
||||
onConfirm={handleStepNameChange}
|
||||
prefixValue={`${step.position}. `}
|
||||
disabled={editorContext.readOnly || collapsed}
|
||||
>
|
||||
{step.name}
|
||||
</EditableTypography>
|
||||
</Stack>
|
||||
|
||||
<Box display="flex" flex={1} justifyContent="end">
|
||||
{/* as there are no other actions besides "delete step", we hide the context menu. */}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import MuiListItemButton from '@mui/material/ListItemButton';
|
||||
import MuiTypography from '@mui/material/Typography';
|
||||
|
||||
export const ListItemButton = styled(MuiListItemButton)`
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export const Typography = styled(MuiTypography)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||
import PropTypes from 'prop-types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
const noop = () => null;
|
||||
|
||||
@@ -18,6 +19,8 @@ function Form(props) {
|
||||
...formProps
|
||||
} = props;
|
||||
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
const methods = useForm({
|
||||
defaultValues,
|
||||
reValidateMode,
|
||||
@@ -25,6 +28,8 @@ function Form(props) {
|
||||
mode,
|
||||
});
|
||||
|
||||
const { setError } = methods;
|
||||
|
||||
const form = useWatch({ control: methods.control });
|
||||
const prevDefaultValues = React.useRef(defaultValues);
|
||||
|
||||
@@ -44,9 +49,53 @@ function Form(props) {
|
||||
}
|
||||
}, [defaultValues]);
|
||||
|
||||
const handleErrors = React.useCallback(
|
||||
function (errors) {
|
||||
if (!errors) return;
|
||||
|
||||
let shouldSetGenericGeneralError = true;
|
||||
const fieldNames = Object.keys(defaultValues);
|
||||
|
||||
Object.entries(errors).forEach(([fieldName, fieldErrors]) => {
|
||||
if (fieldNames.includes(fieldName) && Array.isArray(fieldErrors)) {
|
||||
shouldSetGenericGeneralError = false;
|
||||
setError(fieldName, {
|
||||
type: 'fieldRequestError',
|
||||
message: fieldErrors.join(', '),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// in case of general errors
|
||||
if (Array.isArray(errors.general)) {
|
||||
for (const error of errors.general) {
|
||||
shouldSetGenericGeneralError = false;
|
||||
setError('root.general', { type: 'requestError', message: error });
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSetGenericGeneralError) {
|
||||
setError('root.general', {
|
||||
type: 'requestError',
|
||||
message: formatMessage('form.genericError'),
|
||||
});
|
||||
}
|
||||
},
|
||||
[defaultValues, formatMessage, setError],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit(onSubmit)} {...formProps}>
|
||||
<form
|
||||
onSubmit={methods.handleSubmit(async (data, event) => {
|
||||
try {
|
||||
return await onSubmit?.(data);
|
||||
} catch (errors) {
|
||||
handleErrors(errors);
|
||||
}
|
||||
})}
|
||||
{...formProps}
|
||||
>
|
||||
{render ? render(methods) : children}
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -2,11 +2,10 @@ import * as React from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Alert } from '@mui/material';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import Link from '@mui/material/Link';
|
||||
|
||||
@@ -16,21 +15,41 @@ import * as URLS from 'config/urls';
|
||||
import Form from 'components/Form';
|
||||
import TextField from 'components/TextField';
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
fullName: yup.string().trim().required('installationForm.mandatoryInput'),
|
||||
email: yup
|
||||
.string()
|
||||
.trim()
|
||||
.email('installationForm.validateEmail')
|
||||
.required('installationForm.mandatoryInput'),
|
||||
password: yup.string().required('installationForm.mandatoryInput'),
|
||||
confirmPassword: yup
|
||||
.string()
|
||||
.required('installationForm.mandatoryInput')
|
||||
.oneOf([yup.ref('password')], 'installationForm.passwordsMustMatch'),
|
||||
});
|
||||
const getValidationSchema = (formatMessage) => {
|
||||
const getMandatoryInputMessage = (inputNameId) =>
|
||||
formatMessage('installationForm.mandatoryInput', {
|
||||
inputName: formatMessage(inputNameId),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
return yup.object().shape({
|
||||
fullName: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(
|
||||
getMandatoryInputMessage('installationForm.fullNameFieldLabel'),
|
||||
),
|
||||
email: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(getMandatoryInputMessage('installationForm.emailFieldLabel'))
|
||||
.email(formatMessage('installationForm.validateEmail')),
|
||||
password: yup
|
||||
.string()
|
||||
.required(getMandatoryInputMessage('installationForm.passwordFieldLabel'))
|
||||
.min(6, formatMessage('installationForm.passwordMinLength')),
|
||||
confirmPassword: yup
|
||||
.string()
|
||||
.required(
|
||||
getMandatoryInputMessage('installationForm.confirmPasswordFieldLabel'),
|
||||
)
|
||||
.oneOf(
|
||||
[yup.ref('password')],
|
||||
formatMessage('installationForm.passwordsMustMatch'),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
fullName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -39,7 +58,7 @@ const initialValues = {
|
||||
|
||||
function InstallationForm() {
|
||||
const formatMessage = useFormatMessage();
|
||||
const install = useInstallation();
|
||||
const { mutateAsync: install, isSuccess, isPending } = useInstallation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleOnRedirect = () => {
|
||||
@@ -48,21 +67,16 @@ function InstallationForm() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (values) => {
|
||||
const { fullName, email, password } = values;
|
||||
const handleSubmit = async ({ fullName, email, password }) => {
|
||||
try {
|
||||
await install.mutateAsync({
|
||||
await install({
|
||||
fullName,
|
||||
email,
|
||||
password,
|
||||
});
|
||||
} catch (error) {
|
||||
enqueueSnackbar(
|
||||
error?.message || formatMessage('installationForm.error'),
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
const errors = error?.response?.data?.errors;
|
||||
throw errors || error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,11 +96,13 @@ function InstallationForm() {
|
||||
{formatMessage('installationForm.title')}
|
||||
</Typography>
|
||||
<Form
|
||||
defaultValues={initialValues}
|
||||
automaticValidation={false}
|
||||
noValidate
|
||||
defaultValues={defaultValues}
|
||||
onSubmit={handleSubmit}
|
||||
resolver={yupResolver(validationSchema)}
|
||||
resolver={yupResolver(getValidationSchema(formatMessage))}
|
||||
mode="onChange"
|
||||
render={({ formState: { errors, touchedFields } }) => (
|
||||
render={({ formState: { errors } }) => (
|
||||
<>
|
||||
<TextField
|
||||
label={formatMessage('installationForm.fullNameFieldLabel')}
|
||||
@@ -95,19 +111,12 @@ function InstallationForm() {
|
||||
margin="dense"
|
||||
autoComplete="fullName"
|
||||
data-test="fullName-text-field"
|
||||
error={touchedFields.fullName && !!errors?.fullName}
|
||||
helperText={
|
||||
touchedFields.fullName && errors?.fullName?.message
|
||||
? formatMessage(errors?.fullName?.message, {
|
||||
inputName: formatMessage(
|
||||
'installationForm.fullNameFieldLabel',
|
||||
),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.fullName}
|
||||
helperText={errors?.fullName?.message}
|
||||
required
|
||||
readOnly={install.isSuccess}
|
||||
readOnly={isSuccess}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={formatMessage('installationForm.emailFieldLabel')}
|
||||
name="email"
|
||||
@@ -115,19 +124,12 @@ function InstallationForm() {
|
||||
margin="dense"
|
||||
autoComplete="email"
|
||||
data-test="email-text-field"
|
||||
error={touchedFields.email && !!errors?.email}
|
||||
helperText={
|
||||
touchedFields.email && errors?.email?.message
|
||||
? formatMessage(errors?.email?.message, {
|
||||
inputName: formatMessage(
|
||||
'installationForm.emailFieldLabel',
|
||||
),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.email}
|
||||
helperText={errors?.email?.message}
|
||||
required
|
||||
readOnly={install.isSuccess}
|
||||
readOnly={isSuccess}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={formatMessage('installationForm.passwordFieldLabel')}
|
||||
name="password"
|
||||
@@ -135,19 +137,12 @@ function InstallationForm() {
|
||||
margin="dense"
|
||||
type="password"
|
||||
data-test="password-text-field"
|
||||
error={touchedFields.password && !!errors?.password}
|
||||
helperText={
|
||||
touchedFields.password && errors?.password?.message
|
||||
? formatMessage(errors?.password?.message, {
|
||||
inputName: formatMessage(
|
||||
'installationForm.passwordFieldLabel',
|
||||
),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.password}
|
||||
helperText={errors?.password?.message}
|
||||
required
|
||||
readOnly={install.isSuccess}
|
||||
readOnly={isSuccess}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={formatMessage(
|
||||
'installationForm.confirmPasswordFieldLabel',
|
||||
@@ -157,52 +152,53 @@ function InstallationForm() {
|
||||
margin="dense"
|
||||
type="password"
|
||||
data-test="repeat-password-text-field"
|
||||
error={touchedFields.confirmPassword && !!errors?.confirmPassword}
|
||||
helperText={
|
||||
touchedFields.confirmPassword &&
|
||||
errors?.confirmPassword?.message
|
||||
? formatMessage(errors?.confirmPassword?.message, {
|
||||
inputName: formatMessage(
|
||||
'installationForm.confirmPasswordFieldLabel',
|
||||
),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.confirmPassword}
|
||||
helperText={errors?.confirmPassword?.message}
|
||||
required
|
||||
readOnly={install.isSuccess}
|
||||
readOnly={isSuccess}
|
||||
/>
|
||||
|
||||
{errors?.root?.general && (
|
||||
<Alert data-test="error-alert" severity="error" sx={{ mt: 2 }}>
|
||||
{errors.root.general.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<Alert
|
||||
data-test="success-alert"
|
||||
severity="success"
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
{formatMessage('installationForm.success', {
|
||||
link: (str) => (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={URLS.LOGIN}
|
||||
onClick={handleOnRedirect}
|
||||
replace
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
})}
|
||||
</Alert>
|
||||
)}
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ boxShadow: 2, mt: 3 }}
|
||||
loading={install.isPending}
|
||||
disabled={install.isSuccess}
|
||||
sx={{ boxShadow: 2, mt: 2 }}
|
||||
loading={isPending}
|
||||
disabled={isSuccess}
|
||||
fullWidth
|
||||
data-test="signUp-button"
|
||||
data-test="installation-button"
|
||||
>
|
||||
{formatMessage('installationForm.submit')}
|
||||
</LoadingButton>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
{install.isSuccess && (
|
||||
<Alert data-test="success-alert" severity="success" sx={{ mt: 3 }}>
|
||||
{formatMessage('installationForm.success', {
|
||||
link: (str) => (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={URLS.LOGIN}
|
||||
onClick={handleOnRedirect}
|
||||
replace
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
})}
|
||||
</Alert>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
43
packages/web/src/components/OAuthClientsDialog/index.ee.jsx
Normal file
43
packages/web/src/components/OAuthClientsDialog/index.ee.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import * as React from 'react';
|
||||
import useOAuthClients from 'hooks/useOAuthClients';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
function AppOAuthClientsDialog(props) {
|
||||
const { appKey, onClientClick, onClose } = props;
|
||||
const { data: appOAuthClients } = useOAuthClients(appKey);
|
||||
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
if (!appOAuthClients?.data.length) return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true}>
|
||||
<DialogTitle>{formatMessage('appOAuthClientsDialog.title')}</DialogTitle>
|
||||
|
||||
<List sx={{ pt: 0 }}>
|
||||
{appOAuthClients.data.map((oauthClient) => (
|
||||
<ListItem disableGutters key={oauthClient.id}>
|
||||
<ListItemButton onClick={() => onClientClick(oauthClient.id)}>
|
||||
<ListItemText primary={oauthClient.name} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
AppOAuthClientsDialog.propTypes = {
|
||||
appKey: PropTypes.string.isRequired,
|
||||
onClientClick: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AppOAuthClientsDialog;
|
||||
@@ -39,14 +39,14 @@ const PermissionCatalogFieldLoader = () => {
|
||||
{[...Array(5)].map((action, index) => (
|
||||
<TableCell key={index} align="center">
|
||||
<Typography variant="subtitle2">
|
||||
<ControlledCheckbox name="value" />
|
||||
<ControlledCheckbox name="value" disabled />
|
||||
</Typography>
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" gap={1} justifyContent="right">
|
||||
<IconButton color="info" size="small">
|
||||
<IconButton color="info" size="small" disabled>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
@@ -21,13 +21,15 @@ const PermissionCatalogField = ({
|
||||
name = 'permissions',
|
||||
disabled = false,
|
||||
syncIsCreator = false,
|
||||
loading = false,
|
||||
}) => {
|
||||
const { data, isLoading: isPermissionCatalogLoading } =
|
||||
usePermissionCatalog();
|
||||
const permissionCatalog = data?.data;
|
||||
const [dialogName, setDialogName] = React.useState();
|
||||
|
||||
if (isPermissionCatalogLoading) return <PermissionCatalogFieldLoader />;
|
||||
if (isPermissionCatalogLoading || loading)
|
||||
return <PermissionCatalogFieldLoader />;
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
@@ -118,6 +120,7 @@ PermissionCatalogField.propTypes = {
|
||||
name: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
syncIsCreator: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default PermissionCatalogField;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
|
||||
@@ -12,24 +13,41 @@ import Form from 'components/Form';
|
||||
import TextField from 'components/TextField';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useCreateAccessToken from 'hooks/useCreateAccessToken';
|
||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
import useRegisterUser from 'hooks/useRegisterUser';
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
fullName: yup.string().trim().required('signupForm.mandatoryInput'),
|
||||
email: yup
|
||||
.string()
|
||||
.trim()
|
||||
.email('signupForm.validateEmail')
|
||||
.required('signupForm.mandatoryInput'),
|
||||
password: yup.string().required('signupForm.mandatoryInput'),
|
||||
confirmPassword: yup
|
||||
.string()
|
||||
.required('signupForm.mandatoryInput')
|
||||
.oneOf([yup.ref('password')], 'signupForm.passwordsMustMatch'),
|
||||
});
|
||||
const getValidationSchema = (formatMessage) => {
|
||||
const getMandatoryInputMessage = (inputNameId) =>
|
||||
formatMessage('signupForm.mandatoryInput', {
|
||||
inputName: formatMessage(inputNameId),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
return yup.object().shape({
|
||||
fullName: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(getMandatoryInputMessage('signupForm.fullNameFieldLabel')),
|
||||
email: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required(getMandatoryInputMessage('signupForm.emailFieldLabel'))
|
||||
.email(formatMessage('signupForm.validateEmail')),
|
||||
password: yup
|
||||
.string()
|
||||
.required(getMandatoryInputMessage('signupForm.passwordFieldLabel'))
|
||||
.min(6, formatMessage('signupForm.passwordMinLength')),
|
||||
confirmPassword: yup
|
||||
.string()
|
||||
.required(
|
||||
getMandatoryInputMessage('signupForm.confirmPasswordFieldLabel'),
|
||||
)
|
||||
.oneOf(
|
||||
[yup.ref('password')],
|
||||
formatMessage('signupForm.passwordsMustMatch'),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
fullName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -40,7 +58,6 @@ function SignUpForm() {
|
||||
const navigate = useNavigate();
|
||||
const authentication = useAuthentication();
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const { mutateAsync: registerUser, isPending: isRegisterUserPending } =
|
||||
useRegisterUser();
|
||||
const { mutateAsync: createAccessToken, isPending: loginLoading } =
|
||||
@@ -67,27 +84,8 @@ function SignUpForm() {
|
||||
const { token } = data;
|
||||
authentication.updateToken(token);
|
||||
} catch (error) {
|
||||
const errors = error?.response?.data?.errors
|
||||
? Object.values(error.response.data.errors)
|
||||
: [];
|
||||
|
||||
if (errors.length) {
|
||||
for (const [error] of errors) {
|
||||
enqueueSnackbar(error, {
|
||||
variant: 'error',
|
||||
SnackbarProps: {
|
||||
'data-test': 'snackbar-sign-up-error',
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar(error?.message || formatMessage('signupForm.error'), {
|
||||
variant: 'error',
|
||||
SnackbarProps: {
|
||||
'data-test': 'snackbar-sign-up-error',
|
||||
},
|
||||
});
|
||||
}
|
||||
const errors = error?.response?.data?.errors;
|
||||
throw errors || error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,11 +106,13 @@ function SignUpForm() {
|
||||
</Typography>
|
||||
|
||||
<Form
|
||||
defaultValues={initialValues}
|
||||
automaticValidation={false}
|
||||
noValidate
|
||||
defaultValues={defaultValues}
|
||||
onSubmit={handleSubmit}
|
||||
resolver={yupResolver(validationSchema)}
|
||||
resolver={yupResolver(getValidationSchema(formatMessage))}
|
||||
mode="onChange"
|
||||
render={({ formState: { errors, touchedFields } }) => (
|
||||
render={({ formState: { errors } }) => (
|
||||
<>
|
||||
<TextField
|
||||
label={formatMessage('signupForm.fullNameFieldLabel')}
|
||||
@@ -121,14 +121,9 @@ function SignUpForm() {
|
||||
margin="dense"
|
||||
autoComplete="fullName"
|
||||
data-test="fullName-text-field"
|
||||
error={touchedFields.fullName && !!errors?.fullName}
|
||||
helperText={
|
||||
touchedFields.fullName && errors?.fullName?.message
|
||||
? formatMessage(errors?.fullName?.message, {
|
||||
inputName: formatMessage('signupForm.fullNameFieldLabel'),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.fullName}
|
||||
helperText={errors?.fullName?.message}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -138,14 +133,9 @@ function SignUpForm() {
|
||||
margin="dense"
|
||||
autoComplete="email"
|
||||
data-test="email-text-field"
|
||||
error={touchedFields.email && !!errors?.email}
|
||||
helperText={
|
||||
touchedFields.email && errors?.email?.message
|
||||
? formatMessage(errors?.email?.message, {
|
||||
inputName: formatMessage('signupForm.emailFieldLabel'),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.email}
|
||||
helperText={errors?.email?.message}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -154,14 +144,9 @@ function SignUpForm() {
|
||||
fullWidth
|
||||
margin="dense"
|
||||
type="password"
|
||||
error={touchedFields.password && !!errors?.password}
|
||||
helperText={
|
||||
touchedFields.password && errors?.password?.message
|
||||
? formatMessage(errors?.password?.message, {
|
||||
inputName: formatMessage('signupForm.passwordFieldLabel'),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.password}
|
||||
helperText={errors?.password?.message}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@@ -170,19 +155,21 @@ function SignUpForm() {
|
||||
fullWidth
|
||||
margin="dense"
|
||||
type="password"
|
||||
error={touchedFields.confirmPassword && !!errors?.confirmPassword}
|
||||
helperText={
|
||||
touchedFields.confirmPassword &&
|
||||
errors?.confirmPassword?.message
|
||||
? formatMessage(errors?.confirmPassword?.message, {
|
||||
inputName: formatMessage(
|
||||
'signupForm.confirmPasswordFieldLabel',
|
||||
),
|
||||
})
|
||||
: ''
|
||||
}
|
||||
error={!!errors?.confirmPassword}
|
||||
helperText={errors?.confirmPassword?.message}
|
||||
required
|
||||
/>
|
||||
|
||||
{errors?.root?.general && (
|
||||
<Alert
|
||||
data-test="alert-sign-up-error"
|
||||
severity="error"
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
{errors.root.general.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
variant="contained"
|
||||
|
||||
@@ -67,17 +67,12 @@ export default function SplitButton(props) {
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
placement="bottom-end"
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom' ? 'center top' : 'center bottom',
|
||||
}}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList autoFocusItem>
|
||||
|
||||
Reference in New Issue
Block a user