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:
commit
9ea79a18d6
7 changed files with 94 additions and 60 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -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;
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue