♻️(frontend) improve separation of concerns in DocShareModal
Improve separation of concerns in the DocShareModal component. The member and invitation list are now in a separate component. It will help us to integrate cleanly the request access list.
This commit is contained in:
committed by
Manuel Raynaud
parent
394f91387d
commit
411d52c73b
@@ -66,10 +66,5 @@ export const useDeleteDocAccess = (options?: UseDeleteDocAccessOptions) => {
|
|||||||
void options.onSuccess(data, variables, context);
|
void options.onSuccess(data, variables, context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error, variables, context) => {
|
|
||||||
if (options?.onError) {
|
|
||||||
void options.onError(error, variables, context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import {
|
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||||
DefinedInitialDataInfiniteOptions,
|
|
||||||
InfiniteData,
|
|
||||||
QueryKey,
|
|
||||||
UseQueryOptions,
|
|
||||||
useInfiniteQuery,
|
|
||||||
useQuery,
|
|
||||||
} from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
import {
|
||||||
|
APIError,
|
||||||
|
APIList,
|
||||||
|
errorCauses,
|
||||||
|
fetchAPI,
|
||||||
|
useAPIInfiniteQuery,
|
||||||
|
} from '@/api';
|
||||||
import { Access } from '@/docs/doc-management';
|
import { Access } from '@/docs/doc-management';
|
||||||
|
|
||||||
export type DocAccessesParam = {
|
export type DocAccessesParams = {
|
||||||
docId: string;
|
docId: string;
|
||||||
ordering?: string;
|
ordering?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DocAccessesAPIParams = DocAccessesParam & {
|
export type DocAccessesAPIParams = DocAccessesParams & {
|
||||||
page: number;
|
page: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,33 +61,6 @@ export function useDocAccesses(
|
|||||||
* @param queryConfig
|
* @param queryConfig
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useDocAccessesInfinite(
|
export function useDocAccessesInfinite(params: DocAccessesParams) {
|
||||||
param: DocAccessesParam,
|
return useAPIInfiniteQuery(KEY_LIST_DOC_ACCESSES, getDocAccesses, params);
|
||||||
queryConfig?: DefinedInitialDataInfiniteOptions<
|
|
||||||
AccessesResponse,
|
|
||||||
APIError,
|
|
||||||
InfiniteData<AccessesResponse>,
|
|
||||||
QueryKey,
|
|
||||||
number
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
return useInfiniteQuery<
|
|
||||||
AccessesResponse,
|
|
||||||
APIError,
|
|
||||||
InfiniteData<AccessesResponse>,
|
|
||||||
QueryKey,
|
|
||||||
number
|
|
||||||
>({
|
|
||||||
initialPageParam: 1,
|
|
||||||
queryKey: [KEY_LIST_DOC_ACCESSES, param],
|
|
||||||
queryFn: ({ pageParam }) =>
|
|
||||||
getDocAccesses({
|
|
||||||
...param,
|
|
||||||
page: pageParam,
|
|
||||||
}),
|
|
||||||
getNextPageParam(lastPage, allPages) {
|
|
||||||
return lastPage.next ? allPages.length + 1 : undefined;
|
|
||||||
},
|
|
||||||
...queryConfig,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import {
|
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||||
DefinedInitialDataInfiniteOptions,
|
|
||||||
InfiniteData,
|
|
||||||
QueryKey,
|
|
||||||
UseQueryOptions,
|
|
||||||
useInfiniteQuery,
|
|
||||||
useQuery,
|
|
||||||
} from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
import {
|
||||||
|
APIError,
|
||||||
|
APIList,
|
||||||
|
errorCauses,
|
||||||
|
fetchAPI,
|
||||||
|
useAPIInfiniteQuery,
|
||||||
|
} from '@/api';
|
||||||
import { Invitation } from '@/docs/doc-share/types';
|
import { Invitation } from '@/docs/doc-share/types';
|
||||||
|
|
||||||
export type DocInvitationsParams = {
|
export type DocInvitationsParams = {
|
||||||
@@ -66,33 +65,10 @@ export function useDocInvitations(
|
|||||||
* @param queryConfig
|
* @param queryConfig
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useDocInvitationsInfinite(
|
export function useDocInvitationsInfinite(params: DocInvitationsParams) {
|
||||||
param: DocInvitationsParams,
|
return useAPIInfiniteQuery(
|
||||||
queryConfig?: DefinedInitialDataInfiniteOptions<
|
KEY_LIST_DOC_INVITATIONS,
|
||||||
DocInvitationsResponse,
|
getDocInvitations,
|
||||||
APIError,
|
params,
|
||||||
InfiniteData<DocInvitationsResponse>,
|
);
|
||||||
QueryKey,
|
|
||||||
number
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
return useInfiniteQuery<
|
|
||||||
DocInvitationsResponse,
|
|
||||||
APIError,
|
|
||||||
InfiniteData<DocInvitationsResponse>,
|
|
||||||
QueryKey,
|
|
||||||
number
|
|
||||||
>({
|
|
||||||
initialPageParam: 1,
|
|
||||||
queryKey: [KEY_LIST_DOC_INVITATIONS, param],
|
|
||||||
queryFn: ({ pageParam }) =>
|
|
||||||
getDocInvitations({
|
|
||||||
...param,
|
|
||||||
page: pageParam,
|
|
||||||
}),
|
|
||||||
getNextPageParam(lastPage, allPages) {
|
|
||||||
return lastPage.next ? allPages.length + 1 : undefined;
|
|
||||||
},
|
|
||||||
...queryConfig,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,5 @@ export const useUpdateDocAccess = (options?: UseUpdateDocAccessOptions) => {
|
|||||||
void options.onSuccess(data, variables, context);
|
void options.onSuccess(data, variables, context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error, variables, context) => {
|
|
||||||
if (options?.onError) {
|
|
||||||
void options.onError(error, variables, context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,44 @@
|
|||||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuOption,
|
DropdownMenuOption,
|
||||||
|
Icon,
|
||||||
IconOptions,
|
IconOptions,
|
||||||
|
LoadMoreText,
|
||||||
|
Text,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc, Role } from '@/docs/doc-management';
|
import { Doc, Role } from '@/docs/doc-management';
|
||||||
import { User } from '@/features/auth';
|
import { User } from '@/features/auth';
|
||||||
|
|
||||||
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
|
import {
|
||||||
|
useDeleteDocInvitation,
|
||||||
|
useDocInvitationsInfinite,
|
||||||
|
useUpdateDocInvitation,
|
||||||
|
} from '../api';
|
||||||
import { Invitation } from '../types';
|
import { Invitation } from '../types';
|
||||||
|
|
||||||
import { DocRoleDropdown } from './DocRoleDropdown';
|
import { DocRoleDropdown } from './DocRoleDropdown';
|
||||||
import { SearchUserRow } from './SearchUserRow';
|
import { SearchUserRow } from './SearchUserRow';
|
||||||
|
|
||||||
type Props = {
|
type DocShareInvitationItemProps = {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
invitation: Invitation;
|
invitation: Invitation;
|
||||||
};
|
};
|
||||||
export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
|
||||||
|
const DocShareInvitationItem = ({
|
||||||
|
doc,
|
||||||
|
invitation,
|
||||||
|
}: DocShareInvitationItemProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { spacingsTokens } = useCunninghamTheme();
|
const { spacingsTokens } = useCunninghamTheme();
|
||||||
const fakeUser: User = {
|
const invitedUser: User = {
|
||||||
id: invitation.email,
|
id: invitation.email,
|
||||||
full_name: invitation.email,
|
full_name: invitation.email,
|
||||||
email: invitation.email,
|
email: invitation.email,
|
||||||
@@ -79,6 +93,7 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
|||||||
disabled: !canUpdate,
|
disabled: !canUpdate,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
$width="100%"
|
$width="100%"
|
||||||
@@ -88,7 +103,7 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
|||||||
<SearchUserRow
|
<SearchUserRow
|
||||||
isInvitation={true}
|
isInvitation={true}
|
||||||
alwaysShowRight={true}
|
alwaysShowRight={true}
|
||||||
user={fakeUser}
|
user={invitedUser}
|
||||||
right={
|
right={
|
||||||
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
|
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
|
||||||
<DocRoleDropdown
|
<DocRoleDropdown
|
||||||
@@ -111,3 +126,85 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DocShareModalInviteUserRowProps = {
|
||||||
|
user: User;
|
||||||
|
};
|
||||||
|
export const DocShareModalInviteUserRow = ({
|
||||||
|
user,
|
||||||
|
}: DocShareModalInviteUserRowProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
$width="100%"
|
||||||
|
data-testid={`search-user-row-${user.email}`}
|
||||||
|
className="--docs--doc-share-modal-invite-user-row"
|
||||||
|
>
|
||||||
|
<SearchUserRow
|
||||||
|
user={user}
|
||||||
|
right={
|
||||||
|
<Box
|
||||||
|
className="right-hover"
|
||||||
|
$direction="row"
|
||||||
|
$align="center"
|
||||||
|
$css={css`
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: var(--c--theme--font--sizes--sm);
|
||||||
|
color: var(--c--theme--colors--greyscale-400);
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Text $theme="primary" $variation="800">
|
||||||
|
{t('Add')}
|
||||||
|
</Text>
|
||||||
|
<Icon $theme="primary" $variation="800" iconName="add" />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface QuickSearchGroupInvitationProps {
|
||||||
|
doc: Doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QuickSearchGroupInvitation = ({
|
||||||
|
doc,
|
||||||
|
}: QuickSearchGroupInvitationProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data, hasNextPage, fetchNextPage } = useDocInvitationsInfinite({
|
||||||
|
docId: doc.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitationsData: QuickSearchData<Invitation> = useMemo(() => {
|
||||||
|
const invitations = data?.pages.flatMap((page) => page.results) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupName: t('Pending invitations'),
|
||||||
|
elements: invitations,
|
||||||
|
endActions: hasNextPage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
content: <LoadMoreText data-testid="load-more-invitations" />,
|
||||||
|
onSelect: () => void fetchNextPage(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}, [data?.pages, fetchNextPage, hasNextPage, t]);
|
||||||
|
|
||||||
|
if (!invitationsData.elements.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box aria-label={t('List invitation card')}>
|
||||||
|
<QuickSearchGroup
|
||||||
|
group={invitationsData}
|
||||||
|
renderElement={(invitation) => (
|
||||||
|
<DocShareInvitationItem doc={doc} invitation={invitation} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -6,13 +7,19 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuOption,
|
DropdownMenuOption,
|
||||||
IconOptions,
|
IconOptions,
|
||||||
|
LoadMoreText,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Access, Doc, Role } from '@/docs/doc-management/';
|
import { Access, Doc, Role } from '@/docs/doc-management/';
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
|
|
||||||
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
|
import {
|
||||||
import { useWhoAmI } from '../hooks/';
|
useDeleteDocAccess,
|
||||||
|
useDocAccessesInfinite,
|
||||||
|
useUpdateDocAccess,
|
||||||
|
} from '../api';
|
||||||
|
import { useWhoAmI } from '../hooks';
|
||||||
|
|
||||||
import { DocRoleDropdown } from './DocRoleDropdown';
|
import { DocRoleDropdown } from './DocRoleDropdown';
|
||||||
import { SearchUserRow } from './SearchUserRow';
|
import { SearchUserRow } from './SearchUserRow';
|
||||||
@@ -21,7 +28,8 @@ type Props = {
|
|||||||
doc: Doc;
|
doc: Doc;
|
||||||
access: Access;
|
access: Access;
|
||||||
};
|
};
|
||||||
export const DocShareMemberItem = ({ doc, access }: Props) => {
|
|
||||||
|
const DocShareMemberItem = ({ doc, access }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isLastOwner } = useWhoAmI(access);
|
const { isLastOwner } = useWhoAmI(access);
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
@@ -36,7 +44,7 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
|||||||
|
|
||||||
const { mutate: updateDocAccess } = useUpdateDocAccess({
|
const { mutate: updateDocAccess } = useUpdateDocAccess({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast(t('Error during invitation update'), VariantType.ERROR, {
|
toast(t('Error while updating the member role.'), VariantType.ERROR, {
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -44,7 +52,7 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
|||||||
|
|
||||||
const { mutate: removeDocAccess } = useDeleteDocAccess({
|
const { mutate: removeDocAccess } = useDeleteDocAccess({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast(t('Error while deleting invitation'), VariantType.ERROR, {
|
toast(t('Error while deleting the member.'), VariantType.ERROR, {
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -105,3 +113,52 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface QuickSearchGroupMemberProps {
|
||||||
|
doc: Doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QuickSearchGroupMember = ({
|
||||||
|
doc,
|
||||||
|
}: QuickSearchGroupMemberProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const membersQuery = useDocAccessesInfinite({
|
||||||
|
docId: doc.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const membersData: QuickSearchData<Access> = useMemo(() => {
|
||||||
|
const members =
|
||||||
|
membersQuery.data?.pages.flatMap((page) => page.results) || [];
|
||||||
|
|
||||||
|
const count = membersQuery.data?.pages[0]?.count ?? 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupName:
|
||||||
|
count === 1
|
||||||
|
? t('Document owner')
|
||||||
|
: t('Share with {{count}} users', {
|
||||||
|
count: count,
|
||||||
|
}),
|
||||||
|
elements: members,
|
||||||
|
endActions: membersQuery.hasNextPage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
content: <LoadMoreText data-testid="load-more-members" />,
|
||||||
|
onSelect: () => void membersQuery.fetchNextPage(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}, [membersQuery, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box aria-label={t('List members card')}>
|
||||||
|
<QuickSearchGroup
|
||||||
|
group={membersData}
|
||||||
|
renderElement={(access) => (
|
||||||
|
<DocShareMemberItem doc={doc} access={access} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,30 +4,26 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { createGlobalStyle, css } from 'styled-components';
|
import { createGlobalStyle, css } from 'styled-components';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import { Box, HorizontalSeparator, LoadMoreText, Text } from '@/components';
|
import { Box, HorizontalSeparator, Text } from '@/components';
|
||||||
import {
|
import {
|
||||||
QuickSearch,
|
QuickSearch,
|
||||||
QuickSearchData,
|
QuickSearchData,
|
||||||
QuickSearchGroup,
|
QuickSearchGroup,
|
||||||
} from '@/components/quick-search/';
|
} from '@/components/quick-search/';
|
||||||
import { User } from '@/features/auth';
|
import { User } from '@/features/auth';
|
||||||
import { Access, Doc } from '@/features/docs';
|
import { Doc } from '@/features/docs';
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
import { isValidEmail } from '@/utils';
|
import { isValidEmail } from '@/utils';
|
||||||
|
|
||||||
import {
|
import { KEY_LIST_USER, useUsers } from '../api';
|
||||||
KEY_LIST_USER,
|
|
||||||
useDocAccessesInfinite,
|
|
||||||
useDocInvitationsInfinite,
|
|
||||||
useUsers,
|
|
||||||
} from '../api';
|
|
||||||
import { Invitation } from '../types';
|
|
||||||
|
|
||||||
import { DocShareAddMemberList } from './DocShareAddMemberList';
|
import { DocShareAddMemberList } from './DocShareAddMemberList';
|
||||||
import { DocShareInvitationItem } from './DocShareInvitationItem';
|
import {
|
||||||
import { DocShareMemberItem } from './DocShareMemberItem';
|
DocShareModalInviteUserRow,
|
||||||
|
QuickSearchGroupInvitation,
|
||||||
|
} from './DocShareInvitation';
|
||||||
|
import { QuickSearchGroupMember } from './DocShareMember';
|
||||||
import { DocShareModalFooter } from './DocShareModalFooter';
|
import { DocShareModalFooter } from './DocShareModalFooter';
|
||||||
import { DocShareModalInviteUserRow } from './DocShareModalInviteUserByEmail';
|
|
||||||
|
|
||||||
const ShareModalStyle = createGlobalStyle`
|
const ShareModalStyle = createGlobalStyle`
|
||||||
.c__modal__title {
|
.c__modal__title {
|
||||||
@@ -66,10 +62,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
setInputValue('');
|
setInputValue('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const membersQuery = useDocAccessesInfinite({
|
|
||||||
docId: doc.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchUsersQuery = useUsers(
|
const searchUsersQuery = useUsers(
|
||||||
{ query: userQuery, docId: doc.id },
|
{ query: userQuery, docId: doc.id },
|
||||||
{
|
{
|
||||||
@@ -78,31 +70,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const membersData: QuickSearchData<Access> = useMemo(() => {
|
|
||||||
const members =
|
|
||||||
membersQuery.data?.pages.flatMap((page) => page.results) || [];
|
|
||||||
|
|
||||||
const count = membersQuery.data?.pages[0]?.count ?? 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupName:
|
|
||||||
count === 1
|
|
||||||
? t('Document owner')
|
|
||||||
: t('Share with {{count}} users', {
|
|
||||||
count: count,
|
|
||||||
}),
|
|
||||||
elements: members,
|
|
||||||
endActions: membersQuery.hasNextPage
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
content: <LoadMoreText data-testid="load-more-members" />,
|
|
||||||
onSelect: () => void membersQuery.fetchNextPage(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}, [membersQuery, t]);
|
|
||||||
|
|
||||||
const onFilter = useDebouncedCallback((str: string) => {
|
const onFilter = useDebouncedCallback((str: string) => {
|
||||||
setUserQuery(str);
|
setUserQuery(str);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -205,10 +172,10 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
placeholder={t('Type a name or email')}
|
placeholder={t('Type a name or email')}
|
||||||
>
|
>
|
||||||
{showMemberSection ? (
|
{showMemberSection ? (
|
||||||
<QuickSearchMemberSection
|
<>
|
||||||
doc={doc}
|
<QuickSearchGroupInvitation doc={doc} />
|
||||||
membersData={membersData}
|
<QuickSearchGroupMember doc={doc} />
|
||||||
/>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<QuickSearchInviteInputSection
|
<QuickSearchInviteInputSection
|
||||||
searchUsersRawData={searchUsersQuery.data}
|
searchUsersRawData={searchUsersQuery.data}
|
||||||
@@ -279,59 +246,3 @@ const QuickSearchInviteInputSection = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QuickSearchMemberSectionProps {
|
|
||||||
doc: Doc;
|
|
||||||
membersData: QuickSearchData<Access>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QuickSearchMemberSection = ({
|
|
||||||
doc,
|
|
||||||
membersData,
|
|
||||||
}: QuickSearchMemberSectionProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data, hasNextPage, fetchNextPage } = useDocInvitationsInfinite({
|
|
||||||
docId: doc.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const invitationsData: QuickSearchData<Invitation> = useMemo(() => {
|
|
||||||
const invitations = data?.pages.flatMap((page) => page.results) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupName: t('Pending invitations'),
|
|
||||||
elements: invitations,
|
|
||||||
endActions: hasNextPage
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
content: <LoadMoreText data-testid="load-more-invitations" />,
|
|
||||||
onSelect: () => void fetchNextPage(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}, [data?.pages, fetchNextPage, hasNextPage, t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{invitationsData.elements.length > 0 && (
|
|
||||||
<Box aria-label={t('List invitation card')}>
|
|
||||||
<QuickSearchGroup
|
|
||||||
group={invitationsData}
|
|
||||||
renderElement={(invitation) => (
|
|
||||||
<DocShareInvitationItem doc={doc} invitation={invitation} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box aria-label={t('List members card')}>
|
|
||||||
<QuickSearchGroup
|
|
||||||
group={membersData}
|
|
||||||
renderElement={(access) => (
|
|
||||||
<DocShareMemberItem doc={doc} access={access} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { css } from 'styled-components';
|
|
||||||
|
|
||||||
import { Box, Icon, Text } from '@/components';
|
|
||||||
import { User } from '@/features/auth';
|
|
||||||
|
|
||||||
import { SearchUserRow } from './SearchUserRow';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
user: User;
|
|
||||||
};
|
|
||||||
export const DocShareModalInviteUserRow = ({ user }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
$width="100%"
|
|
||||||
data-testid={`search-user-row-${user.email}`}
|
|
||||||
className="--docs--doc-share-modal-invite-user-row"
|
|
||||||
>
|
|
||||||
<SearchUserRow
|
|
||||||
user={user}
|
|
||||||
right={
|
|
||||||
<Box
|
|
||||||
className="right-hover"
|
|
||||||
$direction="row"
|
|
||||||
$align="center"
|
|
||||||
$css={css`
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
font-size: var(--c--theme--font--sizes--sm);
|
|
||||||
color: var(--c--theme--colors--greyscale-400);
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Text $theme="primary" $variation="800">
|
|
||||||
{t('Add')}
|
|
||||||
</Text>
|
|
||||||
<Icon $theme="primary" $variation="800" iconName="add" />
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user