Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/google-docs/src/hooks/useGoogleDocPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function useGoogleDocsPicker(
await loadPickerApi();

const google = (window as any).google;
const gapi = (window as any).gapi;

// Only show Google Docs
const view = new google.picker.View(google.picker.ViewId.DOCS);
Expand Down
4 changes: 2 additions & 2 deletions apps/google-docs/src/hooks/useProgressTracking.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState, useCallback } from 'react';
import { SelectedContentType } from '../locations/Page/components/modals/step_2/SelectContentTypeModal';
import { ContentTypeProps } from 'contentful-management';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 💪


export const useProgressTracking = () => {
const [documentId, setDocumentId] = useState<string>('');
const [selectedContentTypes, setSelectedContentTypes] = useState<SelectedContentType[]>([]);
const [selectedContentTypes, setSelectedContentTypes] = useState<ContentTypeProps[]>([]);
const [pendingCloseAction, setPendingCloseAction] = useState<(() => void) | null>(null);

const hasProgress = documentId.trim().length > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import { ReviewEntriesModal } from '../modals/step_4/ReviewEntriesModal';
import { ErrorEntriesModal } from '../modals/step_4/ErrorEntriesModal';
import { createEntriesFromPreview, EntryCreationResult } from '../../../../services/entryService';
import SelectDocumentModal from '../modals/step_1/SelectDocumentModal';
import {
SelectedContentType,
ContentTypePickerModal,
} from '../modals/step_2/SelectContentTypeModal';
import { ContentTypePickerModal } from '../modals/step_2/SelectContentTypeModal';
import { PreviewModal } from '../modals/step_3/PreviewModal';
import { ContentTypeProps } from 'contentful-management';

export interface ModalOrchestratorHandle {
startFlow: () => void;
Expand Down Expand Up @@ -109,22 +107,24 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
openModal(ModalType.CONTENT_TYPE_PICKER);
};

const handleContentTypeSelected = async (contentTypes: SelectedContentType[]) => {
const ids = contentTypes.map((ct) => ct.id);
const handleContentTypeSelected = async (contentTypes: ContentTypeProps[]) => {
const ids = contentTypes.map((ct) => ct.sys.id);
await submit(ids);
};

const handlePreviewModalConfirm = async (contentTypes: SelectedContentType[]) => {
const handlePreviewModalConfirm = async (contentTypeIds: string[]) => {
if (!previewEntries || previewEntries.length === 0) {
sdk.notifier.error('No entries to create');
return;
}

setIsCreatingEntries(true);
try {
const ids = contentTypes.map((ct) => ct.id);
const entries = previewEntries.map((p) => p.entry);
const entryResult: EntryCreationResult = await createEntriesFromPreview(sdk, entries, ids);
const entryResult: EntryCreationResult = await createEntriesFromPreview(
sdk,
entries,
contentTypeIds
);

const createdCount = entryResult.createdEntries.length;

Expand Down Expand Up @@ -203,7 +203,9 @@ export const ModalOrchestrator = forwardRef<ModalOrchestratorHandle, ModalOrches
isOpen={modalStates.isPreviewModalOpen}
onClose={() => closeModal(ModalType.PREVIEW)}
previewEntries={previewEntries}
onCreateEntries={() => handlePreviewModalConfirm(selectedContentTypes)}
onCreateEntries={() =>
handlePreviewModalConfirm(selectedContentTypes.map((ct) => ct.sys.id))
}
isLoading={isSubmitting}
isCreatingEntries={isCreatingEntries}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,22 @@ import {
Modal,
Paragraph,
Pill,
Select,
Multiselect,
Spinner,
} from '@contentful/f36-components';
import { PageAppSDK } from '@contentful/app-sdk';
import { ContentTypeProps } from 'contentful-management';

export interface SelectedContentType {
id: string;
name: string;
}
import { css } from '@emotion/css';

interface ContentTypePickerModalProps {
sdk: PageAppSDK;
isOpen: boolean;
onClose: () => void;
onSelect: (contentTypes: SelectedContentType[]) => void;
onSelect: (contentTypes: ContentTypeProps[]) => void;
isSubmitting: boolean;
selectedContentTypes: SelectedContentType[];
selectedContentTypes: ContentTypeProps[];
setSelectedContentTypes: (
contentTypes: SelectedContentType[] | ((prev: SelectedContentType[]) => SelectedContentType[])
contentTypes: ContentTypeProps[] | ((prev: ContentTypeProps[]) => ContentTypeProps[])
) => void;
}

Expand All @@ -39,6 +35,7 @@ export const ContentTypePickerModal = ({
setSelectedContentTypes,
}: ContentTypePickerModalProps) => {
const [contentTypes, setContentTypes] = useState<ContentTypeProps[]>([]);
const [filteredContentTypes, setFilteredContentTypes] = useState<ContentTypeProps[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState<boolean>(false);
const [hasFetchError, setHasFetchError] = useState<boolean>(false);
Expand Down Expand Up @@ -68,6 +65,7 @@ export const ContentTypePickerModal = ({
environmentId: environment.sys.id,
});
setContentTypes(contentTypesResponse.items || []);
setFilteredContentTypes(contentTypesResponse.items || []);
} catch (error) {
console.error('Failed to fetch content types:', error);
setHasFetchError(true);
Expand All @@ -87,23 +85,35 @@ export const ContentTypePickerModal = ({
}
}, [isOpen]);

const handleAddContentType = (contentTypeId: string) => {
if (!contentTypeId || isSubmitting) return;
const onSearchValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const searchTerm = e.target.value.toLowerCase().trim();
if (!searchTerm) {
setFilteredContentTypes(contentTypes);
return;
}
const filtered = contentTypes.filter((ct) => ct.name.toLowerCase().includes(searchTerm));
setFilteredContentTypes(filtered);
};

const contentType = contentTypes.find((ct) => ct.sys.id === contentTypeId);
if (contentType && !selectedContentTypes.some((ct) => ct.id === contentTypeId)) {
const getPlaceholderText = () => {
if (isLoading) return 'Loading content types...';
if (contentTypes.length === 0) return 'No content types in space';
if (selectedContentTypes.length === 0) return 'Select one or more';
return `${selectedContentTypes.length} selected`;
};

const handleSelectContentType = (e: React.ChangeEvent<HTMLInputElement>) => {
const { checked, value } = e.target;
if (checked) {
setSelectedContentTypes([
...selectedContentTypes,
{ id: contentType.sys.id, name: contentType.name },
contentTypes.find((ct) => ct.sys.id === value) as ContentTypeProps,
]);
} else {
setSelectedContentTypes(selectedContentTypes.filter((selected) => selected.sys.id !== value));
}
};

const handleRemoveContentType = (contentTypeId: string) => {
if (isSubmitting) return;
setSelectedContentTypes(selectedContentTypes.filter((ct) => ct.id !== contentTypeId));
};

const handleClose = () => {
if (isSubmitting) return; // Prevent closing during submission
onClose();
Expand All @@ -118,14 +128,6 @@ export const ContentTypePickerModal = ({
onSelect(selectedContentTypes);
};

const availableContentTypes = useMemo(
() =>
contentTypes.filter(
(ct) => !selectedContentTypes.some((selected) => selected.id === ct.sys.id)
),
[contentTypes, selectedContentTypes]
);

return (
<Modal title="Select content type(s)" isShown={isOpen} onClose={handleClose} size="medium">
{() => (
Expand All @@ -137,23 +139,28 @@ export const ContentTypePickerModal = ({
</Paragraph>
<FormControl isRequired isInvalid={isInvalidSelectionError || showFetchError}>
<FormControl.Label>Content type</FormControl.Label>
<Select
id="content-type-select"
name="content-type-select"
value=""
onChange={(e) => {
handleAddContentType(e.target.value);
<Multiselect
searchProps={{
searchPlaceholder: 'Search content types',
onSearchValueChange,
}}
isDisabled={isLoading || availableContentTypes.length === 0 || isSubmitting}>
<Select.Option value="" isDisabled>
{isLoading ? 'Loading content types...' : 'Select one or more'}
</Select.Option>
{availableContentTypes.map((ct) => (
<Select.Option key={ct.sys.id} value={ct.sys.id}>
currentSelection={selectedContentTypes.map((ct) => ct.sys.id)}
placeholder={getPlaceholderText()}>
{filteredContentTypes.map((ct) => (
<Multiselect.Option
className={css({ padding: `0.25rem` })}
key={ct.sys.id}
value={ct.sys.id}
itemId={ct.sys.id}
isChecked={selectedContentTypes.some(
(selected) => selected.sys.id === ct.sys.id
)}
isDisabled={isLoading || isSubmitting}
onSelectItem={handleSelectContentType}>
{ct.name}
</Select.Option>
</Multiselect.Option>
))}
</Select>
</Multiselect>
{showFetchError && (
<FormControl.ValidationMessage>
Unable to load content types.
Expand All @@ -170,9 +177,18 @@ export const ContentTypePickerModal = ({
<Flex flexWrap="wrap" gap="spacingXs" marginTop="spacingS">
{selectedContentTypes.map((ct) => (
<Pill
key={ct.id}
key={ct.sys.id}
label={ct.name}
onClose={isSubmitting ? undefined : () => handleRemoveContentType(ct.id)}
onClose={
isSubmitting
? undefined
: () =>
setSelectedContentTypes(
selectedContentTypes.filter(
(selected) => selected.sys.id !== ct.sys.id
)
)
}
/>
))}
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { Box, Button, Flex, Modal, Paragraph, Text } from '@contentful/f36-components';
import { EntryToCreate } from '../../../../../../functions/agents/documentParserAgent/schema';
import { SelectedContentType } from '../step_2/SelectContentTypeModal';
import tokens from '@contentful/f36-tokens';

export interface PreviewEntry {
Expand All @@ -13,7 +12,7 @@ interface PreviewModalProps {
isOpen: boolean;
onClose: () => void;
previewEntries: PreviewEntry[];
onCreateEntries: (contentTypes: SelectedContentType[]) => void;
onCreateEntries: (contentTypeIds: string[]) => void;
isCreatingEntries: boolean;
isLoading: boolean;
}
Expand Down Expand Up @@ -87,11 +86,7 @@ export const PreviewModal: React.FC<PreviewModalProps> = ({
</Button>
<Button
onClick={() =>
onCreateEntries(
previewEntries.map((item) => ({
id: item.entry.contentTypeId,
})) as SelectedContentType[]
)
onCreateEntries(previewEntries.map((preview) => preview.entry.contentTypeId))
}
variant="primary"
isDisabled={isLoading || previewEntries.length === 0}>
Expand Down