feat: introduce application auth clients tab in the admin panel (#1423)

* feat: introduce application auth clients tab in the admin panel

* feat: introduce improvements

* feat: use loading state returned from useMutation

* feat: use error returned by useMutation hook
This commit is contained in:
kattoczko
2023-11-10 13:09:23 +00:00
committed by GitHub
parent 878fab347a
commit c461cc4878
14 changed files with 580 additions and 97 deletions

View File

@@ -0,0 +1,104 @@
import React from 'react';
import type { IField } from '@automatisch/types';
import LoadingButton from '@mui/lab/LoadingButton';
import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import CircularProgress from '@mui/material/CircularProgress';
import { FieldValues, SubmitHandler } from 'react-hook-form';
import type { UseFormProps } from 'react-hook-form';
import type { ApolloError } from '@apollo/client';
import useFormatMessage from 'hooks/useFormatMessage';
import InputCreator from 'components/InputCreator';
import Switch from 'components/Switch';
import TextField from 'components/TextField';
import { Form } from './style';
type AdminApplicationAuthClientDialogProps = {
title: string;
authFields?: IField[];
defaultValues: UseFormProps['defaultValues'];
loading: boolean;
submitting: boolean;
disabled?: boolean;
error?: ApolloError;
submitHandler: SubmitHandler<FieldValues>;
onClose: () => void;
};
export default function AdminApplicationAuthClientDialog(
props: AdminApplicationAuthClientDialogProps
): React.ReactElement {
const {
error,
onClose,
title,
loading,
submitHandler,
authFields,
submitting,
defaultValues,
disabled = false,
} = props;
const formatMessage = useFormatMessage();
return (
<Dialog open={true} onClose={onClose}>
<DialogTitle>{title}</DialogTitle>
{error && (
<Alert
severity="error"
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
>
{error.message}
</Alert>
)}
<DialogContent>
{loading ? (
<CircularProgress
data-test="search-for-app-loader"
sx={{ display: 'block', margin: '20px auto' }}
/>
) : (
<DialogContentText tabIndex={-1} component="div">
<Form
onSubmit={submitHandler}
defaultValues={defaultValues}
render={({ formState: { isDirty } }) => (
<>
<Switch
name="active"
label={formatMessage('authClient.inputActive')}
/>
<TextField
required={true}
name="name"
label={formatMessage('authClient.inputName')}
fullWidth
/>
{authFields?.map((field: IField) => (
<InputCreator key={field.key} schema={field} />
))}
<LoadingButton
type="submit"
variant="contained"
color="primary"
sx={{ boxShadow: 2 }}
loading={submitting}
disabled={disabled || !isDirty}
>
{formatMessage('authClient.buttonSubmit')}
</LoadingButton>
</>
)}
></Form>
</DialogContentText>
)}
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,9 @@
import { styled } from '@mui/material/styles';
import BaseForm from 'components/Form';
export const Form = styled(BaseForm)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
paddingTop: theme.spacing(1),
}));

View File

@@ -0,0 +1,89 @@
import { Link } from 'react-router-dom';
import CircularProgress from '@mui/material/CircularProgress';
import Stack from '@mui/material/Stack';
import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
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 * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
import NoResultFound from 'components/NoResultFound';
type AdminApplicationAuthClientsProps = {
appKey: string;
};
function AdminApplicationAuthClients(
props: AdminApplicationAuthClientsProps
): React.ReactElement {
const { appKey } = props;
const formatMessage = useFormatMessage();
const { appAuthClients, loading } = useAppAuthClients({ appKey });
if (loading)
return <CircularProgress sx={{ display: 'block', margin: '20px auto' }} />;
if (!appAuthClients?.length) {
return (
<NoResultFound
to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}
text={formatMessage('adminAppsAuthClients.noAuthClients')}
/>
);
}
const sortedAuthClients = appAuthClients.slice().sort((a, b) => {
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
return 0;
});
return (
<div>
{sortedAuthClients.map((client) => (
<Card sx={{ mb: 1 }} key={client.id}>
<CardActionArea
component={Link}
to={URLS.ADMIN_APP_AUTH_CLIENT(appKey, client.id)}
>
<CardContent>
<Stack direction="row" justifyContent="space-between">
<Typography variant="h6" noWrap>
{client.name}
</Typography>
<Chip
size="small"
color={client?.active ? 'success' : 'info'}
variant={client?.active ? 'filled' : 'outlined'}
label={formatMessage(
client?.active
? 'adminAppsAuthClients.statusActive'
: 'adminAppsAuthClients.statusInactive'
)}
/>
</Stack>
</CardContent>
</CardActionArea>
</Card>
))}
<Stack justifyContent="flex-end" direction="row">
<Link to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}>
<Button variant="contained" sx={{ mt: 2 }} component="div">
{formatMessage('createAuthClient.button')}
</Button>
</Link>
</Stack>
</div>
);
}
export default AdminApplicationAuthClients;

View File

@@ -0,0 +1,112 @@
import React, { useCallback, useMemo } from 'react';
import type { IApp } from '@automatisch/types';
import { FieldValues, SubmitHandler } from 'react-hook-form';
import { useMutation } from '@apollo/client';
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client';
import useAppConfig from 'hooks/useAppConfig.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
type AdminApplicationCreateAuthClientProps = {
appKey: string;
application: IApp;
onClose: () => void;
};
export default function AdminApplicationCreateAuthClient(
props: AdminApplicationCreateAuthClientProps
): React.ReactElement {
const { appKey, application, onClose } = props;
const { auth } = application;
const formatMessage = useFormatMessage();
const { appConfig, loading: loadingAppConfig } = useAppConfig(appKey);
const [
createAppConfig,
{ loading: loadingCreateAppConfig, error: createAppConfigError },
] = useMutation(CREATE_APP_CONFIG, {
refetchQueries: ['GetAppConfig'],
context: { autoSnackbar: false },
});
const [
createAppAuthClient,
{ loading: loadingCreateAppAuthClient, error: createAppAuthClientError },
] = useMutation(CREATE_APP_AUTH_CLIENT, {
refetchQueries: ['GetAppAuthClients'],
context: { autoSnackbar: false },
});
const submitHandler: SubmitHandler<FieldValues> = async (values) => {
let appConfigId = appConfig?.id;
if (!appConfigId) {
const { data: appConfigData } = await createAppConfig({
variables: {
input: {
key: appKey,
allowCustomConnection: false,
shared: false,
disabled: false,
},
},
});
appConfigId = appConfigData.createAppConfig.id;
}
const { name, active, ...formattedAuthDefaults } = values;
await createAppAuthClient({
variables: {
input: {
appConfigId,
name,
active,
formattedAuthDefaults,
},
},
});
onClose();
};
const getAuthFieldsDefaultValues = useCallback(() => {
if (!auth?.fields) {
return {};
}
const defaultValues: {
[key: string]: any;
} = {};
auth.fields.forEach((field) => {
if (field.value || field.type !== 'string') {
defaultValues[field.key] = field.value;
} else if (field.type === 'string') {
defaultValues[field.key] = '';
}
});
return defaultValues;
}, [auth?.fields]);
const defaultValues = useMemo(
() => ({
name: '',
active: false,
...getAuthFieldsDefaultValues(),
}),
[getAuthFieldsDefaultValues]
);
return (
<AdminApplicationAuthClientDialog
onClose={onClose}
error={createAppConfigError || createAppAuthClientError}
title={formatMessage('createAuthClient.title')}
loading={loadingAppConfig}
submitHandler={submitHandler}
authFields={auth?.fields}
submitting={loadingCreateAppConfig || loadingCreateAppAuthClient}
defaultValues={defaultValues}
/>
);
}

View File

@@ -0,0 +1,95 @@
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import type { IApp } from '@automatisch/types';
import { FieldValues, SubmitHandler } from 'react-hook-form';
import { useMutation } from '@apollo/client';
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';
import useAppAuthClient from 'hooks/useAppAuthClient.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
type AdminApplicationUpdateAuthClientProps = {
application: IApp;
onClose: () => void;
};
export default function AdminApplicationUpdateAuthClient(
props: AdminApplicationUpdateAuthClientProps
): React.ReactElement {
const { application, onClose } = props;
const { auth } = application;
const authFields = auth?.fields?.map((field) => ({
...field,
required: false,
}));
const formatMessage = useFormatMessage();
const { clientId } = useParams();
const { appAuthClient, loading: loadingAuthClient } =
useAppAuthClient(clientId);
const [updateAppAuthClient, { loading: loadingUpdateAppAuthClient, error }] =
useMutation(UPDATE_APP_AUTH_CLIENT, {
refetchQueries: ['GetAppAuthClients'],
context: { autoSnackbar: false },
});
const submitHandler: SubmitHandler<FieldValues> = async (values) => {
if (!appAuthClient) {
return;
}
const { name, active, ...formattedAuthDefaults } = values;
await updateAppAuthClient({
variables: {
input: {
id: appAuthClient.id,
name,
active,
formattedAuthDefaults,
},
},
});
onClose();
};
const getAuthFieldsDefaultValues = useCallback(() => {
if (!authFields) {
return {};
}
const defaultValues: {
[key: string]: any;
} = {};
authFields.forEach((field) => {
if (field.value || field.type !== 'string') {
defaultValues[field.key] = field.value;
} else if (field.type === 'string') {
defaultValues[field.key] = '';
}
});
return defaultValues;
}, [auth?.fields]);
const defaultValues = useMemo(
() => ({
name: appAuthClient?.name || '',
active: appAuthClient?.active || false,
...getAuthFieldsDefaultValues(),
}),
[appAuthClient, getAuthFieldsDefaultValues]
);
return (
<AdminApplicationAuthClientDialog
onClose={onClose}
error={error}
title={formatMessage('updateAuthClient.title')}
loading={loadingAuthClient}
submitHandler={submitHandler}
authFields={authFields}
submitting={loadingUpdateAppAuthClient}
defaultValues={defaultValues}
disabled={!appAuthClient}
/>
);
}

View File

@@ -17,7 +17,7 @@ type AppAuthClientsDialogProps = {
export default function AppAuthClientsDialog(props: AppAuthClientsDialogProps) {
const { appKey, onClientClick, onClose } = props;
const { appAuthClients } = useAppAuthClients(appKey);
const { appAuthClients } = useAppAuthClients({ appKey, active: true });
const formatMessage = useFormatMessage();
React.useEffect(