0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -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; bottom: 0;
width: 50%; width: 50%;
max-width: 900px; max-width: 900px;
min-width: 770px; min-width: 800px;
outline: none; outline: none;
background: var(--color-base); 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 { Navigate, Route, Routes, useParams } from 'react-router-dom';
import useSWR from 'swr'; 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 OrganizationIcon from '@/assets/icons/organization-preview.svg';
import ActionsButton from '@/components/ActionsButton';
import AppError from '@/components/AppError'; import AppError from '@/components/AppError';
import DetailsPage from '@/components/DetailsPage'; import DetailsPage from '@/components/DetailsPage';
import DetailsPageHeader from '@/components/DetailsPage/DetailsPageHeader';
import Skeleton from '@/components/DetailsPage/Skeleton'; import Skeleton from '@/components/DetailsPage/Skeleton';
import Drawer from '@/components/Drawer';
import PageMeta from '@/components/PageMeta'; import PageMeta from '@/components/PageMeta';
import ThemedIcon from '@/components/ThemedIcon'; import ThemedIcon from '@/components/ThemedIcon';
import Card from '@/ds-components/Card'; import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import TabNav, { TabNavItem } from '@/ds-components/TabNav';
import useApi, { type RequestError } from '@/hooks/use-api'; import useApi, { type RequestError } from '@/hooks/use-api';
import useTenantPathname from '@/hooks/use-tenant-pathname'; import useTenantPathname from '@/hooks/use-tenant-pathname';
import IntroductionAndPermissions from '../Organizations/Guide/IntroductionAndPermissions';
import Members from './Members'; import Members from './Members';
import Settings from './Settings'; import Settings from './Settings';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
@ -35,6 +39,8 @@ function OrganizationDetails() {
id && `api/organizations/${id}` id && `api/organizations/${id}`
); );
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
const [isGuideDrawerOpen, setIsGuideDrawerOpen] = useState(false);
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
const api = useApi(); const api = useApi();
const deleteOrganization = useCallback(async () => { const deleteOrganization = useCallback(async () => {
@ -60,27 +66,46 @@ function OrganizationDetails() {
{error && <AppError errorCode={error.body?.code} errorMessage={error.body?.message} />} {error && <AppError errorCode={error.body?.code} errorMessage={error.body?.message} />}
{data && ( {data && (
<> <>
<Card className={styles.header}> <DetailsPageHeader
<div className={styles.metadata}> icon={<ThemedIcon for={OrganizationIcon} size={60} />}
<ThemedIcon for={OrganizationIcon} size={60} /> title={data.name}
<div> identifier={{ name: t('organization_details.organization_id'), value: data.id }}
<div className={styles.name}>{data.name}</div> additionalActionButton={{
<div className={styles.row}> icon: <File />,
<span className={styles.label}>{t('organization_details.organization_id')} </span> title: 'application_details.check_guide',
<CopyToClipboard size="default" value={data.id} /> onClick: () => {
</div> setIsGuideDrawerOpen(true);
</div> },
</div> }}
<ActionsButton actionMenuItems={[
buttonProps={{ {
type: 'default', icon: <Delete />,
size: 'large', title: 'general.delete',
}} type: 'danger',
deleteConfirmation="organization_details.delete_confirmation" onClick: () => {
fieldName="organizations.title" setIsDeleteFormOpen(true);
onDelete={deleteOrganization} },
/> },
</Card> ]}
/>
<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> <TabNav>
<TabNavItem href={`${pathname}/${data.id}/${tabs.settings}`}> <TabNavItem href={`${pathname}/${data.id}/${tabs.settings}`}>
{t('general.settings_nav')} {t('general.settings_nav')}

View file

@ -39,9 +39,14 @@ type PermissionForm = {
permissions: Array<Omit<OrganizationScope, 'id' | 'tenantId'>>; 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: '' }; const defaultPermission = { name: '', description: '' };
function CreatePermissions() { function IntroductionAndPermissions({ isReadonly }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.organizations.guide' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.organizations.guide' });
const theme = useTheme(); const theme = useTheme();
const { OrganizationIcon, PermissionIcon } = icons[theme]; const { OrganizationIcon, PermissionIcon } = icons[theme];
@ -100,41 +105,45 @@ function CreatePermissions() {
<OrganizationIcon className={styles.icon} /> <OrganizationIcon className={styles.icon} />
<Introduction /> <Introduction />
</Card> </Card>
<Card className={styles.card}> {!isReadonly && (
<PermissionIcon className={styles.icon} /> <Card className={styles.card}>
<div className={styles.title}>{t('step_1')}</div> <PermissionIcon className={styles.icon} />
<form> <div className={styles.title}>{t('step_1')}</div>
<DynamicFormFields <form>
isLoading={!data && !error} <DynamicFormFields
title="organizations.guide.organization_permissions" isLoading={!data && !error}
fields={fields} title="organizations.guide.organization_permissions"
render={(index) => ( fields={fields}
<div className={styles.fieldGroup}> render={(index) => (
<FormField isRequired title="organizations.guide.permission_name"> <div className={styles.fieldGroup}>
<TextInput <FormField isRequired title="organizations.guide.permission_name">
{...register(`permissions.${index}.name`, { required: true })} <TextInput
error={Boolean(errors.permissions?.[index]?.name)} {...register(`permissions.${index}.name`, { required: true })}
/> error={Boolean(errors.permissions?.[index]?.name)}
</FormField> />
<FormField title="general.description"> </FormField>
<TextInput {...register(`permissions.${index}.description`)} /> <FormField title="general.description">
</FormField> <TextInput {...register(`permissions.${index}.description`)} />
</div> </FormField>
)} </div>
onAdd={() => { )}
append(defaultPermission); onAdd={() => {
}} append(defaultPermission);
onRemove={remove} }}
/> onRemove={remove}
</form> />
</Card> </form>
</Card>
)}
</div> </div>
</OverlayScrollbar> </OverlayScrollbar>
<ActionBar step={1} totalSteps={3}> {!isReadonly && (
<Button isLoading={isSubmitting} title="general.next" type="primary" onClick={onSubmit} /> <ActionBar step={1} totalSteps={3}>
</ActionBar> <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 * as modalStyles from '@/scss/modal.module.scss';
import CreateOrganization from './CreateOrganization'; import CreateOrganization from './CreateOrganization';
import CreatePermissions from './CreatePermissions';
import CreateRoles from './CreateRoles'; import CreateRoles from './CreateRoles';
import IntroductionAndPermissions from './IntroductionAndPermissions';
import { steps } from './const'; import { steps } from './const';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
@ -29,7 +29,7 @@ function Guide() {
/> />
<Routes> <Routes>
<Route index element={<Navigate replace to={steps.createPermissions} />} /> <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.createRoles} element={<CreateRoles />} />
<Route path={steps.createOrganization} element={<CreateOrganization />} /> <Route path={steps.createOrganization} element={<CreateOrganization />} />
</Routes> </Routes>