refactor(web): remove typescript

This commit is contained in:
Ali BARIN
2024-02-27 15:23:23 +00:00
parent 636870a075
commit b3ae2d2748
337 changed files with 2067 additions and 4997 deletions

View File

@@ -15,11 +15,9 @@ import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import useFormatMessage from 'hooks/useFormatMessage';
import { GET_APP } from 'graphql/queries/get-app';
import * as URLS from 'config/urls';
import AppIcon from 'components/AppIcon';
import Container from 'components/Container';
import PageTitle from 'components/PageTitle';
@@ -27,17 +25,11 @@ import AdminApplicationSettings from 'components/AdminApplicationSettings';
import AdminApplicationAuthClients from 'components/AdminApplicationAuthClients';
import AdminApplicationCreateAuthClient from 'components/AdminApplicationCreateAuthClient';
import AdminApplicationUpdateAuthClient from 'components/AdminApplicationUpdateAuthClient';
type AdminApplicationParams = {
appKey: string;
};
export default function AdminApplication(): React.ReactElement | null {
export default function AdminApplication() {
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
const formatMessage = useFormatMessage();
const navigate = useNavigate();
const connectionsPathMatch = useMatch({
path: URLS.ADMIN_APP_CONNECTIONS_PATTERN,
end: false,
@@ -50,16 +42,11 @@ export default function AdminApplication(): React.ReactElement | null {
path: URLS.ADMIN_APP_AUTH_CLIENTS_PATTERN,
end: false,
});
const { appKey } = useParams() as AdminApplicationParams;
const { appKey } = useParams();
const { data, loading } = useQuery(GET_APP, { variables: { key: appKey } });
const app = data?.getApp || {};
const goToAuthClientsPage = () => navigate('auth-clients');
if (loading) return null;
return (
<>
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>

View File

@@ -3,17 +3,13 @@ import Grid from '@mui/material/Grid';
import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import { useQuery } from '@apollo/client';
import { IApp } from 'types';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import SearchInput from 'components/SearchInput';
import AppRow from 'components/AppRow';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import { GET_APPS } from 'graphql/queries/get-apps';
function AdminApplications() {
const formatMessage = useFormatMessage();
const [appName, setAppName] = React.useState(null);
@@ -21,11 +17,9 @@ function AdminApplications() {
variables: { name: appName },
});
const apps = data?.getApps;
const onSearchChange = React.useCallback((event) => {
setAppName(event.target.value);
}, []);
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>
@@ -50,7 +44,7 @@ function AdminApplications() {
)}
{!appsLoading &&
apps?.map((app: IApp) => (
apps?.map((app) => (
<Grid item xs={12} key={app.name}>
<AppRow application={app} url={URLS.ADMIN_APP(app.key)} />
</Grid>
@@ -59,5 +53,4 @@ function AdminApplications() {
</Container>
);
}
export default AdminApplications;

View File

@@ -17,12 +17,10 @@ import Grid from '@mui/material/Grid';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import AddIcon from '@mui/icons-material/Add';
import useFormatMessage from 'hooks/useFormatMessage';
import useAppConfig from 'hooks/useAppConfig.ee';
import { GET_APP } from 'graphql/queries/get-app';
import * as URLS from 'config/urls';
import SplitButton from 'components/SplitButton';
import ConditionalIconButton from 'components/ConditionalIconButton';
import AppConnections from 'components/AppConnections';
@@ -31,23 +29,9 @@ import AddAppConnection from 'components/AddAppConnection';
import AppIcon from 'components/AppIcon';
import Container from 'components/Container';
import PageTitle from 'components/PageTitle';
type ApplicationParams = {
appKey: string;
connectionId?: string;
};
type ConnectionOption = {
key: string;
label: string;
'data-test': string;
to: string;
};
const ReconnectConnection = (props: any): React.ReactElement => {
const ReconnectConnection = (props) => {
const { application, onClose } = props;
const { connectionId } = useParams() as ApplicationParams;
const { connectionId } = useParams();
return (
<AddAppConnection
onClose={onClose}
@@ -56,8 +40,7 @@ const ReconnectConnection = (props: any): React.ReactElement => {
/>
);
};
export default function Application(): React.ReactElement | null {
export default function Application() {
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
const formatMessage = useFormatMessage();
@@ -67,19 +50,17 @@ export default function Application(): React.ReactElement | null {
});
const flowsPathMatch = useMatch({ path: URLS.APP_FLOWS_PATTERN, end: false });
const [searchParams] = useSearchParams();
const { appKey } = useParams() as ApplicationParams;
const { appKey } = useParams();
const navigate = useNavigate();
const { data, loading } = useQuery(GET_APP, { variables: { key: appKey } });
const { appConfig } = useAppConfig(appKey);
const connectionId = searchParams.get('connectionId') || undefined;
const goToApplicationPage = () => navigate('connections');
const app = data?.getApp || {};
const connectionOptions = React.useMemo(() => {
const shouldHaveCustomConnection =
appConfig?.canConnect && appConfig?.canCustomConnect;
const options: ConnectionOption[] = [
const options = [
{
label: formatMessage('app.addConnection'),
key: 'addConnection',
@@ -87,7 +68,6 @@ export default function Application(): React.ReactElement | null {
to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.canConnect),
},
];
if (shouldHaveCustomConnection) {
options.push({
label: formatMessage('app.addCustomConnection'),
@@ -96,12 +76,9 @@ export default function Application(): React.ReactElement | null {
to: URLS.APP_ADD_CONNECTION(appKey),
});
}
return options;
}, [appKey, appConfig]);
if (loading) return null;
return (
<>
<Box sx={{ py: 3 }}>
@@ -132,7 +109,7 @@ export default function Application(): React.ReactElement | null {
component={Link}
to={URLS.CREATE_FLOW_WITH_APP_AND_CONNECTION(
appKey,
connectionId
connectionId,
)}
fullWidth
icon={<AddIcon />}

View File

@@ -6,8 +6,6 @@ import Grid from '@mui/material/Grid';
import Divider from '@mui/material/Divider';
import CircularProgress from '@mui/material/CircularProgress';
import AddIcon from '@mui/icons-material/Add';
import type { IApp } from 'types';
import Can from 'components/Can';
import NoResultFound from 'components/NoResultFound';
import ConditionalIconButton from 'components/ConditionalIconButton';
@@ -19,26 +17,21 @@ import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage';
import { GET_CONNECTED_APPS } from 'graphql/queries/get-connected-apps';
import * as URLS from 'config/urls';
export default function Applications(): React.ReactElement {
export default function Applications() {
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const [appName, setAppName] = React.useState(null);
const { data, loading } = useQuery(GET_CONNECTED_APPS, {
variables: { name: appName },
});
const apps: IApp[] = data?.getConnectedApps;
const apps = data?.getConnectedApps;
const hasApps = apps?.length;
const onSearchChange = React.useCallback((event) => {
setAppName(event.target.value);
}, []);
const goToApps = React.useCallback(() => {
navigate(URLS.APPS);
}, [navigate]);
return (
<Box sx={{ py: 3 }}>
<Container>
@@ -97,7 +90,7 @@ export default function Applications(): React.ReactElement {
)}
{!loading &&
apps?.map((app: IApp) => (
apps?.map((app) => (
<AppRow key={app.name} application={app} url={URLS.APP(app.key)} />
))}

View File

@@ -1,36 +1,25 @@
import { useMutation } from '@apollo/client';
import { TSamlAuthProvider, TSamlAuthProviderRole } from 'types';
import LoadingButton from '@mui/lab/LoadingButton';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import { useMemo } from 'react';
import Form from 'components/Form';
import { UPSERT_SAML_AUTH_PROVIDERS_ROLE_MAPPINGS } from 'graphql/mutations/upsert-saml-auth-providers-role-mappings';
import useFormatMessage from 'hooks/useFormatMessage';
import useSamlAuthProviderRoleMappings from 'hooks/useSamlAuthProviderRoleMappings';
import RoleMappingsFieldArray from './RoleMappingsFieldsArray';
type RoleMappingsProps = {
provider?: TSamlAuthProvider;
providerLoading: boolean;
};
function generateFormRoleMappings(roleMappings: TSamlAuthProviderRole[]) {
function generateFormRoleMappings(roleMappings) {
if (roleMappings.length === 0) {
return [{ roleId: '', remoteRoleName: '' }];
}
return roleMappings.map(({ roleId, remoteRoleName }) => ({
roleId,
remoteRoleName,
}));
}
function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
function RoleMappings({ provider, providerLoading }) {
const formatMessage = useFormatMessage();
const enqueueSnackbar = useEnqueueSnackbar();
const { roleMappings, loading: roleMappingsLoading } =
@@ -39,8 +28,7 @@ function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
upsertSamlAuthProvidersRoleMappings,
{ loading: upsertRoleMappingsLoading },
] = useMutation(UPSERT_SAML_AUTH_PROVIDERS_ROLE_MAPPINGS);
const handleRoleMappingsUpdate = async (values: any) => {
const handleRoleMappingsUpdate = async (values) => {
try {
if (provider?.id) {
await upsertSamlAuthProvidersRoleMappings({
@@ -48,16 +36,10 @@ function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
input: {
samlAuthProviderId: provider.id,
samlAuthProvidersRoleMappings: values.roleMappings.map(
({
({ roleId, remoteRoleName }) => ({
roleId,
remoteRoleName,
}: {
roleId: string;
remoteRoleName: string;
}) => ({
roleId,
remoteRoleName,
})
}),
),
},
},
@@ -73,18 +55,15 @@ function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
throw new Error('Failed while saving!');
}
};
const defaultValues = useMemo(
() => ({
roleMappings: generateFormRoleMappings(roleMappings),
}),
[roleMappings]
[roleMappings],
);
if (providerLoading || !provider?.id || roleMappingsLoading) {
return null;
}
return (
<>
<Divider sx={{ pt: 2 }} />
@@ -108,5 +87,4 @@ function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
</>
);
}
export default RoleMappings;

View File

@@ -1,22 +1,17 @@
import { useFieldArray, useFormContext } from 'react-hook-form';
import { IRole } from 'types';
import MuiTextField from '@mui/material/TextField';
import Stack from '@mui/material/Stack';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import useRoles from 'hooks/useRoles.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import ControlledAutocomplete from 'components/ControlledAutocomplete';
import TextField from 'components/TextField';
import { Divider, Typography } from '@mui/material';
function generateRoleOptions(roles: IRole[]) {
function generateRoleOptions(roles) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
}
function RoleMappingsFieldArray() {
const formatMessage = useFormatMessage();
const { control } = useFormContext();
@@ -25,9 +20,8 @@ function RoleMappingsFieldArray() {
control,
name: 'roleMappings',
});
const handleAppendMapping = () => append({ roleId: '', remoteRoleName: '' });
const handleRemoveMapping = (index: number) => () => remove(index);
const handleRemoveMapping = (index) => () => remove(index);
return (
<>
{fields.length === 0 && (
@@ -89,5 +83,4 @@ function RoleMappingsFieldArray() {
</>
);
}
export default RoleMappingsFieldArray;

View File

@@ -1,26 +1,16 @@
import { QueryResult, useMutation } from '@apollo/client';
import { IRole, TSamlAuthProvider } from 'types';
import { useMutation } from '@apollo/client';
import LoadingButton from '@mui/lab/LoadingButton';
import Stack from '@mui/material/Stack';
import MuiTextField from '@mui/material/TextField';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import ControlledAutocomplete from 'components/ControlledAutocomplete';
import Form from 'components/Form';
import Switch from 'components/Switch';
import TextField from 'components/TextField';
import { UPSERT_SAML_AUTH_PROVIDER } from 'graphql/mutations/upsert-saml-auth-provider';
import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee';
type SamlConfigurationProps = {
provider?: TSamlAuthProvider;
providerLoading: boolean;
refetchProvider: QueryResult<TSamlAuthProvider | undefined>['refetch'];
};
const defaultValues = {
active: false,
name: '',
@@ -34,26 +24,17 @@ const defaultValues = {
roleAttributeName: '',
defaultRoleId: '',
};
function generateRoleOptions(roles: IRole[]) {
function generateRoleOptions(roles) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
}
function SamlConfiguration({
provider,
providerLoading,
refetchProvider,
}: SamlConfigurationProps) {
function SamlConfiguration({ provider, providerLoading, refetchProvider }) {
const formatMessage = useFormatMessage();
const { roles, loading: rolesLoading } = useRoles();
const enqueueSnackbar = useEnqueueSnackbar();
const [upsertSamlAuthProvider, { loading }] = useMutation(
UPSERT_SAML_AUTH_PROVIDER
UPSERT_SAML_AUTH_PROVIDER,
);
const handleProviderUpdate = async (
providerDataToUpdate: Partial<TSamlAuthProvider>
) => {
const handleProviderUpdate = async (providerDataToUpdate) => {
try {
const {
name,
@@ -68,7 +49,6 @@ function SamlConfiguration({
active,
defaultRoleId,
} = providerDataToUpdate;
await upsertSamlAuthProvider({
variables: {
input: {
@@ -86,11 +66,9 @@ function SamlConfiguration({
},
},
});
if (!provider?.id) {
await refetchProvider();
}
enqueueSnackbar(formatMessage('authenticationForm.successfullySaved'), {
variant: 'success',
SnackbarProps: {
@@ -101,11 +79,9 @@ function SamlConfiguration({
throw new Error('Failed while saving!');
}
};
if (providerLoading) {
return null;
}
return (
<Form
defaultValues={provider || defaultValues}
@@ -209,5 +185,4 @@ function SamlConfiguration({
</Form>
);
}
export default SamlConfiguration;

View File

@@ -1,15 +1,11 @@
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import useFormatMessage from 'hooks/useFormatMessage';
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
import SamlConfiguration from './SamlConfiguration';
import RoleMappings from './RoleMappings';
function AuthenticationPage() {
const formatMessage = useFormatMessage();
const {
@@ -17,7 +13,6 @@ function AuthenticationPage() {
loading: providerLoading,
refetch: refetchProvider,
} = useSamlAuthProvider();
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>
@@ -41,5 +36,4 @@ function AuthenticationPage() {
</Container>
);
}
export default AuthenticationPage;

View File

@@ -1,7 +1,6 @@
import * as React from 'react';
import { Navigate } from 'react-router-dom';
import Grid from '@mui/material/Grid';
import * as URLS from 'config/urls';
import UsageDataInformation from 'components/UsageDataInformation/index.ee';
import Invoices from 'components/Invoices/index.ee';
@@ -9,20 +8,16 @@ import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import useFormatMessage from 'hooks/useFormatMessage';
import useCloud from 'hooks/useCloud';
function BillingAndUsageSettings() {
const isCloud = useCloud();
const formatMessage = useFormatMessage();
// redirect to the initial settings page
if (isCloud === false) {
return <Navigate to={URLS.SETTINGS} replace={true} />;
}
// render nothing until we know if it's cloud or not
// here, `isCloud` is not `false`, but `undefined`
if (!isCloud) return <React.Fragment />;
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={8}>
@@ -43,5 +38,4 @@ function BillingAndUsageSettings() {
</Container>
);
}
export default BillingAndUsageSettings;

View File

@@ -6,31 +6,22 @@ import PermissionCatalogField from 'components/PermissionCatalogField/index.ee';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import Container from 'components/Container';
import Form from 'components/Form';
import PageTitle from 'components/PageTitle';
import TextField from 'components/TextField';
import * as URLS from 'config/urls';
import { CREATE_ROLE } from 'graphql/mutations/create-role.ee';
import {
RoleWithComputedPermissions,
getPermissions,
} from 'helpers/computePermissions.ee';
import { getPermissions } from 'helpers/computePermissions.ee';
import useFormatMessage from 'hooks/useFormatMessage';
export default function CreateRole(): React.ReactElement {
export default function CreateRole() {
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const [createRole, { loading }] = useMutation(CREATE_ROLE);
const enqueueSnackbar = useEnqueueSnackbar();
const handleRoleCreation = async (
roleData: Partial<RoleWithComputedPermissions>
) => {
const handleRoleCreation = async (roleData) => {
try {
const permissions = getPermissions(roleData.computedPermissions);
await createRole({
variables: {
input: {
@@ -40,20 +31,17 @@ export default function CreateRole(): React.ReactElement {
},
},
});
enqueueSnackbar(formatMessage('createRole.successfullyCreated'), {
variant: 'success',
SnackbarProps: {
'data-test': 'snackbar-create-role-success',
},
});
navigate(URLS.ROLES);
} catch (error) {
throw new Error('Failed while creating!');
}
};
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>

View File

@@ -1,5 +1,4 @@
import { useMutation } from '@apollo/client';
import { IRole, IUser } from 'types';
import LoadingButton from '@mui/lab/LoadingButton';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
@@ -7,7 +6,6 @@ import MuiTextField from '@mui/material/TextField';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import Can from 'components/Can';
import Container from 'components/Container';
import ControlledAutocomplete from 'components/ControlledAutocomplete';
@@ -18,19 +16,16 @@ import * as URLS from 'config/urls';
import { CREATE_USER } from 'graphql/mutations/create-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee';
function generateRoleOptions(roles: IRole[]) {
function generateRoleOptions(roles) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
}
export default function CreateUser(): React.ReactElement {
export default function CreateUser() {
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const [createUser, { loading }] = useMutation(CREATE_USER);
const { roles, loading: rolesLoading } = useRoles();
const enqueueSnackbar = useEnqueueSnackbar();
const handleUserCreation = async (userData: Partial<IUser>) => {
const handleUserCreation = async (userData) => {
try {
await createUser({
variables: {
@@ -44,7 +39,6 @@ export default function CreateUser(): React.ReactElement {
},
},
});
enqueueSnackbar(formatMessage('createUser.successfullyCreated'), {
variant: 'success',
persist: true,
@@ -52,13 +46,11 @@ export default function CreateUser(): React.ReactElement {
'data-test': 'snackbar-create-user-success',
},
});
navigate(URLS.USERS);
} catch (error) {
throw new Error('Failed while creating!');
}
};
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>

View File

@@ -6,7 +6,6 @@ import Stack from '@mui/material/Stack';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import Container from 'components/Container';
import Form from 'components/Form';
import PageTitle from 'components/PageTitle';
@@ -15,31 +14,21 @@ import TextField from 'components/TextField';
import * as URLS from 'config/urls';
import { UPDATE_ROLE } from 'graphql/mutations/update-role.ee';
import {
RoleWithComputedPermissions,
getPermissions,
getRoleWithComputedPermissions,
} from 'helpers/computePermissions.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import useRole from 'hooks/useRole.ee';
type EditRoleParams = {
roleId: string;
};
export default function EditRole(): React.ReactElement {
export default function EditRole() {
const formatMessage = useFormatMessage();
const [updateRole, { loading }] = useMutation(UPDATE_ROLE);
const navigate = useNavigate();
const { roleId } = useParams<EditRoleParams>();
const { roleId } = useParams();
const { role, loading: roleLoading } = useRole(roleId);
const enqueueSnackbar = useEnqueueSnackbar();
const handleRoleUpdate = async (
roleData: Partial<RoleWithComputedPermissions>
) => {
const handleRoleUpdate = async (roleData) => {
try {
const newPermissions = getPermissions(roleData.computedPermissions);
await updateRole({
variables: {
input: {
@@ -50,22 +39,18 @@ export default function EditRole(): React.ReactElement {
},
},
});
enqueueSnackbar(formatMessage('editRole.successfullyUpdated'), {
variant: 'success',
SnackbarProps: {
'data-test': 'snackbar-edit-role-success',
},
});
navigate(URLS.ROLES);
} catch (error) {
throw new Error('Failed while updating!');
}
};
const roleWithComputedPermissions = getRoleWithComputedPermissions(role);
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>

View File

@@ -1,5 +1,4 @@
import { useMutation } from '@apollo/client';
import { IRole, IUser } from 'types';
import LoadingButton from '@mui/lab/LoadingButton';
import Grid from '@mui/material/Grid';
import Skeleton from '@mui/material/Skeleton';
@@ -8,7 +7,6 @@ import MuiTextField from '@mui/material/TextField';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import Can from 'components/Can';
import Container from 'components/Container';
import ControlledAutocomplete from 'components/ControlledAutocomplete';
@@ -20,25 +18,18 @@ import { UPDATE_USER } from 'graphql/mutations/update-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee';
import useUser from 'hooks/useUser';
type EditUserParams = {
userId: string;
};
function generateRoleOptions(roles: IRole[]) {
function generateRoleOptions(roles) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
}
export default function EditUser(): React.ReactElement {
export default function EditUser() {
const formatMessage = useFormatMessage();
const [updateUser, { loading }] = useMutation(UPDATE_USER);
const { userId } = useParams<EditUserParams>();
const { userId } = useParams();
const { user, loading: userLoading } = useUser(userId);
const { roles, loading: rolesLoading } = useRoles();
const enqueueSnackbar = useEnqueueSnackbar();
const navigate = useNavigate();
const handleUserUpdate = async (userDataToUpdate: Partial<IUser>) => {
const handleUserUpdate = async (userDataToUpdate) => {
try {
await updateUser({
variables: {
@@ -52,7 +43,6 @@ export default function EditUser(): React.ReactElement {
},
},
});
enqueueSnackbar(formatMessage('editUser.successfullyUpdated'), {
variant: 'success',
SnackbarProps: {
@@ -60,13 +50,11 @@ export default function EditUser(): React.ReactElement {
persist: true,
},
});
navigate(URLS.USERS);
} catch (error) {
throw new Error('Failed while updating!');
}
};
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>

View File

@@ -3,47 +3,36 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import { CREATE_FLOW } from 'graphql/mutations/create-flow';
import Box from '@mui/material/Box';
export default function CreateFlow(): React.ReactElement {
export default function CreateFlow() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const [createFlow] = useMutation(CREATE_FLOW);
const appKey = searchParams.get('appKey');
const connectionId = searchParams.get('connectionId');
React.useEffect(() => {
async function initiate() {
const variables: { [key: string]: string } = {};
const variables = {};
if (appKey) {
variables.triggerAppKey = appKey;
}
if (connectionId) {
variables.connectionId = connectionId;
}
const response = await createFlow({
variables: {
input: variables,
},
});
const flowId = response.data?.createFlow?.id;
navigate(URLS.FLOW_EDITOR(flowId), { replace: true });
}
initiate();
}, [createFlow, navigate, appKey, connectionId]);
return (
<Box
sx={{

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import EditorLayout from 'components/EditorLayout';
export default function FlowEditor(): React.ReactElement {
export default function FlowEditor() {
return <EditorLayout />;
}

View File

@@ -2,8 +2,7 @@ import * as React from 'react';
import { Routes, Route } from 'react-router-dom';
import CreateFlowPage from './create';
import EditorPage from './index';
export default function EditorRoutes(): React.ReactElement {
export default function EditorRoutes() {
return (
<Routes>
<Route path="/create" element={<CreateFlowPage />} />

View File

@@ -5,28 +5,19 @@ import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import AlertTitle from '@mui/material/AlertTitle';
import Alert from '@mui/material/Alert';
import type { IExecutionStep } from 'types';
import useFormatMessage from 'hooks/useFormatMessage';
import ExecutionHeader from 'components/ExecutionHeader';
import ExecutionStep from 'components/ExecutionStep';
import Container from 'components/Container';
import { GET_EXECUTION } from 'graphql/queries/get-execution';
import { GET_EXECUTION_STEPS } from 'graphql/queries/get-execution-steps';
type ExecutionParams = {
executionId: string;
};
const EXECUTION_PER_PAGE = 100;
const getLimitAndOffset = (page: number) => ({
const getLimitAndOffset = (page) => ({
limit: EXECUTION_PER_PAGE,
offset: (page - 1) * EXECUTION_PER_PAGE,
});
export default function Execution(): React.ReactElement {
const { executionId } = useParams() as ExecutionParams;
export default function Execution() {
const { executionId } = useParams();
const formatMessage = useFormatMessage();
const { data: execution } = useQuery(GET_EXECUTION, {
variables: { executionId },
@@ -34,12 +25,8 @@ export default function Execution(): React.ReactElement {
const { data, loading } = useQuery(GET_EXECUTION_STEPS, {
variables: { executionId, ...getLimitAndOffset(1) },
});
const { edges } = data?.getExecutionSteps || {};
const executionSteps: IExecutionStep[] = edges?.map(
(edge: { node: IExecutionStep }) => edge.node
);
const executionSteps = edges?.map((edge) => edge.node);
return (
<Container sx={{ py: 3 }}>
<ExecutionHeader execution={execution?.getExecution} />

View File

@@ -7,27 +7,21 @@ import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import Pagination from '@mui/material/Pagination';
import PaginationItem from '@mui/material/PaginationItem';
import type { IExecution } from 'types';
import NoResultFound from 'components/NoResultFound';
import ExecutionRow from 'components/ExecutionRow';
import Container from 'components/Container';
import PageTitle from 'components/PageTitle';
import useFormatMessage from 'hooks/useFormatMessage';
import { GET_EXECUTIONS } from 'graphql/queries/get-executions';
const EXECUTION_PER_PAGE = 10;
const getLimitAndOffset = (page: number) => ({
const getLimitAndOffset = (page) => ({
limit: EXECUTION_PER_PAGE,
offset: (page - 1) * EXECUTION_PER_PAGE,
});
export default function Executions(): React.ReactElement {
export default function Executions() {
const formatMessage = useFormatMessage();
const [searchParams, setSearchParams] = useSearchParams();
const page = parseInt(searchParams.get('page') || '', 10) || 1;
const { data, refetch, loading } = useQuery(GET_EXECUTIONS, {
variables: getLimitAndOffset(page),
fetchPolicy: 'cache-and-network',
@@ -35,16 +29,11 @@ export default function Executions(): React.ReactElement {
});
const getExecutions = data?.getExecutions || {};
const { pageInfo, edges } = getExecutions;
React.useEffect(() => {
refetch(getLimitAndOffset(page));
}, [refetch, page]);
const executions: IExecution[] = edges?.map(
({ node }: { node: IExecution }) => node
);
const executions = edges?.map(({ node }) => node);
const hasExecutions = executions?.length;
return (
<Box sx={{ py: 3 }}>
<Container>

View File

@@ -2,16 +2,9 @@ import * as React from 'react';
import { useParams } from 'react-router-dom';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Container from 'components/Container';
type FlowParams = {
flowId: string;
};
export default function Flow(): React.ReactElement {
const { flowId } = useParams() as FlowParams;
export default function Flow() {
const { flowId } = useParams();
return (
<Box sx={{ py: 3 }}>
<Container>

View File

@@ -9,8 +9,6 @@ import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import Pagination from '@mui/material/Pagination';
import PaginationItem from '@mui/material/PaginationItem';
import type { IFlow } from 'types';
import Can from 'components/Can';
import FlowRow from 'components/FlowRow';
import NoResultFound from 'components/NoResultFound';
@@ -21,15 +19,12 @@ import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage';
import { GET_FLOWS } from 'graphql/queries/get-flows';
import * as URLS from 'config/urls';
const FLOW_PER_PAGE = 10;
const getLimitAndOffset = (page: number) => ({
const getLimitAndOffset = (page) => ({
limit: FLOW_PER_PAGE,
offset: (page - 1) * FLOW_PER_PAGE,
});
export default function Flows(): React.ReactElement {
export default function Flows() {
const formatMessage = useFormatMessage();
const [searchParams, setSearchParams] = useSearchParams();
const page = parseInt(searchParams.get('page') || '', 10) || 1;
@@ -40,7 +35,6 @@ export default function Flows(): React.ReactElement {
setLoading(false);
},
});
const fetchData = React.useMemo(
() =>
debounce(
@@ -51,43 +45,35 @@ export default function Flows(): React.ReactElement {
name,
},
}),
300
300,
),
[page, getFlows]
[page, getFlows],
);
React.useEffect(
function fetchFlowsOnSearch() {
setLoading(true);
fetchData(flowName);
},
[fetchData, flowName]
[fetchData, flowName],
);
React.useEffect(
function resetPageOnSearch() {
// reset search params which only consists of `page`
setSearchParams({});
},
[flowName]
[flowName],
);
React.useEffect(function cancelDebounceOnUnmount() {
return () => {
fetchData.cancel();
};
}, []);
const { pageInfo, edges } = data?.getFlows || {};
const flows: IFlow[] = edges?.map(({ node }: { node: IFlow }) => node);
const flows = edges?.map(({ node }) => node);
const hasFlows = flows?.length;
const onSearchChange = React.useCallback((event) => {
setFlowName(event.target.value);
}, []);
return (
<Box sx={{ py: 3 }}>
<Container>
@@ -130,21 +116,17 @@ export default function Flows(): React.ReactElement {
</Grid>
<Divider sx={{ mt: [2, 0], mb: 2 }} />
{loading && (
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
)}
{!loading &&
flows?.map((flow) => <FlowRow key={flow.id} flow={flow} />)}
{!loading && !hasFlows && (
<NoResultFound
text={formatMessage('flows.noFlows')}
to={URLS.CREATE_FLOW}
/>
)}
{!loading && pageInfo && pageInfo.totalPages > 1 && (
<Pagination
sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}

View File

@@ -1,13 +1,10 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import useCloud from 'hooks/useCloud';
import Container from 'components/Container';
import ForgotPasswordForm from 'components/ForgotPasswordForm/index.ee';
export default function ForgotPassword(): React.ReactElement {
export default function ForgotPassword() {
useCloud({ redirect: true });
return (
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
<Container maxWidth="sm">

View File

@@ -4,9 +4,7 @@ import Stack from '@mui/material/Stack';
import Container from 'components/Container';
import LoginForm from 'components/LoginForm';
import SsoProviders from 'components/SsoProviders/index.ee';
export default function Login(): React.ReactElement {
export default function Login() {
return (
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
<Container maxWidth="sm">

View File

@@ -1,29 +1,22 @@
import * as React from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import useAuthentication from 'hooks/useAuthentication';
import * as URLS from 'config/urls';
export default function LoginCallback(): React.ReactElement {
export default function LoginCallback() {
const navigate = useNavigate();
const authentication = useAuthentication();
const [searchParams] = useSearchParams();
React.useEffect(() => {
if (authentication.isAuthenticated) {
navigate(URLS.DASHBOARD);
}
}, [authentication.isAuthenticated]);
React.useEffect(() => {
const token = searchParams.get('token');
if (token) {
authentication.updateToken(token);
}
// TODO: handle non-existing token scenario
}, []);
return (<></>);
return <></>;
}

View File

@@ -2,7 +2,6 @@ import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import useNotifications from 'hooks/useNotifications';
import Container from 'components/Container';
import NotificationCard from 'components/NotificationCard';
@@ -10,29 +9,19 @@ import PageTitle from 'components/PageTitle';
import useFormatMessage from 'hooks/useFormatMessage';
import useAutomatischInfo from 'hooks/useAutomatischInfo';
import * as URLS from 'config/urls';
interface INotification {
name: string;
createdAt: string;
documentationUrl: string;
description: string;
}
export default function Updates(): React.ReactElement {
export default function Updates() {
const navigate = useNavigate();
const formatMessage = useFormatMessage();
const { notifications } = useNotifications();
const { isMation, loading } = useAutomatischInfo();
React.useEffect(
function redirectToHomepageInMation() {
if (!loading && isMation) {
navigate(URLS.DASHBOARD);
}
},
[loading, isMation]
[loading, isMation],
);
return (
<Box sx={{ py: 3 }}>
<Container>
@@ -41,7 +30,7 @@ export default function Updates(): React.ReactElement {
</PageTitle>
<Stack gap={2}>
{notifications.map((notification: INotification) => (
{notifications.map((notification) => (
<NotificationCard
key={notification.name}
name={`Version ${notification.name}`}

View File

@@ -1,34 +1,27 @@
import * as React from 'react';
import { Navigate } from 'react-router-dom';
import Grid from '@mui/material/Grid';
import * as URLS from 'config/urls';
import UpgradeFreeTrial from 'components/UpgradeFreeTrial/index.ee';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import useFormatMessage from 'hooks/useFormatMessage';
import useCloud from 'hooks/useCloud';
function PlanUpgrade() {
const isCloud = useCloud();
const formatMessage = useFormatMessage();
// redirect to the initial settings page
if (isCloud === false) {
return <Navigate to={URLS.SETTINGS} replace={true} />;
}
// render nothing until we know if it's cloud or not
// here, `isCloud` is not `false`, but `undefined`
if (!isCloud) return <React.Fragment />;
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={8}>
<Grid item xs={12} sx={{ mb: [2, 5] }}>
<PageTitle>
{formatMessage('planUpgrade.title')}
</PageTitle>
<PageTitle>{formatMessage('planUpgrade.title')}</PageTitle>
</Grid>
<Grid item xs={12} sx={{ mb: 6 }}>
@@ -38,5 +31,4 @@ function PlanUpgrade() {
</Container>
);
}
export default PlanUpgrade;

View File

@@ -9,7 +9,6 @@ import { styled } from '@mui/material/styles';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react';
import * as yup from 'yup';
import Container from 'components/Container';
import DeleteAccountDialog from 'components/DeleteAccountDialog/index.ee';
import Form from 'components/Form';
@@ -18,13 +17,6 @@ import TextField from 'components/TextField';
import { UPDATE_CURRENT_USER } from 'graphql/mutations/update-current-user';
import useCurrentUser from 'hooks/useCurrentUser';
import useFormatMessage from 'hooks/useFormatMessage';
type TMutationInput = {
fullName: string;
email: string;
password?: string;
};
const validationSchema = yup
.object({
fullName: yup.string().required(),
@@ -35,13 +27,11 @@ const validationSchema = yup
.oneOf([yup.ref('password')], 'Passwords must match'),
})
.required();
const StyledForm = styled(Form)`
display: flex;
align-items: end;
flex-direction: column;
`;
function ProfileSettings() {
const [showDeleteAccountConfirmation, setShowDeleteAccountConfirmation] =
React.useState(false);
@@ -49,19 +39,15 @@ function ProfileSettings() {
const currentUser = useCurrentUser();
const formatMessage = useFormatMessage();
const [updateCurrentUser] = useMutation(UPDATE_CURRENT_USER);
const handleProfileSettingsUpdate = async (data: any) => {
const handleProfileSettingsUpdate = async (data) => {
const { fullName, password, email } = data;
const mutationInput: TMutationInput = {
const mutationInput = {
fullName,
email,
};
if (password) {
mutationInput.password = password;
}
await updateCurrentUser({
variables: {
input: mutationInput,
@@ -75,15 +61,13 @@ function ProfileSettings() {
},
},
});
enqueueSnackbar(formatMessage('profileSettings.updatedProfile'), {
variant: 'success',
SnackbarProps: {
'data-test': 'snackbar-update-profile-settings-success'
}
'data-test': 'snackbar-update-profile-settings-success',
},
});
};
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={8} lg={6}>
@@ -209,5 +193,4 @@ function ProfileSettings() {
</Container>
);
}
export default ProfileSettings;

View File

@@ -1,13 +1,10 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import useCloud from 'hooks/useCloud';
import Container from 'components/Container';
import ResetPasswordForm from 'components/ResetPasswordForm/index.ee';
export default function ResetPassword(): React.ReactElement {
export default function ResetPassword() {
useCloud({ redirect: true });
return (
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
<Container maxWidth="sm">

View File

@@ -2,17 +2,14 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
import Grid from '@mui/material/Grid';
import AddIcon from '@mui/icons-material/Add';
import * as URLS from 'config/urls';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import RoleList from 'components/RoleList/index.ee';
import ConditionalIconButton from 'components/ConditionalIconButton';
import useFormatMessage from 'hooks/useFormatMessage';
function RolesPage() {
const formatMessage = useFormatMessage();
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>
@@ -47,5 +44,4 @@ function RolesPage() {
</Container>
);
}
export default RolesPage;

View File

@@ -1,13 +1,10 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import useCloud from 'hooks/useCloud';
import Container from 'components/Container';
import SignUpForm from 'components/SignUpForm/index.ee';
export default function SignUp(): React.ReactElement {
export default function SignUp() {
useCloud({ redirect: true });
return (
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
<Container maxWidth="sm">

View File

@@ -6,7 +6,6 @@ import Stack from '@mui/material/Stack';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import merge from 'lodash/merge';
import * as React from 'react';
import ColorInput from 'components/ColorInput';
import Container from 'components/Container';
import Form from 'components/Form';
@@ -22,33 +21,16 @@ import {
primaryLightColor,
primaryMainColor,
} from 'styles/theme';
type UserInterface = {
palette: {
primary: {
dark: string;
light: string;
main: string;
};
};
title: string;
logo: {
svgData: string;
};
};
const getPrimaryMainColor = (color?: string) => color || primaryMainColor;
const getPrimaryDarkColor = (color?: string) => color || primaryDarkColor;
const getPrimaryLightColor = (color?: string) => color || primaryLightColor;
const getPrimaryMainColor = (color) => color || primaryMainColor;
const getPrimaryDarkColor = (color) => color || primaryDarkColor;
const getPrimaryLightColor = (color) => color || primaryLightColor;
const defaultValues = {
title: 'Automatisch',
'palette.primary.main': primaryMainColor,
'palette.primary.dark': primaryDarkColor,
'palette.primary.light': primaryLightColor,
};
export default function UserInterface(): React.ReactElement {
export default function UserInterface() {
const formatMessage = useFormatMessage();
const [updateConfig, { loading }] = useMutation(UPDATE_CONFIG);
const { config, loading: configLoading } = useConfig([
@@ -59,28 +41,22 @@ export default function UserInterface(): React.ReactElement {
'logo.svgData',
]);
const enqueueSnackbar = useEnqueueSnackbar();
const configWithDefaults = merge(
{},
defaultValues,
nestObject<UserInterface>(config)
);
const handleUserInterfaceUpdate = async (uiData: Partial<UserInterface>) => {
const configWithDefaults = merge({}, defaultValues, nestObject(config));
const handleUserInterfaceUpdate = async (uiData) => {
try {
const input = {
title: uiData?.title,
'palette.primary.main': getPrimaryMainColor(
uiData?.palette?.primary.main
uiData?.palette?.primary.main,
),
'palette.primary.dark': getPrimaryDarkColor(
uiData?.palette?.primary.dark
uiData?.palette?.primary.dark,
),
'palette.primary.light': getPrimaryLightColor(
uiData?.palette?.primary.light
uiData?.palette?.primary.light,
),
'logo.svgData': uiData?.logo?.svgData,
};
await updateConfig({
variables: {
input,
@@ -90,14 +66,12 @@ export default function UserInterface(): React.ReactElement {
},
update: async function (cache, { data: { updateConfig } }) {
const newConfigWithDefaults = merge({}, defaultValues, updateConfig);
cache.writeQuery({
query: GET_CONFIG,
data: {
getConfig: newConfigWithDefaults,
},
});
cache.writeQuery({
query: GET_CONFIG,
data: {
@@ -107,7 +81,6 @@ export default function UserInterface(): React.ReactElement {
keys: ['logo.svgData'],
},
});
cache.writeQuery({
query: GET_CONFIG,
data: {
@@ -125,18 +98,16 @@ export default function UserInterface(): React.ReactElement {
});
},
});
enqueueSnackbar(formatMessage('userInterfacePage.successfullyUpdated'), {
variant: 'success',
SnackbarProps: {
'data-test': 'snackbar-update-user-interface-success'
}
'data-test': 'snackbar-update-user-interface-success',
},
});
} catch (error) {
throw new Error('Failed while updating!');
}
};
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>
@@ -169,7 +140,7 @@ export default function UserInterface(): React.ReactElement {
<ColorInput
name="palette.primary.main"
label={formatMessage(
'userInterfacePage.primaryMainColorFieldLabel'
'userInterfacePage.primaryMainColorFieldLabel',
)}
fullWidth
data-test="primary-main-color-input"
@@ -178,7 +149,7 @@ export default function UserInterface(): React.ReactElement {
<ColorInput
name="palette.primary.dark"
label={formatMessage(
'userInterfacePage.primaryDarkColorFieldLabel'
'userInterfacePage.primaryDarkColorFieldLabel',
)}
fullWidth
data-test="primary-dark-color-input"
@@ -187,7 +158,7 @@ export default function UserInterface(): React.ReactElement {
<ColorInput
name="palette.primary.light"
label={formatMessage(
'userInterfacePage.primaryLightColorFieldLabel'
'userInterfacePage.primaryLightColorFieldLabel',
)}
fullWidth
data-test="primary-light-color-input"

View File

@@ -2,17 +2,14 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
import Grid from '@mui/material/Grid';
import AddIcon from '@mui/icons-material/Add';
import * as URLS from 'config/urls';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import UserList from 'components/UserList';
import ConditionalIconButton from 'components/ConditionalIconButton';
import useFormatMessage from 'hooks/useFormatMessage';
function UsersPage() {
const formatMessage = useFormatMessage();
return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={10} md={9}>
@@ -47,5 +44,4 @@ function UsersPage() {
</Container>
);
}
export default UsersPage;