0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

feat(console): add unsaved changes alert for connector config (#1414)

This commit is contained in:
Xiao Yijun 2022-07-06 16:39:41 +08:00 committed by GitHub
parent 181e8a228d
commit 78407fc6c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 73 deletions

View file

@ -0,0 +1,126 @@
import { Connector, ConnectorDTO, ConnectorMetadata, ConnectorType } from '@logto/schemas';
import React, { useEffect, useMemo, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Button from '@/components/Button';
import CodeEditor from '@/components/CodeEditor';
import FormField from '@/components/FormField';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import useApi from '@/hooks/use-api';
import * as detailsStyles from '@/scss/details.module.scss';
import * as styles from '../index.module.scss';
import SenderTester from './SenderTester';
type Props = {
connectorData: ConnectorDTO;
onConnectorUpdated: (connector: ConnectorDTO) => void;
};
const ConnectorContent = ({ connectorData, onConnectorUpdated }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [config, setConfig] = useState<string>();
const [saveError, setSaveError] = useState<string>();
const [isSubmitting, setIsSubmitting] = useState(false);
const api = useApi();
const defaultConfig = useMemo(() => {
const hasData = Object.keys(connectorData.config).length > 0;
return hasData ? JSON.stringify(connectorData.config, null, 2) : connectorData.configTemplate;
}, [connectorData]);
useEffect(() => {
setConfig(defaultConfig);
return () => {
setConfig(defaultConfig);
};
}, [defaultConfig]);
const hasUnsavedChanges = useMemo(() => {
if (!config) {
return false;
}
try {
const parsedConfig = JSON.stringify(JSON.parse(config), null, 2);
return parsedConfig !== defaultConfig;
} catch {
return true;
}
}, [config, defaultConfig]);
const handleSave = async () => {
setSaveError(undefined);
if (!config) {
setSaveError(t('connector_details.save_error_empty_config'));
return;
}
try {
const configJson = JSON.parse(config) as JSON;
setIsSubmitting(true);
const { metadata, ...reset } = await api
.patch(`/api/connectors/${connectorData.id}`, {
json: { config: configJson },
})
.json<
Connector & {
metadata: ConnectorMetadata;
}
>();
onConnectorUpdated({ ...reset, ...metadata });
toast.success(t('general.saved'));
} catch (error: unknown) {
if (error instanceof SyntaxError) {
setSaveError(t('connector_details.save_error_json_parse_error'));
}
}
setIsSubmitting(false);
};
return (
<>
<div className={styles.main}>
<FormField title="admin_console.connector_details.edit_config_label">
<CodeEditor
className={styles.codeEditor}
language="json"
value={config}
onChange={(value) => {
setConfig(value);
}}
/>
</FormField>
{connectorData.type !== ConnectorType.Social && (
<SenderTester
connectorId={connectorData.id}
connectorType={connectorData.type}
config={config}
/>
)}
{saveError && <div>{saveError}</div>}
</div>
<div className={detailsStyles.footer}>
<div className={detailsStyles.footerMain}>
<Button
type="primary"
size="large"
title="admin_console.general.save_changes"
isLoading={isSubmitting}
onClick={handleSave}
/>
</div>
</div>
<UnsavedChangesAlertModal hasUnsavedChanges={hasUnsavedChanges} />
</>
);
};
export default ConnectorContent;

View file

@ -1,6 +1,6 @@
import { AppearanceMode, ConnectorDTO, ConnectorType } from '@logto/schemas';
import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
@ -9,11 +9,9 @@ import useSWR from 'swr';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import Button from '@/components/Button';
import Card from '@/components/Card';
import CodeEditor from '@/components/CodeEditor';
import CopyToClipboard from '@/components/CopyToClipboard';
import DetailsSkeleton from '@/components/DetailsSkeleton';
import Drawer from '@/components/Drawer';
import FormField from '@/components/FormField';
import LinkButton from '@/components/LinkButton';
import Markdown from '@/components/Markdown';
import Status from '@/components/Status';
@ -29,20 +27,17 @@ import Reset from '@/icons/Reset';
import * as detailsStyles from '@/scss/details.module.scss';
import CreateForm from '../Connectors/components/CreateForm';
import ConnectorContent from './components/ConnectorContent';
import ConnectorTabs from './components/ConnectorTabs';
import ConnectorTypeName from './components/ConnectorTypeName';
import SenderTester from './components/SenderTester';
import * as styles from './index.module.scss';
const ConnectorDetails = () => {
const { connectorId } = useParams();
const [isReadMeOpen, setIsReadMeOpen] = useState(false);
const [config, setConfig] = useState<string>();
const [saveError, setSaveError] = useState<string>();
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSetupOpen, setIsSetupOpen] = useState(false);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { data, error } = useSWR<ConnectorDTO, RequestError>(
const { data, error, mutate } = useSWR<ConnectorDTO, RequestError>(
connectorId && `/api/connectors/${connectorId}`
);
const inUse = useConnectorInUse(data?.type, data?.target);
@ -51,44 +46,6 @@ const ConnectorDetails = () => {
const navigate = useNavigate();
const theme = useTheme();
useEffect(() => {
if (data) {
const hasData = Object.keys(data.config).length > 0;
setConfig(hasData ? JSON.stringify(data.config, null, 2) : data.configTemplate);
}
}, [data]);
const handleSave = async () => {
if (!connectorId) {
return;
}
setSaveError(undefined);
if (!config) {
setSaveError(t('connector_details.save_error_empty_config'));
return;
}
try {
const configJson = JSON.parse(config) as JSON;
setIsSubmitting(true);
await api
.patch(`/api/connectors/${connectorId}`, {
json: { config: configJson },
})
.json<ConnectorDTO>();
toast.success(t('general.saved'));
} catch (error: unknown) {
if (error instanceof SyntaxError) {
setSaveError(t('connector_details.save_error_json_parse_error'));
}
}
setIsSubmitting(false);
};
const handleDelete = async () => {
if (!connectorId) {
return;
@ -208,33 +165,12 @@ const ConnectorDetails = () => {
{t('general.settings_nav')}
</TabNavItem>
</TabNav>
<div className={styles.main}>
<FormField title="admin_console.connector_details.edit_config_label">
<CodeEditor
className={styles.codeEditor}
language="json"
value={config}
onChange={(value) => {
setConfig(value);
}}
/>
</FormField>
{data.type !== ConnectorType.Social && (
<SenderTester connectorId={data.id} connectorType={data.type} config={config} />
)}
{saveError && <div>{saveError}</div>}
</div>
<div className={detailsStyles.footer}>
<div className={detailsStyles.footerMain}>
<Button
type="primary"
size="large"
title="admin_console.general.save_changes"
isLoading={isSubmitting}
onClick={handleSave}
/>
</div>
</div>
<ConnectorContent
connectorData={data}
onConnectorUpdated={(connector) => {
void mutate(connector);
}}
/>
</Card>
)}
</div>