From bb8fd2e0c52f735ecfdba8c86a4f552b87a4a463 Mon Sep 17 00:00:00 2001 From: runarvestmann Date: Tue, 15 Oct 2024 16:16:45 +0000 Subject: [PATCH 1/3] Allow two subpages to have same slug --- .../fields/project-subpage-slug-field.tsx | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx diff --git a/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx new file mode 100644 index 000000000000..38e7f86e9d70 --- /dev/null +++ b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx @@ -0,0 +1,151 @@ +import { useEffect, useRef, useState } from 'react' +import { useDebounce } from 'react-use' +import type { EntryProps } from 'contentful-management' +import { FieldExtensionSDK } from '@contentful/app-sdk' +import { Stack, Text, TextInput } from '@contentful/f36-components' +import { useCMA, useSDK } from '@contentful/react-apps-toolkit' +import slugify from '@sindresorhus/slugify' + +const DEBOUNCE_TIME = 100 + +const OrganizationSubpageSlugField = () => { + const sdk = useSDK() + const cma = useCMA() + const [value, setValue] = useState(sdk.field?.getValue() ?? '') + const [isValid, setIsValid] = useState(true) + const [projectPage, setProjectPage] = useState(null) + const initialTitleChange = useRef(true) + const [hasEntryBeenPublished, setHasEntryBeenPublished] = useState( + Boolean(sdk.entry.getSys()?.firstPublishedAt), + ) + + const defaultLocale = sdk.locales.default + + useEffect(() => { + const fetchProjectPage = async () => { + const response = await cma.entry.getMany({ + query: { + links_to_entry: sdk.entry.getSys().id, + content_type: 'projectPage', + 'sys.archivedVersion[exists]': false, + limit: 1, + }, + }) + if (response.items.length > 0) { + setProjectPage(response.items[0]) + } + } + fetchProjectPage() + }, [cma.entry, sdk.entry]) + + useEffect(() => { + sdk.entry.onSysChanged((newSys) => { + setHasEntryBeenPublished(Boolean(newSys?.firstPublishedAt)) + }) + }, [sdk.entry]) + + // Update slug field if the title field changes + useEffect(() => { + return sdk.entry.fields.title + .getForLocale(sdk.field.locale) + .onValueChanged((newTitle) => { + if (hasEntryBeenPublished) { + return + } + + // Callback gets called on initial render, so we want to ignore that + if (initialTitleChange.current) { + initialTitleChange.current = false + return + } + + if (newTitle) { + setValue(slugify(String(newTitle))) + } + }) + }, [hasEntryBeenPublished, sdk.entry.fields.title, sdk.field.locale]) + + useEffect(() => { + sdk.window.startAutoResizer() + }, [sdk.window]) + + // Validate the user input + useDebounce( + async () => { + if (!projectPage) { + setIsValid(true) + return + } + + console.log(projectPage) + + const subpageIds: string[] = + projectPage?.fields?.projectSubpages?.[defaultLocale]?.map( + (subpage) => subpage.sys.id, + ) ?? [] + + const subpagesWithSameSlug = ( + await cma.entry.getMany({ + query: { + locale: sdk.field.locale, + content_type: 'projectSubpage', + 'fields.slug': value, + 'sys.id[ne]': sdk.entry.getSys().id, + 'sys.archivedVersion[exists]': false, + limit: 1000, + select: 'sys', + }, + }) + ).items + + const subpageExistsWithSameSlug = subpagesWithSameSlug.some((subpage) => + subpageIds.includes(subpage.sys.id), + ) + + if (subpageExistsWithSameSlug) { + setIsValid(false) + return + } + setIsValid(true) + }, + DEBOUNCE_TIME, + [value, projectPage], + ) + + useDebounce( + () => { + if (isValid) { + sdk.field.setValue(value) + } else { + sdk.field.setValue(null) // Set to null to prevent entry publish + } + sdk.field.setInvalid(!isValid) + }, + DEBOUNCE_TIME, + [isValid, value], + ) + + const isInvalid = value.length === 0 || !isValid + + return ( + + { + setValue(ev.target.value) + }} + isInvalid={isInvalid} + /> + {value.length === 0 && sdk.field.locale === defaultLocale && ( + Invalid slug + )} + {value.length > 0 && isInvalid && ( + + Project subpage already exists with this slug + + )} + + ) +} + +export default OrganizationSubpageSlugField From 7ae94807a4ade387eeec6e4e6889b9025912694a Mon Sep 17 00:00:00 2001 From: runarvestmann Date: Tue, 15 Oct 2024 16:39:43 +0000 Subject: [PATCH 2/3] Change component name --- .../pages/fields/project-subpage-slug-field.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx index 38e7f86e9d70..17ca287ece8d 100644 --- a/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx +++ b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx @@ -8,7 +8,7 @@ import slugify from '@sindresorhus/slugify' const DEBOUNCE_TIME = 100 -const OrganizationSubpageSlugField = () => { +const ProjectSubpageSlugField = () => { const sdk = useSDK() const cma = useCMA() const [value, setValue] = useState(sdk.field?.getValue() ?? '') @@ -148,4 +148,4 @@ const OrganizationSubpageSlugField = () => { ) } -export default OrganizationSubpageSlugField +export default ProjectSubpageSlugField From bee11ea2d4b310130a38c03d3982a44b044c942c Mon Sep 17 00:00:00 2001 From: runarvestmann Date: Wed, 16 Oct 2024 09:21:07 +0000 Subject: [PATCH 3/3] Remove console.log --- .../contentful-apps/pages/fields/project-subpage-slug-field.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx index 17ca287ece8d..fc914059d0cd 100644 --- a/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx +++ b/apps/contentful-apps/pages/fields/project-subpage-slug-field.tsx @@ -77,8 +77,6 @@ const ProjectSubpageSlugField = () => { return } - console.log(projectPage) - const subpageIds: string[] = projectPage?.fields?.projectSubpages?.[defaultLocale]?.map( (subpage) => subpage.sys.id,