0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00

feat(console): delete api resource (#389)

This commit is contained in:
Xiao Yijun 2022-03-16 14:47:31 +08:00 committed by GitHub
parent 86030ab97c
commit 78b2e3be30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 197 additions and 2 deletions

View file

@ -3,9 +3,19 @@ import React from 'react';
import * as styles from './ActionMenuItem.module.scss';
type Props = {
onClick?: () => void;
children: React.ReactNode;
};
const ActionMenuItem = ({ children }: Props) => <li className={styles.item}>{children}</li>;
const ActionMenuItem = ({ onClick, children }: Props) => (
<li
className={styles.item}
onClick={() => {
onClick?.();
}}
>
{children}
</li>
);
export default ActionMenuItem;

View file

@ -47,7 +47,14 @@ const ActionMenu = ({ children, buttonProps, title }: Props) => {
>
<div ref={overlayReference}>
{title && <div className={styles.title}>{title}</div>}
<ul className={styles.actionList}>{children}</ul>
<ul
className={styles.actionList}
onClick={() => {
setIsOpen(false);
}}
>
{children}
</ul>
</div>
</ReactModal>
</div>

View file

@ -0,0 +1,41 @@
@use '@/scss/underscore' as _;
.card {
padding: _.unit(8);
max-width: 800px;
> :not(:first-child) {
margin-top: _.unit(6);
}
.headline {
display: flex;
align-items: center;
justify-content: space-between;
.close {
cursor: pointer;
}
}
.line {
border-top: 1px solid var(--color-neutral-90);
}
.description {
font: var(--font-body-2);
}
.hightlight {
color: var(--color-primary-50);
}
.actions {
text-align: right;
> :not(:first-child) {
margin-left: _.unit(4);
}
}
}

View file

@ -0,0 +1,86 @@
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
import { Trans, useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import Button from '@/components/Button';
import Card from '@/components/Card';
import CardTitle from '@/components/CardTitle';
import TextInput from '@/components/TextInput';
import Close from '@/icons/Close';
import api from '@/utilities/api';
import * as styles from './index.module.scss';
type Props = {
id: string;
name: string;
onClose: () => void;
};
const DeleteForm = ({ id, name, onClose }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const navigate = useNavigate();
const [inputName, setInputName] = useState('');
const [loading, setLoading] = useState(false);
const inputMismatched = inputName !== name;
const handleDelete = async () => {
setLoading(true);
try {
await api.delete(`/api/resources/${id}`);
onClose();
navigate(`/api-resources`);
toast.success(t('api_resource_details.api_resource_deleted', { name }));
} finally {
setLoading(false);
}
};
return (
// TODO LOG-1907: Modal
// TODO LOG-1890: Icon Button
<Card className={styles.card}>
<div className={styles.headline}>
<CardTitle title="api_resource_details.reminder" />
<Close className={styles.close} onClick={onClose} />
</div>
<div className={styles.description}>
<Trans
t={t}
i18nKey="api_resource_details.delete_description"
values={{ name }}
components={{ span: <span className={styles.hightlight} /> }}
/>
</div>
<TextInput
value={inputName}
placeholder={t('api_resource_details.enter_your_api_resource_name')}
hasError={inputMismatched}
onChange={(event) => {
setInputName(event.currentTarget.value);
}}
/>
<div className={styles.line} />
<div className={styles.actions}>
<Button
type="outline"
title="admin_console.api_resource_details.cancel"
onClick={onClose}
/>
<Button
disabled={inputMismatched || loading}
type="danger"
title="admin_console.api_resource_details.delete"
onClick={handleDelete}
/>
</div>
</Card>
);
};
export default DeleteForm;

View file

@ -2,9 +2,11 @@ import { Resource } from '@logto/schemas';
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import Modal from 'react-modal';
import { useLocation, useParams } from 'react-router-dom';
import useSWR from 'swr';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import BackLink from '@/components/BackLink';
import Button from '@/components/Button';
import Card from '@/components/Card';
@ -13,9 +15,11 @@ import FormField from '@/components/FormField';
import ImagePlaceholder from '@/components/ImagePlaceholder';
import TabNav, { TabNavLink } from '@/components/TabNav';
import TextInput from '@/components/TextInput';
import * as modalStyles from '@/scss/modal.module.scss';
import { RequestError } from '@/swr';
import api from '@/utilities/api';
import DeleteForm from './components/DeleteForm';
import * as styles from './index.module.scss';
type FormData = {
@ -36,6 +40,8 @@ const ApiResourceDetails = () => {
});
const [submitting, setSubmitting] = useState(false);
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
useEffect(() => {
if (!data) {
return;
@ -79,6 +85,31 @@ const ApiResourceDetails = () => {
</div>
<div className={styles.operation}>
<Button title="admin_console.api_resource_details.check_help_guide" />
<ActionMenu
buttonProps={{ title: 'admin_console.api_resource_details.options' }}
title={t('api_resource_details.more_options')}
>
<ActionMenuItem
onClick={() => {
setIsDeleteFormOpen(true);
}}
>
{t('api_resource_details.options_delete')}
</ActionMenuItem>
</ActionMenu>
<Modal
isOpen={isDeleteFormOpen}
className={modalStyles.content}
overlayClassName={modalStyles.overlay}
>
<DeleteForm
id={data.id}
name={data.name}
onClose={() => {
setIsDeleteFormOpen(false);
}}
/>
</Modal>
</div>
</Card>
<Card className={styles.body}>

View file

@ -104,9 +104,19 @@ const translation = {
api_resource_details: {
back_to_api_resources: 'Back to my API resources',
check_help_guide: 'Check Help Guide',
options: 'Options',
more_options: 'More Options',
options_delete: 'Delete',
settings: 'Settings',
save_changes: 'Save Changes',
token_expiration_time_in_seconds: 'Token Expiration Time (in seconds)',
reminder: 'Reminder',
delete_description:
'This action cannot be undone. This will permanently delete the this application. Please enter the api resource name <span>{{name}}</span> to proceed.',
enter_your_api_resource_name: 'Enter your API resource name',
cancel: 'Cancel',
delete: 'Delete',
api_resource_deleted: 'The API Resource {{name}} deleted.',
},
connectors: {
title: 'Connectors',

View file

@ -106,9 +106,19 @@ const translation = {
api_resource_details: {
back_to_api_resources: 'Back to my API resources',
check_help_guide: 'Check Help Guide',
options: 'Options',
more_options: 'More Options',
options_delete: 'Delete',
settings: 'Settings',
save_changes: 'Save Changes',
token_expiration_time_in_seconds: 'Token Expiration Time (in seconds)',
reminder: 'Reminder',
delete_description:
'This action cannot be undone. This will permanently delete the this application. Please enter the api resource name <span>{{name}}</span> to proceed.',
enter_your_api_resource_name: 'Enter your API resource name',
cancel: 'Cancel',
delete: 'Delete',
api_resource_deleted: 'The API Resource {{name}} deleted.',
},
connectors: {
title: '连接器',