mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor(console): fixing code review to-dos
This commit is contained in:
parent
63d6a3b7db
commit
52d8e3ad37
47 changed files with 263 additions and 176 deletions
|
@ -11,13 +11,17 @@ type Props = {
|
|||
};
|
||||
|
||||
function OrganizationRolesSelect({ value, onChange, keyword, setKeyword }: Props) {
|
||||
const { data: scopes } = useSearchValues<OrganizationScope>('api/organization-roles', keyword);
|
||||
const { data: scopes, isLoading } = useSearchValues<OrganizationScope>(
|
||||
'api/organization-roles',
|
||||
keyword
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
value={value}
|
||||
options={scopes.map(({ id, name }) => ({ value: id, title: name }))}
|
||||
placeholder="organizations.search_permission_placeholder"
|
||||
isOptionsLoading={isLoading}
|
||||
onChange={onChange}
|
||||
onSearch={setKeyword}
|
||||
/>
|
||||
|
|
|
@ -11,13 +11,17 @@ type Props = {
|
|||
};
|
||||
|
||||
function OrganizationScopesSelect({ value, onChange, keyword, setKeyword }: Props) {
|
||||
const { data: scopes } = useSearchValues<OrganizationScope>('api/organization-scopes', keyword);
|
||||
const { data: scopes, isLoading } = useSearchValues<OrganizationScope>(
|
||||
'api/organization-scopes',
|
||||
keyword
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
value={value}
|
||||
options={scopes.map(({ id, name }) => ({ value: id, title: name }))}
|
||||
placeholder="organizations.search_permission_placeholder"
|
||||
isOptionsLoading={isLoading}
|
||||
onChange={onChange}
|
||||
onSearch={setKeyword}
|
||||
/>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Close from '@/assets/icons/close.svg';
|
||||
import { Ring as Spinner } from '@/ds-components/Spinner';
|
||||
import { onKeyDownHandler } from '@/utils/a11y';
|
||||
|
||||
import Dropdown, { DropdownItem } from '../Dropdown';
|
||||
|
@ -26,6 +27,7 @@ type Props<T> = {
|
|||
isReadOnly?: boolean;
|
||||
error?: string | boolean;
|
||||
placeholder?: AdminConsoleKey;
|
||||
isOptionsLoading?: boolean;
|
||||
};
|
||||
|
||||
function MultiSelect<T extends string>({
|
||||
|
@ -37,6 +39,7 @@ function MultiSelect<T extends string>({
|
|||
isReadOnly,
|
||||
error,
|
||||
placeholder,
|
||||
isOptionsLoading,
|
||||
}: Props<T>) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const selectRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -145,18 +148,25 @@ function MultiSelect<T extends string>({
|
|||
className={styles.dropdown}
|
||||
anchorRef={selectRef}
|
||||
>
|
||||
{filteredOptions.length === 0 && <div className={styles.noResult}>{t('errors.empty')}</div>}
|
||||
{filteredOptions.map(({ value, title }) => (
|
||||
<DropdownItem
|
||||
key={value}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
handleSelect({ value, title });
|
||||
}}
|
||||
>
|
||||
{title ?? value}
|
||||
</DropdownItem>
|
||||
))}
|
||||
{isOptionsLoading && <Spinner className={styles.spinner} />}
|
||||
{!isOptionsLoading && (
|
||||
<>
|
||||
{filteredOptions.length === 0 && (
|
||||
<div className={styles.noResult}>{t('errors.empty')}</div>
|
||||
)}
|
||||
{filteredOptions.map(({ value, title }) => (
|
||||
<DropdownItem
|
||||
key={value}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
handleSelect({ value, title });
|
||||
}}
|
||||
>
|
||||
{title ?? value}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -119,6 +119,11 @@
|
|||
max-height: 288px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
color: var(--color-placeholder);
|
||||
margin: _.unit(2);
|
||||
}
|
||||
|
||||
.noResult {
|
||||
color: var(--color-placeholder);
|
||||
font: var(--font-body-2);
|
||||
|
|
|
@ -9,7 +9,7 @@ import { type RequestError } from './use-api';
|
|||
const useSearchValues = <T>(pathname: string, keyword: string) => {
|
||||
const {
|
||||
data: response,
|
||||
error, // TODO: handle error
|
||||
error,
|
||||
mutate,
|
||||
} = useSWR<[T[], number], RequestError>(
|
||||
buildUrl(pathname, {
|
||||
|
@ -23,11 +23,12 @@ const useSearchValues = <T>(pathname: string, keyword: string) => {
|
|||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isLoading: !response && !error,
|
||||
data,
|
||||
mutate,
|
||||
error,
|
||||
}),
|
||||
[data, error, mutate]
|
||||
[data, error, mutate, response]
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -27,37 +27,36 @@ function AddMembersToOrganization({ organization, isOpen, onClose }: Props) {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const tAction = useActionTranslation();
|
||||
const api = useApi();
|
||||
const { reset, control, handleSubmit } = useForm<{
|
||||
const {
|
||||
reset,
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<{
|
||||
users: User[];
|
||||
scopes: Array<Option<string>>;
|
||||
}>({
|
||||
defaultValues: { users: [], scopes: [] },
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
trySubmitSafe(async (data) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await api.post(`api/organizations/${organization.id}/users`, {
|
||||
await api.post(`api/organizations/${organization.id}/users`, {
|
||||
json: {
|
||||
userIds: data.users.map(({ id }) => id),
|
||||
},
|
||||
});
|
||||
|
||||
if (data.scopes.length > 0) {
|
||||
await api.post(`api/organizations/${organization.id}/users/roles`, {
|
||||
json: {
|
||||
userIds: data.users.map(({ id }) => id),
|
||||
organizationRoleIds: data.scopes.map(({ value }) => value),
|
||||
},
|
||||
});
|
||||
|
||||
if (data.scopes.length > 0) {
|
||||
await api.post(`api/organizations/${organization.id}/users/roles`, {
|
||||
json: {
|
||||
userIds: data.users.map(({ id }) => id),
|
||||
organizationRoleIds: data.scopes.map(({ value }) => value),
|
||||
},
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
onClose();
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -86,7 +85,7 @@ function AddMembersToOrganization({ organization, isOpen, onClose }: Props) {
|
|||
subtitle="organization_details.add_members_to_organization_description"
|
||||
footer={
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
size="large"
|
||||
type="primary"
|
||||
title={<>{tAction('add', 'organization_details.member_other')}</>}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type OrganizationRole, type UserWithOrganizationRoles } from '@logto/schemas';
|
||||
import { type UserWithOrganizationRoles } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
@ -9,7 +9,6 @@ import FormField from '@/ds-components/FormField';
|
|||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
import { type Option } from '@/ds-components/Select/MultiSelect';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useSearchValues from '@/hooks/use-search-values';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import { decapitalize } from '@/utils/string';
|
||||
|
||||
|
@ -26,7 +25,6 @@ function EditOrganizationRolesModal({ organizationId, user, isOpen, onClose }: P
|
|||
const [roles, setRoles] = useState<Array<Option<string>>>(
|
||||
user.organizationRoles.map(({ id, name }) => ({ value: id, title: name }))
|
||||
);
|
||||
const { data } = useSearchValues<OrganizationRole>('api/organization-roles', keyword);
|
||||
const name = user.name ?? decapitalize(t('organization_details.user'));
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const api = useApi();
|
||||
|
@ -60,11 +58,7 @@ function EditOrganizationRolesModal({ organizationId, user, isOpen, onClose }: P
|
|||
})}
|
||||
</>
|
||||
}
|
||||
subtitle={
|
||||
<>
|
||||
Authorize <b>{name}</b> to access the following roles
|
||||
</>
|
||||
}
|
||||
subtitle={<>{t('organization_details.authorize_to_roles', { name })}</>}
|
||||
footer={
|
||||
<Button
|
||||
size="large"
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { type Organization } from '@logto/schemas';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Navigate, Route, Routes, useParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import OrganizationIcon from '@/assets/icons/organization-preview.svg';
|
||||
import ActionsButton from '@/components/ActionsButton';
|
||||
import AppError from '@/components/AppError';
|
||||
import DetailsPage from '@/components/DetailsPage';
|
||||
import Skeleton from '@/components/DetailsPage/Skeleton';
|
||||
import PageMeta from '@/components/PageMeta';
|
||||
import ThemedIcon from '@/components/ThemedIcon';
|
||||
import Card from '@/ds-components/Card';
|
||||
|
@ -45,63 +46,65 @@ function OrganizationDetails() {
|
|||
try {
|
||||
await api.delete(`api/organizations/${id}`);
|
||||
navigate(pathname);
|
||||
} catch (error) {
|
||||
toast.error(String(error));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
}, [api, id, isDeleting, navigate]);
|
||||
|
||||
if (!id || error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const isLoading = !data && !error;
|
||||
|
||||
return (
|
||||
<DetailsPage backLink={pathname} backLinkTitle="organizations.title" className={styles.page}>
|
||||
<PageMeta titleKey="organization_details.page_title" />
|
||||
<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} />
|
||||
{isLoading && <Skeleton />}
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
<ActionsButton
|
||||
buttonProps={{
|
||||
type: 'default',
|
||||
size: 'large',
|
||||
}}
|
||||
deleteConfirmation="organization_details.delete_confirmation"
|
||||
fieldName="organizations.title"
|
||||
onDelete={deleteOrganization}
|
||||
/>
|
||||
</Card>
|
||||
<TabNav>
|
||||
<TabNavItem href={`${pathname}/${id}/${tabs.settings}`}>Settings</TabNavItem>
|
||||
<TabNavItem href={`${pathname}/${id}/${tabs.members}`}>Members</TabNavItem>
|
||||
</TabNav>
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to={tabs.settings} />} />
|
||||
<Route
|
||||
path={tabs.settings}
|
||||
element={
|
||||
<Settings
|
||||
isDeleting={isDeleting}
|
||||
data={data}
|
||||
onUpdated={async (data) => mutate(data)}
|
||||
<ActionsButton
|
||||
buttonProps={{
|
||||
type: 'default',
|
||||
size: 'large',
|
||||
}}
|
||||
deleteConfirmation="organization_details.delete_confirmation"
|
||||
fieldName="organizations.title"
|
||||
onDelete={deleteOrganization}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path={tabs.members} element={<Members organization={data} />} />
|
||||
</Routes>
|
||||
</Card>
|
||||
<TabNav>
|
||||
<TabNavItem href={`${pathname}/${data.id}/${tabs.settings}`}>
|
||||
{t('general.settings_nav')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href={`${pathname}/${data.id}/${tabs.members}`}>
|
||||
{t('organizations.members')}
|
||||
</TabNavItem>
|
||||
</TabNav>
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to={tabs.settings} />} />
|
||||
<Route
|
||||
path={tabs.settings}
|
||||
element={
|
||||
<Settings
|
||||
isDeleting={isDeleting}
|
||||
data={data}
|
||||
onUpdated={async (data) => mutate(data)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path={tabs.members} element={<Members organization={data} />} />
|
||||
</Routes>
|
||||
</>
|
||||
)}
|
||||
</DetailsPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { type Organization, type CreateOrganization } from '@logto/schemas';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
@ -19,27 +19,21 @@ type Props = {
|
|||
|
||||
function CreateOrganizationModal({ isOpen, onClose }: Props) {
|
||||
const api = useApi();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const {
|
||||
reset,
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<Partial<CreateOrganization>>();
|
||||
const submit = handleSubmit(
|
||||
trySubmitSafe(async (json) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const { id } = await api
|
||||
.post('api/organizations', {
|
||||
json,
|
||||
})
|
||||
.json<Organization>();
|
||||
onClose(id);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
const { id } = await api
|
||||
.post('api/organizations', {
|
||||
json,
|
||||
})
|
||||
.json<Organization>();
|
||||
onClose(id);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -62,7 +56,7 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
|
|||
<ModalLayout
|
||||
title="organizations.create_organization"
|
||||
footer={
|
||||
<Button type="primary" title="general.create" isLoading={isLoading} onClick={submit} />
|
||||
<Button type="primary" title="general.create" isLoading={isSubmitting} onClick={submit} />
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { type Organization } from '@logto/schemas';
|
||||
import { joinPath } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import OrganizationIcon from '@/assets/icons/organization-preview.svg';
|
||||
|
@ -24,19 +25,17 @@ function OrganizationsTable() {
|
|||
page_size: String(pageSize),
|
||||
})
|
||||
);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const isLoading = !response && !error;
|
||||
const [data, totalCount] = response ?? [[], 0];
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>; // TODO: Add loading skeleton
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
isLoading={isLoading}
|
||||
rowGroups={[{ key: 'data', data }]}
|
||||
columns={[
|
||||
{
|
||||
title: 'Name',
|
||||
title: t('general.name'),
|
||||
dataIndex: 'name',
|
||||
render: ({ name, id }) => (
|
||||
<ItemPreview
|
||||
|
@ -47,14 +46,14 @@ function OrganizationsTable() {
|
|||
),
|
||||
},
|
||||
{
|
||||
title: 'Organization ID',
|
||||
title: t('organizations.organization_id'),
|
||||
dataIndex: 'id',
|
||||
render: ({ id }) => <CopyToClipboard value={id} variant="text" />,
|
||||
},
|
||||
{
|
||||
title: 'Members',
|
||||
title: t('organizations.members'),
|
||||
dataIndex: 'members',
|
||||
render: () => 'members',
|
||||
render: () => 'members', // TODO: render members
|
||||
},
|
||||
]}
|
||||
rowIndexKey="id"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { type OrganizationScope } from '@logto/schemas';
|
||||
import { type Nullable } from '@silverhand/essentials';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
@ -13,6 +13,7 @@ import TextInput from '@/ds-components/TextInput';
|
|||
import useActionTranslation from '@/hooks/use-action-translation';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
|
||||
const organizationScopesPath = 'api/organization-scopes';
|
||||
|
||||
|
@ -25,12 +26,11 @@ type Props = {
|
|||
/** A modal that allows users to create or edit an organization scope. */
|
||||
function PermissionModal({ isOpen, editData, onClose }: Props) {
|
||||
const api = useApi();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
reset,
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<Partial<OrganizationScope>>();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const tAction = useActionTranslation();
|
||||
|
@ -39,9 +39,8 @@ function PermissionModal({ isOpen, editData, onClose }: Props) {
|
|||
: tAction('create', 'organizations.organization_permission');
|
||||
const action = editData ? t('general.save') : tAction('create', 'organizations.permission');
|
||||
|
||||
const submit = handleSubmit(async (json) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const submit = handleSubmit(
|
||||
trySubmitSafe(async (json) => {
|
||||
await (editData
|
||||
? api.patch(`${organizationScopesPath}/${editData.id}`, {
|
||||
json,
|
||||
|
@ -50,10 +49,8 @@ function PermissionModal({ isOpen, editData, onClose }: Props) {
|
|||
json,
|
||||
}));
|
||||
onClose();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Reset form on open
|
||||
useEffect(() => {
|
||||
|
@ -75,7 +72,7 @@ function PermissionModal({ isOpen, editData, onClose }: Props) {
|
|||
<Button
|
||||
type="primary"
|
||||
title={<DangerousRaw>{action}</DangerousRaw>}
|
||||
isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
onClick={submit}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -37,10 +37,6 @@ function PermissionsField() {
|
|||
|
||||
const isLoading = !response && !error;
|
||||
|
||||
if (isLoading) {
|
||||
return <>loading</>; // TODO: loading state
|
||||
}
|
||||
|
||||
return (
|
||||
<FormField title="organizations.organization_permission_other">
|
||||
<PermissionModal
|
||||
|
@ -53,6 +49,7 @@ function PermissionsField() {
|
|||
/>
|
||||
<TemplateTable
|
||||
rowIndexKey="id"
|
||||
isLoading={isLoading}
|
||||
page={page}
|
||||
totalCount={totalCount}
|
||||
data={data}
|
||||
|
|
|
@ -15,6 +15,7 @@ import TextInput from '@/ds-components/TextInput';
|
|||
import useActionTranslation from '@/hooks/use-action-translation';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
|
||||
const organizationRolePath = 'api/organization-roles';
|
||||
|
||||
|
@ -27,13 +28,12 @@ type Props = {
|
|||
/** A modal that allows users to create or edit an organization role. */
|
||||
function RoleModal({ isOpen, editData, onClose }: Props) {
|
||||
const api = useApi();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
reset,
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<Partial<OrganizationRole> & { scopes: Array<Option<string>> }>({
|
||||
defaultValues: { scopes: [] },
|
||||
});
|
||||
|
@ -45,10 +45,9 @@ function RoleModal({ isOpen, editData, onClose }: Props) {
|
|||
const action = editData ? t('general.save') : tAction('create', 'organizations.role');
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const submit = handleSubmit(async ({ scopes, ...json }) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Create or update role
|
||||
const submit = handleSubmit(
|
||||
trySubmitSafe(async ({ scopes, ...json }) => {
|
||||
// Create or update rol e
|
||||
const { id } = await (editData
|
||||
? api.patch(`${organizationRolePath}/${editData.id}`, {
|
||||
json,
|
||||
|
@ -63,10 +62,8 @@ function RoleModal({ isOpen, editData, onClose }: Props) {
|
|||
json: { organizationScopeIds: scopes.map(({ value }) => value) },
|
||||
});
|
||||
onClose();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Reset form on open
|
||||
useEffect(() => {
|
||||
|
@ -96,7 +93,7 @@ function RoleModal({ isOpen, editData, onClose }: Props) {
|
|||
<Button
|
||||
type="primary"
|
||||
title={<DangerousRaw>{action}</DangerousRaw>}
|
||||
isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
onClick={submit}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -40,10 +40,6 @@ function RolesField() {
|
|||
|
||||
const isLoading = !response && !error;
|
||||
|
||||
if (isLoading) {
|
||||
return <>loading</>; // TODO: loading state
|
||||
}
|
||||
|
||||
return (
|
||||
<FormField title="organizations.organization_role_other">
|
||||
<RoleModal
|
||||
|
@ -56,6 +52,7 @@ function RolesField() {
|
|||
/>
|
||||
<TemplateTable
|
||||
rowIndexKey="id"
|
||||
isLoading={isLoading}
|
||||
page={page}
|
||||
totalCount={totalCount}
|
||||
data={data}
|
||||
|
|
|
@ -14,6 +14,7 @@ type Props<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValue
|
|||
columns: Array<Column<TFieldValues>>;
|
||||
totalCount: number;
|
||||
page: number;
|
||||
isLoading?: boolean;
|
||||
onPageChange: (page: number) => void;
|
||||
onAdd?: () => void;
|
||||
};
|
||||
|
@ -34,42 +35,42 @@ function TemplateTable<
|
|||
onAdd,
|
||||
totalCount,
|
||||
page,
|
||||
isLoading,
|
||||
onPageChange,
|
||||
}: Props<TFieldValues, TName>) {
|
||||
const hasData = data.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasData && (
|
||||
<Table
|
||||
hasBorder
|
||||
className={styles.table}
|
||||
rowGroups={[
|
||||
{
|
||||
key: 'data',
|
||||
data,
|
||||
},
|
||||
]}
|
||||
columns={columns}
|
||||
rowIndexKey={rowIndexKey}
|
||||
pagination={{
|
||||
page,
|
||||
totalCount,
|
||||
pageSize,
|
||||
onChange: onPageChange,
|
||||
}}
|
||||
footer={
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className={styles.addButton}
|
||||
icon={<CirclePlus />}
|
||||
title="general.create_another"
|
||||
onClick={onAdd}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
hasBorder
|
||||
isLoading={isLoading}
|
||||
className={styles.table}
|
||||
rowGroups={[
|
||||
{
|
||||
key: 'data',
|
||||
data,
|
||||
},
|
||||
]}
|
||||
columns={columns}
|
||||
rowIndexKey={rowIndexKey}
|
||||
pagination={{
|
||||
page,
|
||||
totalCount,
|
||||
pageSize,
|
||||
onChange: onPageChange,
|
||||
}}
|
||||
footer={
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className={styles.addButton}
|
||||
icon={<CirclePlus />}
|
||||
title="general.create_another"
|
||||
onClick={onAdd}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{onAdd && !hasData && (
|
||||
<Button
|
||||
className={styles.addButton}
|
||||
|
|
|
@ -12,7 +12,7 @@ export type SearchOptions<Keys extends string> = {
|
|||
|
||||
/**
|
||||
* Build the SQL for searching for a string within a set of fields (case-insensitive) of a
|
||||
* schema.
|
||||
* schema. Fields are joined by `or`.
|
||||
*
|
||||
* - If `search` is `undefined`, it will return an empty SQL.
|
||||
* - If `search` is defined, it will return a SQL that is wrapped in a pair of parentheses.
|
||||
|
|
|
@ -21,9 +21,7 @@ export class OrganizationRoleApi extends ApiFactory<
|
|||
super('organization-roles');
|
||||
}
|
||||
|
||||
override async getList(
|
||||
params?: URLSearchParams | undefined
|
||||
): Promise<OrganizationRoleWithScopes[]> {
|
||||
override async getList(params?: URLSearchParams): Promise<OrganizationRoleWithScopes[]> {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return super.getList(params) as Promise<OrganizationRoleWithScopes[]>;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -14,6 +14,7 @@ const organization_details = {
|
|||
'Find appropriate users by searching name, email, phone, or user ID. Existing members are not shown in the search results.',
|
||||
add_with_organization_role: 'Add with organization role(s)',
|
||||
user: 'User',
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
remove_user_from_organization: 'Remove user from organization',
|
||||
|
|
|
@ -3,6 +3,8 @@ const organization = {
|
|||
title: 'Organizations',
|
||||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
organization_id: 'Organization ID',
|
||||
members: 'Members',
|
||||
create_organization: 'Create organization',
|
||||
organization_name_placeholder: 'My organization',
|
||||
organization_description_placeholder: 'A brief description of the organization.',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
|
@ -27,6 +27,8 @@ const organization_details = {
|
|||
/** UNTRANSLATED */
|
||||
user: 'User',
|
||||
/** UNTRANSLATED */
|
||||
authorize_to_roles: 'Authorize {{name}} to access the following roles:',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles: 'Edit organization roles',
|
||||
/** UNTRANSLATED */
|
||||
edit_organization_roles_of_user: 'Edit organization roles of {{name}}',
|
||||
|
|
|
@ -7,6 +7,10 @@ const organizations = {
|
|||
subtitle:
|
||||
'Represent the teams, business customers, and partner companies that access your applications as organizations.',
|
||||
/** UNTRANSLATED */
|
||||
organization_id: 'Organization ID',
|
||||
/** UNTRANSLATED */
|
||||
members: 'Members',
|
||||
/** UNTRANSLATED */
|
||||
create_organization: 'Create organization',
|
||||
/** UNTRANSLATED */
|
||||
organization_name_placeholder: 'My organization',
|
||||
|
|
Loading…
Add table
Reference in a new issue