Skip to content

Commit

Permalink
feat: Connect CreateCollectionModal with API
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Sep 7, 2024
1 parent 718ff7f commit ea2ba75
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 15 deletions.
54 changes: 43 additions & 11 deletions src/library-authoring/create-collection/CreateCollectionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,73 @@ import {
ModalDialog,
} from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useParams } from 'react-router-dom';
import { LibraryContext } from '../common/context';
import messages from './messages';
import { useCreateLibraryCollection } from '../data/apiHooks';
import { ToastContext } from '../../generic/toast-context';

const CreateCollectionModal = () => {
const intl = useIntl();
const { libraryId } = useParams();
const create = useCreateLibraryCollection(libraryId);
const {
isCreateCollectionModalOpen,
closeCreateCollectionModal,
} = React.useContext(LibraryContext);
const { showToast } = React.useContext(ToastContext);

const [collectionName, setCollectionName] = React.useState<string | null>(null);
const [isCollectionNameInvalid, setIsCollectionNameInvalid] = React.useState<boolean>(false);
const [collectionNameInvalidMsg, setCollectionNameInvalidMsg] = React.useState<string | null>(null);
const [collectionDescription, setCollectionDescription] = React.useState<string | null>(null);
const [isCreatingCollection, setIsCreatingCollection] = React.useState<boolean>(false);

const handleNameOnChange = React.useCallback((value : string) => {
setCollectionName(value);
setIsCollectionNameInvalid(false);
setCollectionNameInvalidMsg(null);
}, []);

const handleOnClose = React.useCallback(() => {
closeCreateCollectionModal();
setCollectionNameInvalidMsg(null);
setCollectionName(null);
setCollectionDescription(null);
setIsCreatingCollection(false);
}, []);

const handleCreate = React.useCallback(() => {
if (collectionName === null || collectionName === '') {
setIsCollectionNameInvalid(true);
setCollectionNameInvalidMsg(
intl.formatMessage(messages.createCollectionModalNameInvalid),
);
return;
}
// TODO call API
setCollectionName(null);
setCollectionDescription(null);
closeCreateCollectionModal();

setIsCreatingCollection(true);

create.mutateAsync({
title: collectionName,
description: collectionDescription || '',
}).then(() => {
handleOnClose();
showToast(intl.formatMessage(messages.createCollectionSuccess));
}).catch((err) => {
setIsCreatingCollection(false);
if (err.customAttributes.httpErrorStatus === 409) {
setCollectionNameInvalidMsg(
intl.formatMessage(messages.createCollectionModalNameConflict),
);
} else {
showToast(intl.formatMessage(messages.createCollectionError));
}
});
}, [collectionName, collectionDescription]);

return (
<ModalDialog
title={intl.formatMessage(messages.createCollectionModalTitle)}
isOpen={isCreateCollectionModalOpen}
onClose={closeCreateCollectionModal}
onClose={handleOnClose}
size="xl"
hasCloseButton
isFullscreenOnMobile
Expand All @@ -62,9 +94,9 @@ const CreateCollectionModal = () => {
value={collectionName}
onChange={(e) => handleNameOnChange(e.target.value)}
/>
{ isCollectionNameInvalid && (
{ collectionNameInvalidMsg && (
<Form.Control.Feedback type="invalid">
{intl.formatMessage(messages.createCollectionModalNameInvalid)}
{collectionNameInvalidMsg}
</Form.Control.Feedback>
)}
</Form.Group>
Expand All @@ -90,7 +122,7 @@ const CreateCollectionModal = () => {
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.createCollectionModalCancel)}
</ModalDialog.CloseButton>
<Button variant="primary" onClick={handleCreate}>
<Button variant="primary" onClick={handleCreate} disabled={isCreatingCollection}>
{intl.formatMessage(messages.createCollectionModalCreate)}
</Button>
</ActionRow>
Expand Down
15 changes: 15 additions & 0 deletions src/library-authoring/create-collection/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const messages = defineMessages({
defaultMessage: 'Collection name is required',
description: 'Mesasge when the Name field of the Create Collection modal form is invalid',
},
createCollectionModalNameConflict: {
id: 'course-authoring.library-authoring.modals.create-collection.form.name.conflict',
defaultMessage: 'There is another collection with the same name',
description: 'Mesasge when the Name field of the Create Collection modal form is not unique',
},
createCollectionModalDescriptionLabel: {
id: 'course-authoring.library-authoring.modals.create-collection.form.description',
defaultMessage: 'Add a description (optional)',
Expand All @@ -50,6 +55,16 @@ const messages = defineMessages({
defaultMessage: 'Descriptions can help you and your team better organize and find what you are looking for',
description: 'Details of the Description field of the Create Collection modal form',
},
createCollectionSuccess: {
id: 'course-authoring.library-authoring.modals.create-collection.success',
defaultMessage: 'Collection created successfully',
description: 'Success message when creating a library collection',
},
createCollectionError: {
id: 'course-authoring.library-authoring.modals.create-collection.error',
defaultMessage: 'There is an error when creating the library collection',
description: 'Error message when creating a library collection',
},
});

export default messages;
26 changes: 23 additions & 3 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ export const getCommitLibraryChangesUrl = (libraryId: string) => `${getApiBaseUr
*/
export const getLibraryPasteClipboardUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/paste_clipboard/`;
/**
* Get the URL for the xblock metadata API.
*/
* Get the URL for the xblock metadata API.
*/
export const getXBlockFieldsApiUrl = (usageKey: string) => `${getApiBaseUrl()}/api/xblock/v2/xblocks/${usageKey}/fields/`;
/**
* Get the URL for the Library Collections API.
*/
export const getLibraryCollectionsApiUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/collections/`;

export interface ContentLibrary {
id: string;
Expand Down Expand Up @@ -127,6 +131,11 @@ export interface UpdateXBlockFieldsRequest {
};
}

export interface CreateLibraryCollectionDataRequest {
title: string;
description: string | null;
}

/**
* Fetch block types of a library
*/
Expand Down Expand Up @@ -240,7 +249,18 @@ export async function getXBlockFields(usageKey: string): Promise<XBlockFields> {
/**
* Update xblock fields.
*/
export async function updateXBlockFields(usageKey:string, xblockData: UpdateXBlockFieldsRequest) {
export async function updateXBlockFields(usageKey: string, xblockData: UpdateXBlockFieldsRequest) {
const client = getAuthenticatedHttpClient();
await client.post(getXBlockFieldsApiUrl(usageKey), xblockData);
}

export async function createCollection(collectionData: CreateLibraryCollectionDataRequest, libraryId?: string) {
if (!libraryId) {
throw new Error('libraryId is required');
}

const client = getAuthenticatedHttpClient();
const { data } = await client.post(getLibraryCollectionsApiUrl(libraryId), collectionData);

return camelCaseObject(data);
}
17 changes: 16 additions & 1 deletion src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
libraryPasteClipboard,
getXBlockFields,
updateXBlockFields,
createCollection,
CreateLibraryCollectionDataRequest,
} from './api';

const libraryQueryPredicate = (query: Query, libraryId: string): boolean => {
const libraryQueryPredicate = (query: Query, libraryId?: string): boolean => {
// Invalidate all content queries related to this library.
// If we allow searching "all courses and libraries" in the future,
// then we'd have to invalidate all `["content_search", "results"]`
Expand Down Expand Up @@ -209,3 +211,16 @@ export const useUpdateXBlockFields = (contentLibraryId: string, usageKey: string
},
});
};

/**
* Use this mutation to create a library collection
*/
export const useCreateLibraryCollection = (libraryId?: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateLibraryCollectionDataRequest) => createCollection(data, libraryId),
onSettled: () => {
queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) });
},
});
};

0 comments on commit ea2ba75

Please sign in to comment.