0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

Merge pull request #4811 from logto-io/charles-add-org-guide-drawer

feat(console): add organization details guide drawer
This commit is contained in:
Charles Zhao 2023-11-02 15:50:00 +08:00 committed by GitHub
commit 9ea79a18d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 60 deletions

View file

@ -7,7 +7,7 @@
bottom: 0;
width: 50%;
max-width: 900px;
min-width: 770px;
min-width: 800px;
outline: none;
background: var(--color-base);

View file

@ -4,19 +4,23 @@ import { useTranslation } from 'react-i18next';
import { Navigate, Route, Routes, useParams } from 'react-router-dom';
import useSWR from 'swr';
import Delete from '@/assets/icons/delete.svg';
import File from '@/assets/icons/file.svg';
import OrganizationIcon from '@/assets/icons/organization-preview.svg';
import ActionsButton from '@/components/ActionsButton';
import AppError from '@/components/AppError';
import DetailsPage from '@/components/DetailsPage';
import DetailsPageHeader from '@/components/DetailsPage/DetailsPageHeader';
import Skeleton from '@/components/DetailsPage/Skeleton';
import Drawer from '@/components/Drawer';
import PageMeta from '@/components/PageMeta';
import ThemedIcon from '@/components/ThemedIcon';
import Card from '@/ds-components/Card';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import useApi, { type RequestError } from '@/hooks/use-api';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import IntroductionAndPermissions from '../Organizations/Guide/IntroductionAndPermissions';
import Members from './Members';
import Settings from './Settings';
import * as styles from './index.module.scss';
@ -35,6 +39,8 @@ function OrganizationDetails() {
id && `api/organizations/${id}`
);
const [isDeleting, setIsDeleting] = useState(false);
const [isGuideDrawerOpen, setIsGuideDrawerOpen] = useState(false);
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
const api = useApi();
const deleteOrganization = useCallback(async () => {
@ -60,27 +66,46 @@ function OrganizationDetails() {
{error && <AppError errorCode={error.body?.code} errorMessage={error.body?.message} />}
{data && (
<>
<Card className={styles.header}>
<div className={styles.metadata}>
<ThemedIcon for={OrganizationIcon} size={60} />
<div>
<div className={styles.name}>{data.name}</div>
<div className={styles.row}>
<span className={styles.label}>{t('organization_details.organization_id')} </span>
<CopyToClipboard size="default" value={data.id} />
</div>
</div>
</div>
<ActionsButton
buttonProps={{
type: 'default',
size: 'large',
}}
deleteConfirmation="organization_details.delete_confirmation"
fieldName="organizations.title"
onDelete={deleteOrganization}
/>
</Card>
<DetailsPageHeader
icon={<ThemedIcon for={OrganizationIcon} size={60} />}
title={data.name}
identifier={{ name: t('organization_details.organization_id'), value: data.id }}
additionalActionButton={{
icon: <File />,
title: 'application_details.check_guide',
onClick: () => {
setIsGuideDrawerOpen(true);
},
}}
actionMenuItems={[
{
icon: <Delete />,
title: 'general.delete',
type: 'danger',
onClick: () => {
setIsDeleteFormOpen(true);
},
},
]}
/>
<Drawer
isOpen={isGuideDrawerOpen}
onClose={() => {
setIsGuideDrawerOpen(false);
}}
>
<IntroductionAndPermissions isReadonly />
</Drawer>
<DeleteConfirmModal
isOpen={isDeleteFormOpen}
isLoading={isDeleting}
onCancel={() => {
setIsDeleteFormOpen(false);
}}
onConfirm={deleteOrganization}
>
{t('organization_details.delete_confirmation')}
</DeleteConfirmModal>
<TabNav>
<TabNavItem href={`${pathname}/${data.id}/${tabs.settings}`}>
{t('general.settings_nav')}

View file

@ -39,9 +39,14 @@ type PermissionForm = {
permissions: Array<Omit<OrganizationScope, 'id' | 'tenantId'>>;
};
type Props = {
/* True if the guide is in the "Check guide" drawer of organization details page */
isReadonly?: boolean;
};
const defaultPermission = { name: '', description: '' };
function CreatePermissions() {
function IntroductionAndPermissions({ isReadonly }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.organizations.guide' });
const theme = useTheme();
const { OrganizationIcon, PermissionIcon } = icons[theme];
@ -100,41 +105,45 @@ function CreatePermissions() {
<OrganizationIcon className={styles.icon} />
<Introduction />
</Card>
<Card className={styles.card}>
<PermissionIcon className={styles.icon} />
<div className={styles.title}>{t('step_1')}</div>
<form>
<DynamicFormFields
isLoading={!data && !error}
title="organizations.guide.organization_permissions"
fields={fields}
render={(index) => (
<div className={styles.fieldGroup}>
<FormField isRequired title="organizations.guide.permission_name">
<TextInput
{...register(`permissions.${index}.name`, { required: true })}
error={Boolean(errors.permissions?.[index]?.name)}
/>
</FormField>
<FormField title="general.description">
<TextInput {...register(`permissions.${index}.description`)} />
</FormField>
</div>
)}
onAdd={() => {
append(defaultPermission);
}}
onRemove={remove}
/>
</form>
</Card>
{!isReadonly && (
<Card className={styles.card}>
<PermissionIcon className={styles.icon} />
<div className={styles.title}>{t('step_1')}</div>
<form>
<DynamicFormFields
isLoading={!data && !error}
title="organizations.guide.organization_permissions"
fields={fields}
render={(index) => (
<div className={styles.fieldGroup}>
<FormField isRequired title="organizations.guide.permission_name">
<TextInput
{...register(`permissions.${index}.name`, { required: true })}
error={Boolean(errors.permissions?.[index]?.name)}
/>
</FormField>
<FormField title="general.description">
<TextInput {...register(`permissions.${index}.description`)} />
</FormField>
</div>
)}
onAdd={() => {
append(defaultPermission);
}}
onRemove={remove}
/>
</form>
</Card>
)}
</div>
</OverlayScrollbar>
<ActionBar step={1} totalSteps={3}>
<Button isLoading={isSubmitting} title="general.next" type="primary" onClick={onSubmit} />
</ActionBar>
{!isReadonly && (
<ActionBar step={1} totalSteps={3}>
<Button isLoading={isSubmitting} title="general.next" type="primary" onClick={onSubmit} />
</ActionBar>
)}
</>
);
}
export default CreatePermissions;
export default IntroductionAndPermissions;

View file

@ -7,8 +7,8 @@ import useTenantPathname from '@/hooks/use-tenant-pathname';
import * as modalStyles from '@/scss/modal.module.scss';
import CreateOrganization from './CreateOrganization';
import CreatePermissions from './CreatePermissions';
import CreateRoles from './CreateRoles';
import IntroductionAndPermissions from './IntroductionAndPermissions';
import { steps } from './const';
import * as styles from './index.module.scss';
@ -29,7 +29,7 @@ function Guide() {
/>
<Routes>
<Route index element={<Navigate replace to={steps.createPermissions} />} />
<Route path={steps.createPermissions} element={<CreatePermissions />} />
<Route path={steps.createPermissions} element={<IntroductionAndPermissions />} />
<Route path={steps.createRoles} element={<CreateRoles />} />
<Route path={steps.createOrganization} element={<CreateOrganization />} />
</Routes>