Skip to content

Commit 4f83a60

Browse files
committed
✨(frontend) add access request list on doc share modal
Add the access request list to the document share modal, allowing admin to see and manage access requests directly from the modal interface.
1 parent bf0e81e commit 4f83a60

File tree

3 files changed

+161
-1
lines changed

3 files changed

+161
-1
lines changed

src/frontend/apps/impress/src/features/docs/doc-share/api/useDocAccessRequest.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import {
66
useQueryClient,
77
} from '@tanstack/react-query';
88

9-
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
9+
import {
10+
APIError,
11+
APIList,
12+
errorCauses,
13+
fetchAPI,
14+
useAPIInfiniteQuery,
15+
} from '@/api';
1016
import { AccessRequest, Doc, Role } from '@/docs/doc-management';
1117

1218
import { OptionType } from '../types';
@@ -100,3 +106,13 @@ export function useDocAccessRequests(
100106
...queryConfig,
101107
});
102108
}
109+
110+
export const useDocAccessRequestsInfinite = (
111+
params: GetDocAccessRequestsParams,
112+
) => {
113+
return useAPIInfiniteQuery(
114+
KEY_LIST_DOC_ACCESS_REQUESTS,
115+
getDocAccessRequests,
116+
params,
117+
);
118+
};
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
2+
import { useMemo } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
5+
import {
6+
Box,
7+
DropdownMenu,
8+
DropdownMenuOption,
9+
IconOptions,
10+
LoadMoreText,
11+
} from '@/components';
12+
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
13+
import { useCunninghamTheme } from '@/cunningham';
14+
import { AccessRequest, Doc, Role } from '@/docs/doc-management/';
15+
import { useResponsiveStore } from '@/stores';
16+
17+
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
18+
import { useDocAccessRequestsInfinite } from '../api/useDocAccessRequest';
19+
20+
import { DocRoleDropdown } from './DocRoleDropdown';
21+
import { SearchUserRow } from './SearchUserRow';
22+
23+
type Props = {
24+
doc: Doc;
25+
accessRequest: AccessRequest;
26+
};
27+
28+
const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
29+
const { t } = useTranslation();
30+
const { toast } = useToastProvider();
31+
const { isDesktop } = useResponsiveStore();
32+
const { spacingsTokens } = useCunninghamTheme();
33+
34+
const { mutate: updateDocAccess } = useUpdateDocAccess({
35+
onError: () => {
36+
toast(t('Error during invitation update'), VariantType.ERROR, {
37+
duration: 4000,
38+
});
39+
},
40+
});
41+
42+
const { mutate: removeDocAccess } = useDeleteDocAccess({
43+
onError: () => {
44+
toast(t('Error while deleting invitation'), VariantType.ERROR, {
45+
duration: 4000,
46+
});
47+
},
48+
});
49+
50+
const onUpdate = (newRole: Role) => {
51+
updateDocAccess({
52+
docId: doc.id,
53+
role: newRole,
54+
accessId: accessRequest.id,
55+
});
56+
};
57+
58+
const onRemove = () => {
59+
removeDocAccess({ accessId: accessRequest.id, docId: doc.id });
60+
};
61+
62+
const moreActions: DropdownMenuOption[] = [
63+
{
64+
label: t('Delete'),
65+
icon: 'delete',
66+
callback: onRemove,
67+
disabled: !accessRequest.abilities.destroy,
68+
},
69+
];
70+
71+
return (
72+
<Box
73+
$width="100%"
74+
data-testid={`doc-share-access-request-row-${accessRequest.user.email}`}
75+
className="--docs--doc-share-access-request-item"
76+
>
77+
<SearchUserRow
78+
alwaysShowRight={true}
79+
user={accessRequest.user}
80+
right={
81+
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
82+
<DocRoleDropdown
83+
currentRole={accessRequest.role}
84+
onSelectRole={onUpdate}
85+
canUpdate={doc.abilities.accesses_manage}
86+
/>
87+
88+
{isDesktop && doc.abilities.accesses_manage && (
89+
<DropdownMenu options={moreActions}>
90+
<IconOptions
91+
isHorizontal
92+
data-testid="doc-share-access-request-more-actions"
93+
$variation="600"
94+
/>
95+
</DropdownMenu>
96+
)}
97+
</Box>
98+
}
99+
/>
100+
</Box>
101+
);
102+
};
103+
104+
interface QuickSearchGroupAccessRequestProps {
105+
doc: Doc;
106+
}
107+
108+
export const QuickSearchGroupAccessRequest = ({
109+
doc,
110+
}: QuickSearchGroupAccessRequestProps) => {
111+
const { t } = useTranslation();
112+
const accessRequestQuery = useDocAccessRequestsInfinite({ docId: doc.id });
113+
114+
const accessRequestsData: QuickSearchData<AccessRequest> = useMemo(() => {
115+
const accessRequests =
116+
accessRequestQuery.data?.pages.flatMap((page) => page.results) || [];
117+
118+
return {
119+
groupName: t('Access Requests'),
120+
elements: accessRequests,
121+
endActions: accessRequestQuery.hasNextPage
122+
? [
123+
{
124+
content: <LoadMoreText data-testid="load-more-requests" />,
125+
onSelect: () => void accessRequestQuery.fetchNextPage(),
126+
},
127+
]
128+
: undefined,
129+
};
130+
}, [accessRequestQuery, t]);
131+
132+
return (
133+
<Box aria-label={t('List request access card')}>
134+
<QuickSearchGroup
135+
group={accessRequestsData}
136+
renderElement={(accessRequest) => (
137+
<DocShareAccessRequestItem doc={doc} accessRequest={accessRequest} />
138+
)}
139+
/>
140+
</Box>
141+
);
142+
};

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { isValidEmail } from '@/utils';
1717

1818
import { KEY_LIST_USER, useUsers } from '../api';
1919

20+
import { QuickSearchGroupAccessRequest } from './DocShareAccessRequest';
2021
import { DocShareAddMemberList } from './DocShareAddMemberList';
2122
import {
2223
DocShareModalInviteUserRow,
@@ -173,6 +174,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
173174
>
174175
{showMemberSection ? (
175176
<>
177+
<QuickSearchGroupAccessRequest doc={doc} />
176178
<QuickSearchGroupInvitation doc={doc} />
177179
<QuickSearchGroupMember doc={doc} />
178180
</>

0 commit comments

Comments
 (0)