diff --git a/frontend/packages/next-drupal/src/deprecated.ts b/frontend/packages/next-drupal/src/deprecated.ts index 8693c51..870945c 100644 --- a/frontend/packages/next-drupal/src/deprecated.ts +++ b/frontend/packages/next-drupal/src/deprecated.ts @@ -1,4 +1,3 @@ -export * from "./deprecated/get-access-token" export * from "./deprecated/get-menu" export * from "./deprecated/get-paths" export * from "./deprecated/get-resource-collection" diff --git a/frontend/packages/next-drupal/src/deprecated/get-access-token.ts b/frontend/packages/next-drupal/src/deprecated/get-access-token.ts deleted file mode 100644 index 017a666..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-access-token.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { cache } from "./get-cache" -import type { AccessToken } from "../types" - -const CACHE_KEY = "NEXT_DRUPAL_ACCESS_TOKEN" - -export async function getAccessToken(): Promise { - if (!process.env.DRUPAL_CLIENT_ID || !process.env.DRUPAL_CLIENT_SECRET) { - return null - } - - const cached = cache.get(CACHE_KEY) - if (cached?.access_token) { - return cached - } - - const basic = Buffer.from( - `${process.env.DRUPAL_CLIENT_ID}:${process.env.DRUPAL_CLIENT_SECRET}` - ).toString("base64") - - const response = await fetch( - `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/oauth/token`, - { - method: "POST", - headers: { - Authorization: `Basic ${basic}`, - "Content-Type": "application/x-www-form-urlencoded", - }, - body: `grant_type=client_credentials`, - } - ) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const result: AccessToken = await response.json() - - cache.set(CACHE_KEY, result, result.expires_in) - - return result -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-cache.ts b/frontend/packages/next-drupal/src/deprecated/get-cache.ts deleted file mode 100644 index efc68fb..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-cache.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NodeCache from "node-cache" - -export const cache = new NodeCache() diff --git a/frontend/packages/next-drupal/src/deprecated/get-menu.ts b/frontend/packages/next-drupal/src/deprecated/get-menu.ts deleted file mode 100644 index 1726296..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-menu.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { buildHeaders, buildUrl, deserialize } from "./utils" -import type { AccessToken, DrupalMenuLinkContent } from "../types" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -export async function getMenu( - name: string, - options?: { - deserialize?: boolean - accessToken?: AccessToken - } & JsonApiWithLocaleOptions -): Promise<{ - items: T[] - tree: T[] -}> { - options = { - deserialize: true, - ...options, - } - - const localePrefix = - options?.locale && options.locale !== options.defaultLocale - ? `/${options.locale}` - : "" - - const url = buildUrl(`${localePrefix}/jsonapi/menu_items/${name}`) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const data = await response.json() - - const items = options.deserialize ? deserialize(data) : data - - const { items: tree } = buildMenuTree(items) - - return { - items, - tree, - } -} - -function buildMenuTree( - links: DrupalMenuLinkContent[], - parent: DrupalMenuLinkContent["id"] = "" -) { - if (!links?.length) { - return { - items: [], - } - } - - const children = links.filter((link) => link.parent === parent) - - return children.length - ? { - items: children.map((link) => ({ - ...link, - ...buildMenuTree(links, link.id), - })), - } - : {} -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-paths.ts b/frontend/packages/next-drupal/src/deprecated/get-paths.ts deleted file mode 100644 index a2bbfe0..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-paths.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { getResourceCollection } from "./get-resource-collection" -import type { GetStaticPathsContext, GetStaticPathsResult } from "next" -import type { AccessToken, JsonApiParams, Locale } from "../types" - -export async function getPathsFromContext( - types: string | string[], - context: GetStaticPathsContext, - options: { - params?: JsonApiParams - accessToken?: AccessToken - } = {} -): Promise { - if (typeof types === "string") { - types = [types] - } - - const paths = await Promise.all( - types.map(async (type) => { - // Use sparse fieldset to expand max size. - options.params = { - [`fields[${type}]`]: "path", - ...options?.params, - } - - // const paths = await Promise.all( - // context.locales.map(async (locale) => { - // const resources = await getResourceCollection(type, { - // deserialize: true, - // locale, - // defaultLocale: context.defaultLocale, - // ...options, - // }) - - // return buildPathsFromResources(resources, locale) - // }) - // ) - - // return paths.flat() - - // Handle localized path aliases - if (!context.locales?.length) { - const resources = await getResourceCollection(type, { - deserialize: true, - ...options, - }) - - return buildPathsFromResources(resources) - } - - const paths = await Promise.all( - context.locales.map(async (locale) => { - const resources = await getResourceCollection(type, { - deserialize: true, - locale, - defaultLocale: context.defaultLocale, - ...options, - }) - - return buildPathsFromResources(resources, locale) - }) - ) - - return paths.flat() - }) - ) - - return paths.flat() -} - -function buildPathsFromResources(resources, locale?: Locale) { - return resources?.flatMap((resource) => { - const slug = - resource?.path?.alias === process.env.DRUPAL_FRONT_PAGE - ? "/" - : resource?.path?.alias - - const path = { - params: { - slug: `${slug?.replace(/^\/|\/$/g, "")}`.split("/"), - }, - } - - if (locale) { - path["locale"] = locale - } - - return path - }) -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-resource-collection.ts b/frontend/packages/next-drupal/src/deprecated/get-resource-collection.ts deleted file mode 100644 index 209cd3d..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-resource-collection.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - buildHeaders, - buildUrl, - deserialize, - getJsonApiPathForResourceType, -} from "./utils" -import type { GetStaticPropsContext } from "next" -import type { AccessToken, JsonApiParams, JsonApiResource } from "../types" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -export async function getResourceCollection( - type: string, - options?: { - deserialize?: boolean - accessToken?: AccessToken - } & JsonApiWithLocaleOptions -): Promise { - options = { - deserialize: true, - ...options, - } - - const apiPath = await getJsonApiPathForResourceType( - type, - options?.locale !== options?.defaultLocale ? options.locale : undefined - ) - - if (!apiPath) { - throw new Error(`Error: resource of type ${type} not found.`) - } - - const url = buildUrl(apiPath, { - ...options?.params, - }) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const json = await response.json() - - return options.deserialize ? deserialize(json) : json -} - -export async function getResourceCollectionFromContext( - type: string, - context: GetStaticPropsContext, - options?: { - deserialize?: boolean - params?: JsonApiParams - } -): Promise { - options = { - deserialize: true, - ...options, - } - - // // Filter out unpublished entities. - // if (!context.preview) { - // options.params = { - // "filter[status]": "1", - // ...options.params, - // } - // } - - return await getResourceCollection(type, { - ...options, - locale: context.locale, - defaultLocale: context.defaultLocale, - }) -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-resource-type.ts b/frontend/packages/next-drupal/src/deprecated/get-resource-type.ts deleted file mode 100644 index 85f5204..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-resource-type.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { translatePathFromContext } from "./translate-path" -import type { GetStaticPropsContext } from "next" -import type { AccessToken } from "../types" - -export async function getResourceTypeFromContext( - context: GetStaticPropsContext, - options?: { - accessToken?: AccessToken - prefix?: string - } -): Promise { - try { - const response = await translatePathFromContext(context, options) - - return response.jsonapi.resourceName - } catch (error) { - return null - } -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-resource.ts b/frontend/packages/next-drupal/src/deprecated/get-resource.ts deleted file mode 100644 index 7da11f9..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-resource.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { stringify } from "qs" -import { - buildHeaders, - buildUrl, - deserialize, - getJsonApiPathForResourceType, - getPathFromContext, -} from "./utils" -import type { GetStaticPropsContext } from "next" -import type { AccessToken, JsonApiParams, JsonApiResource } from "../types" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -export async function getResourceFromContext( - type: string, - context: GetStaticPropsContext, - options?: { - prefix?: string - deserialize?: boolean - params?: JsonApiParams - accessToken?: AccessToken - isVersionable?: boolean - } -): Promise { - options = { - deserialize: true, - // Add support for revisions for node by default. - // TODO: Make this required before stable? - isVersionable: /^node--/.test(type), - ...options, - } - - const path = getPathFromContext(context, options?.prefix) - - // Filter out unpublished entities. - // if (!context.preview) { - // options.params = { - // "filter[status]": "1", - // ...options?.params, - // } - // } - - const previewData = context.previewData as { resourceVersion?: string } - - const resource = await getResourceByPath(path, { - deserialize: options.deserialize, - isVersionable: options.isVersionable, - locale: context.locale, - defaultLocale: context.defaultLocale, - params: { - resourceVersion: previewData?.resourceVersion, - ...options?.params, - }, - }) - - // If no locale is passed, skip entity if not default_langcode. - // This happens because decoupled_router will still translate the path - // to a resource. - // TODO: Figure out if we want this behavior. - // For now this causes a bug where a non-i18n sites builds (ISR) pages for - // localized pages. - // if (!context.locale && !resource?.default_langcode) { - // return null - // } - - return resource -} - -export async function getResourceByPath( - path: string, - options?: { - accessToken?: AccessToken - deserialize?: boolean - isVersionable?: boolean - } & JsonApiWithLocaleOptions -): Promise { - options = { - deserialize: true, - isVersionable: false, - params: {}, - ...options, - } - - if (!path) { - return null - } - - if ( - options.locale && - options.defaultLocale && - path.indexOf(options.locale) !== 1 - ) { - path = path === "/" ? path : path.replace(/^\/+/, "") - path = getPathFromContext({ - params: { slug: [path] }, - locale: options.locale, - defaultLocale: options.defaultLocale, - }) - } - - const { resourceVersion = "rel:latest-version", ...params } = options.params - - if (options.isVersionable) { - params.resourceVersion = resourceVersion - } - - const resourceParams = stringify(params) - - const payload = [ - { - requestId: "router", - action: "view", - uri: `/router/translate-path?path=${path}&_format=json`, - headers: { Accept: "application/vnd.api+json" }, - }, - { - requestId: "resolvedResource", - action: "view", - uri: `{{router.body@$.jsonapi.individual}}?${resourceParams.toString()}`, - waitFor: ["router"], - }, - ] - - // Localized subrequests. - // I was hoping we would not need this but it seems like subrequests is not properly - // setting the jsonapi locale from a translated path. - let subrequestsPath = "/subrequests" - if ( - options.locale && - options.defaultLocale && - options.locale !== options.defaultLocale - ) { - subrequestsPath = `/${options.locale}/subrequests` - } - - const url = buildUrl(subrequestsPath, { - _format: "json", - }) - - const response = await fetch(url.toString(), { - method: "POST", - credentials: "include", - headers: await buildHeaders(options), - redirect: "follow", - body: JSON.stringify(payload), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const json = await response.json() - - if (!json["resolvedResource#uri{0}"]) { - return null - } - - const data = JSON.parse(json["resolvedResource#uri{0}"]?.body) - - if (data.errors) { - throw new Error(data.errors[0].detail) - } - - return options.deserialize ? deserialize(data) : data -} - -export async function getResource( - type: string, - uuid: string, - options?: { - accessToken?: AccessToken - deserialize?: boolean - } & JsonApiWithLocaleOptions -): Promise { - options = { - deserialize: true, - params: {}, - ...options, - } - - const apiPath = await getJsonApiPathForResourceType( - type, - options?.locale !== options?.defaultLocale ? options.locale : undefined - ) - - if (!apiPath) { - throw new Error(`Error: resource of type ${type} not found.`) - } - - const url = buildUrl(`${apiPath}/${uuid}`, { - ...options?.params, - }) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const json = await response.json() - - return options.deserialize ? deserialize(json) : json -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-search-index.ts b/frontend/packages/next-drupal/src/deprecated/get-search-index.ts deleted file mode 100644 index f97d2fa..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-search-index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { buildHeaders, buildUrl, deserialize } from "./utils" -import type { GetStaticPropsContext } from "next" -import type { AccessToken, JsonApiResource } from "../types" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -export async function getSearchIndex( - name: string, - options?: { - deserialize?: boolean - accessToken?: AccessToken - } & JsonApiWithLocaleOptions -): Promise { - options = { - deserialize: true, - ...options, - } - - const localePrefix = - options?.locale && options.locale !== options.defaultLocale - ? `/${options.locale}` - : "" - - const url = buildUrl(`${localePrefix}/jsonapi/index/${name}`, options.params) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const json = await response.json() - - return options.deserialize ? deserialize(json) : json -} - -export async function getSearchIndexFromContext( - name: string, - context: GetStaticPropsContext, - options?: { - deserialize?: boolean - accessToken?: AccessToken - } & JsonApiWithLocaleOptions -): Promise { - options = { - deserialize: true, - ...options, - } - - return await getSearchIndex(name, { - ...options, - locale: context.locale, - defaultLocale: context.defaultLocale, - }) -} diff --git a/frontend/packages/next-drupal/src/deprecated/get-view.ts b/frontend/packages/next-drupal/src/deprecated/get-view.ts deleted file mode 100644 index 2b7cb0c..0000000 --- a/frontend/packages/next-drupal/src/deprecated/get-view.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { buildHeaders, buildUrl, deserialize } from "./utils" -import type { AccessToken } from "../types" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -export async function getView( - name: string, - options?: { - deserialize?: boolean - accessToken?: AccessToken - } & JsonApiWithLocaleOptions -): Promise<{ - results: T - /* eslint-disable @typescript-eslint/no-explicit-any */ - meta: Record - links: { - [key in "next" | "prev" | "self"]?: { - href: "string" - } - } -}> { - options = { - deserialize: true, - ...options, - } - - const localePrefix = - options?.locale && options.locale !== options.defaultLocale - ? `/${options.locale}` - : "" - - const [viewId, displayId] = name.split("--") - - const url = buildUrl( - `${localePrefix}/jsonapi/views/${viewId}/${displayId}`, - options.params - ) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - const data = await response.json() - - const results = options.deserialize ? deserialize(data) : data - - return { - results, - meta: data.meta, - links: data.links, - } -} diff --git a/frontend/packages/next-drupal/src/deprecated/preview.ts b/frontend/packages/next-drupal/src/deprecated/preview.ts deleted file mode 100644 index 7fb192d..0000000 --- a/frontend/packages/next-drupal/src/deprecated/preview.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { getResourceByPath } from "./get-resource" -import type { NextApiRequest, NextApiResponse } from "next" -import type { JsonApiWithLocaleOptions } from "../types/deprecated" - -interface PreviewOptions { - errorMessages?: { - secret?: string - slug?: string - } -} - -export function DrupalPreview(options?: PreviewOptions) { - return (request, response) => PreviewHandler(request, response, options) -} - -export async function PreviewHandler( - request?: NextApiRequest, - response?: NextApiResponse, - options?: PreviewOptions -) { - const { path, resourceVersion, secret, locale, defaultLocale } = request.query - - if (secret !== process.env.DRUPAL_PREVIEW_SECRET) { - return response.status(401).json({ - message: options?.errorMessages.secret || "Invalid preview secret.", - }) - } - - if (!path) { - return response - .status(401) - .end({ message: options?.errorMessages.slug || "Invalid slug." }) - } - - let _options: GetResourcePreviewUrlOptions = { - isVersionable: typeof resourceVersion !== "undefined", - } - if (locale && defaultLocale) { - _options = { - ..._options, - locale: locale as string, - defaultLocale: defaultLocale as string, - } - } - - const url = await getResourcePreviewUrl(path as string, _options) - - if (!url) { - response - .status(404) - .end({ message: options?.errorMessages.slug || "Invalid slug" }) - } - - response.setPreviewData({ - resourceVersion, - }) - - response.writeHead(307, { Location: url }) - - return response.end() -} - -type GetResourcePreviewUrlOptions = JsonApiWithLocaleOptions & { - isVersionable?: boolean -} - -export async function getResourcePreviewUrl( - slug: string, - options?: GetResourcePreviewUrlOptions -) { - const entity = await getResourceByPath(slug, options) - - if (!entity) { - return null - } - - if (!entity?.path) { - throw new Error( - `Error: the path attribute is missing for entity type ${entity.type}` - ) - } - - return entity?.default_langcode - ? entity.path.alias - : `/${entity.path.langcode}${entity.path.alias}` -} diff --git a/frontend/packages/next-drupal/src/deprecated/translate-path.ts b/frontend/packages/next-drupal/src/deprecated/translate-path.ts deleted file mode 100644 index 0651957..0000000 --- a/frontend/packages/next-drupal/src/deprecated/translate-path.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { buildHeaders, buildUrl, getPathFromContext } from "./utils" -import type { GetStaticPropsContext } from "next" -import type { AccessToken, DrupalTranslatedPath } from "../types" - -export async function translatePath( - path: string, - options?: { - accessToken?: AccessToken - } -): Promise { - const url = buildUrl("/router/translate-path", { - path, - }) - - const response = await fetch(url.toString(), { - headers: await buildHeaders(options), - }) - - if (!response.ok) { - return null - } - - const json = await response.json() - - return json -} - -export async function translatePathFromContext( - context: GetStaticPropsContext, - options?: { - accessToken?: AccessToken - prefix?: string - } -): Promise { - options = { - prefix: "", - ...options, - } - const path = getPathFromContext(context, options.prefix) - - const response = await translatePath(path, { - accessToken: options.accessToken, - }) - - return response -} diff --git a/frontend/packages/next-drupal/src/deprecated/use-menu.ts b/frontend/packages/next-drupal/src/deprecated/use-menu.ts deleted file mode 100644 index 25c82c2..0000000 --- a/frontend/packages/next-drupal/src/deprecated/use-menu.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useRouter } from "next/router" -import { useEffect, useState } from "react" -import { getMenu } from "./get-menu" -import type { DrupalMenuLinkContent } from "../types" - -export function useMenu( - name: string -): { - items: T[] - tree: T[] - error: unknown - isLoading: boolean -} { - const router = useRouter() - const [data, setData] = useState<{ - items: T[] - tree: T[] - }>(null) - const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(false) - - useEffect(() => { - const fetchMenuItems = async () => { - setIsLoading(true) - try { - const data = await getMenu(name, { - locale: router.locale, - defaultLocale: router.defaultLocale, - }) - setData(data) - setIsLoading(false) - } catch (error) { - setError(error) - setIsLoading(false) - } - } - fetchMenuItems() - }, [router.locale]) - - return { ...data, error, isLoading } -} diff --git a/frontend/packages/next-drupal/src/deprecated/utils.ts b/frontend/packages/next-drupal/src/deprecated/utils.ts deleted file mode 100644 index b68d951..0000000 --- a/frontend/packages/next-drupal/src/deprecated/utils.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Jsona } from "jsona" -import { stringify } from "qs" -import { getAccessToken } from "./get-access-token" -import type { GetStaticPropsContext } from "next" -import type { AccessToken, Locale } from "../types" - -const JSONAPI_PREFIX = process.env.DRUPAL_JSONAPI_PREFIX || "/jsonapi" - -const dataFormatter = new Jsona() - -export function deserialize(body, options?) { - if (!body) return null - - return dataFormatter.deserialize(body, options) -} - -export async function getJsonApiPathForResourceType( - type: string, - locale?: Locale -) { - const index = await getJsonApiIndex(locale) - - return index?.links[type]?.href -} - -export async function getJsonApiIndex( - locale?: Locale, - options?: { - accessToken?: AccessToken - } -): Promise<{ - links: { - [type: string]: { - href: string - } - } -}> { - const url = buildUrl( - locale ? `/${locale}${JSONAPI_PREFIX}` : `${JSONAPI_PREFIX}` - ) - - // As per https://www.drupal.org/node/2984034 /jsonapi is public. - // We only call buildHeaders if accessToken or locale is explicitly set. - // This is for rare cases where /jsonapi might be protected. - const response = await fetch(url.toString(), { - headers: - locale || options - ? await buildHeaders(options) - : { - "Content-Type": "application/json", - }, - }) - - if (!response.ok) { - throw new Error(response.statusText) - } - - return await response.json() -} - -export function buildUrl( - path: string, - params?: string | Record | URLSearchParams -): URL { - const url = new URL( - path.charAt(0) === "/" - ? `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${path}` - : path - ) - - if (params) { - // Use instead URLSearchParams for nested params. - url.search = stringify(params) - } - - return url -} - -export async function buildHeaders({ - accessToken, - headers = { - "Content-Type": "application/json", - }, -}: { - accessToken?: AccessToken - headers?: RequestInit["headers"] -} = {}): Promise { - // This allows an access_token (preferrably long-lived) to be set directly on the env. - // This reduces the number of OAuth call to the Drupal server. - // Intentionally marked as unstable for now. - if (process.env.UNSTABLE_DRUPAL_ACCESS_TOKEN) { - headers[ - "Authorization" - ] = `Bearer ${process.env.UNSTABLE_DRUPAL_ACCESS_TOKEN}` - - return headers - } - - const token = accessToken || (await getAccessToken()) - if (token) { - headers["Authorization"] = `Bearer ${token.access_token}` - } - - return headers -} - -export function getPathFromContext( - context: GetStaticPropsContext, - prefix = "" -) { - let { slug } = context.params - - slug = Array.isArray(slug) - ? slug.map((s) => encodeURIComponent(s)).join("/") - : slug - - // Handle locale. - if (context.locale && context.locale !== context.defaultLocale) { - slug = `/${context.locale}/${slug}` - } - - return !slug - ? process.env.DRUPAL_FRONT_PAGE - : prefix - ? `${prefix}/${slug}` - : slug -} - -export function syncDrupalPreviewRoutes(path) { - if (window && window.top !== window.self) { - window.parent.postMessage( - { type: "NEXT_DRUPAL_ROUTE_SYNC", path }, - process.env.NEXT_PUBLIC_DRUPAL_BASE_URL - ) - } -} diff --git a/frontend/packages/next-drupal/src/drupal-client.ts b/frontend/packages/next-drupal/src/drupal-client.ts index 418f521..e7dcb8a 100644 --- a/frontend/packages/next-drupal/src/drupal-client.ts +++ b/frontend/packages/next-drupal/src/drupal-client.ts @@ -45,7 +45,7 @@ export class DrupalClient extends NextDrupal { this.deserializer = ( body: Parameters[0], options: Parameters[1] - ) => this.serializer.deserialize(body, options) + ) => this.serializer!.deserialize(body, options) } async getResourceFromContext( @@ -56,7 +56,7 @@ export class DrupalClient extends NextDrupal { isVersionable?: boolean } & JsonApiOptions ): Promise { - const type = typeof input === "string" ? input : input.jsonapi.resourceName + const type = typeof input === "string" ? input : input.jsonapi!.resourceName const previewData = context.previewData as { resourceVersion?: string diff --git a/frontend/packages/next-drupal/src/index.ts b/frontend/packages/next-drupal/src/index.ts index 97f3ea4..6158d02 100644 --- a/frontend/packages/next-drupal/src/index.ts +++ b/frontend/packages/next-drupal/src/index.ts @@ -5,5 +5,3 @@ export * from "./jsonapi-errors" export * from "./next-drupal" export type * from "./types" - -export * from "./deprecated" diff --git a/frontend/packages/next-drupal/src/next-drupal.ts b/frontend/packages/next-drupal/src/next-drupal.ts index ca37873..03b224c 100644 --- a/frontend/packages/next-drupal/src/next-drupal.ts +++ b/frontend/packages/next-drupal/src/next-drupal.ts @@ -93,7 +93,6 @@ export class NextDrupal extends NextDrupalFetch { ...options, credentials: "same-origin", } - const apiPath = await this.getEntryForResourceType( type, options?.locale !== options?.defaultLocale diff --git a/frontend/packages/next-drupal/src/types/options.ts b/frontend/packages/next-drupal/src/types/options.ts index 972bd7c..edf16d5 100644 --- a/frontend/packages/next-drupal/src/types/options.ts +++ b/frontend/packages/next-drupal/src/types/options.ts @@ -14,6 +14,7 @@ export interface FetchOptions extends RequestInit { export type JsonApiOptions = { deserialize?: boolean params?: JsonApiParams + credentials?: string } & JsonApiWithAuthOption & ( | { diff --git a/frontend/packages/next-drupal/src/types/resource.ts b/frontend/packages/next-drupal/src/types/resource.ts index 99e5f28..07a44d2 100644 --- a/frontend/packages/next-drupal/src/types/resource.ts +++ b/frontend/packages/next-drupal/src/types/resource.ts @@ -20,17 +20,28 @@ export interface JsonApiResponse extends Record { } export interface JsonApiResourceBodyRelationship { - data: { - type: string - id: string - } + data: + | { + type: string + id: string + meta?: Record | Record[] + } + | { + type?: string + attributes?: Record + relationships?: + | Record + | Record[] + }[] } export interface JsonApiCreateResourceBody { data: { type?: string attributes?: Record - relationships?: Record + relationships?: + | Record + | Record[] } } diff --git a/frontend/packages/next-drupal/tsconfig.json b/frontend/packages/next-drupal/tsconfig.json index a5cb75c..a31a1c2 100644 --- a/frontend/packages/next-drupal/tsconfig.json +++ b/frontend/packages/next-drupal/tsconfig.json @@ -1,4 +1,18 @@ { - "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "es2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "exclude": ["node_modules", "**/*.test.ts"], "include": ["src/**/*.ts"] } diff --git a/frontend/starters/development/app/[...slug]/page.tsx b/frontend/starters/development/app/[...slug]/page.tsx index 50d742d..60b577f 100644 --- a/frontend/starters/development/app/[...slug]/page.tsx +++ b/frontend/starters/development/app/[...slug]/page.tsx @@ -1,6 +1,4 @@ -import { draftMode } from "next/headers" import { notFound } from "next/navigation" -import { getDraftData } from "next-drupal/draft" import { Article } from "@/components/drupal/Article" import { BasicPage } from "@/components/drupal/BasicPage" import { drupal } from "@/lib/drupal" @@ -12,12 +10,6 @@ async function getNode(slug: string[]) { const params: JsonApiParams = {} - const draftData = getDraftData() - - if (draftData.path === path) { - params.resourceVersion = draftData.resourceVersion - } - // Translating the path also allows us to discover the entity type. const translatedPath = await drupal.translatePath(path) @@ -77,7 +69,7 @@ export async function generateMetadata( } } -const RESOURCE_TYPES = ["node--page", "node--post"] +const RESOURCE_TYPES = ["node--page"] export async function generateStaticParams(): Promise { const resources = await drupal.getResourceCollectionPathSegments( @@ -109,7 +101,6 @@ export default async function NodePage({ params: { slug }, searchParams, }: NodePageProps) { - const isDraftMode = draftMode().isEnabled const path = `/${slug.join("/")}` let node try { @@ -119,11 +110,6 @@ export default async function NodePage({ notFound() } - // If we're not in draft mode and the resource is not published, return a 404. - if (!isDraftMode && node?.status === false) { - notFound() - } - return ( <> {node.type === "node--page" && } diff --git a/frontend/starters/development/app/admin/content/page.tsx b/frontend/starters/development/app/admin/content/page.tsx index 7a52695..d69f45f 100644 --- a/frontend/starters/development/app/admin/content/page.tsx +++ b/frontend/starters/development/app/admin/content/page.tsx @@ -64,7 +64,7 @@ type NodePageProps = { searchParams: { [key: string]: string | string[] | undefined } } -export async function ContentList() { +async function ContentList() { async function fetchNodes() { try { const fetchedNodes = await getNodes("node--page") @@ -76,7 +76,7 @@ export async function ContentList() { const nodes = await fetchNodes() const tableRows = nodes.map((node) => ( - + ( diff --git a/frontend/starters/development/app/api/disable-draft/route.ts b/frontend/starters/development/app/api/disable-draft/route.ts deleted file mode 100644 index 8190094..0000000 --- a/frontend/starters/development/app/api/disable-draft/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { disableDraftMode } from "next-drupal/draft" -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - return disableDraftMode() -} diff --git a/frontend/starters/development/app/api/draft/route.ts b/frontend/starters/development/app/api/draft/route.ts deleted file mode 100644 index b8757e2..0000000 --- a/frontend/starters/development/app/api/draft/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { drupal } from "@/lib/drupal" -import { enableDraftMode } from "next-drupal/draft" -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest): Promise { - return enableDraftMode(request, drupal) -} diff --git a/frontend/starters/development/app/api/revalidate/route.ts b/frontend/starters/development/app/api/revalidate/route.ts index 391416d..b06a2de 100644 --- a/frontend/starters/development/app/api/revalidate/route.ts +++ b/frontend/starters/development/app/api/revalidate/route.ts @@ -17,7 +17,7 @@ async function handler(request: NextRequest) { } try { - revalidatePath(path) + revalidatePath(path, "layout") return new Response("Revalidated.") } catch (error) { diff --git a/frontend/starters/development/app/blocks/Hero/Hero.tsx b/frontend/starters/development/app/blocks/Hero/Hero.tsx index 5d00039..de7f1b5 100644 --- a/frontend/starters/development/app/blocks/Hero/Hero.tsx +++ b/frontend/starters/development/app/blocks/Hero/Hero.tsx @@ -2,7 +2,7 @@ import { Title, Text, Button, Container } from "@mantine/core" import { Dots } from "./Dots" import classes from "./Hero.module.css" -export function HeroBlock({ title, subtitle, description, buttons }) { +export function HeroBlock({ title, subtitle, description }) { return ( diff --git a/frontend/starters/development/app/create/page/new/page.tsx b/frontend/starters/development/app/create/page/new/page.tsx index 5e58032..aa7e745 100644 --- a/frontend/starters/development/app/create/page/new/page.tsx +++ b/frontend/starters/development/app/create/page/new/page.tsx @@ -9,7 +9,10 @@ import { useRouter } from "next/navigation" import { toast } from "sonner" import { useState } from "react" -export default function Page({ path, data }: { path: string; data: Data }) { +export const dynamic = "force-dynamic" + +export default function Page({ params }) { + const data = params const backendUrl = process.env.NEXT_PUBLIC_DRUPAL_HOST const [loading, setLoading] = useState(false) const router = useRouter() @@ -62,7 +65,11 @@ export default function Page({ path, data }: { path: string; data: Data }) { // Check that we are already POSTing to Drupal const blocks = await processBlocks(data) - const blocksRef: { id: string; type: string }[] = [] + const blocksRef: { + id: string + type: string + meta?: Record + }[] = [] blocks && blocks.forEach((block) => blocksRef.push({ diff --git a/frontend/starters/development/app/edit/[...puckPath]/client.tsx b/frontend/starters/development/app/edit/[...puckPath]/client.tsx index a66bc50..bd00a1c 100644 --- a/frontend/starters/development/app/edit/[...puckPath]/client.tsx +++ b/frontend/starters/development/app/edit/[...puckPath]/client.tsx @@ -7,7 +7,6 @@ import { drupalFieldPrefix } from "@powerstack/utils" import { toast } from "sonner" import { useRouter } from "next/navigation" import { triggerRevalidation } from "@/lib/trigger-revalidation" -import { useEffect } from "react" export function Client({ path, data }: { path: string; data: Data }) { const router = useRouter() @@ -76,7 +75,11 @@ export function Client({ path, data }: { path: string; data: Data }) { } } const blocks = await processBlocks(data) - const blocksRef: { id: string; type: string }[] = [] + const blocksRef: { + id: string + type: string + meta?: Record + }[] = [] blocks && blocks.forEach((block) => blocksRef.push({ diff --git a/frontend/starters/development/app/edit/[...puckPath]/page.tsx b/frontend/starters/development/app/edit/[...puckPath]/page.tsx index 5bf97d5..697e490 100644 --- a/frontend/starters/development/app/edit/[...puckPath]/page.tsx +++ b/frontend/starters/development/app/edit/[...puckPath]/page.tsx @@ -12,9 +12,8 @@ */ import { notFound } from "next/navigation" -import { getDraftData } from "next-drupal/draft" import { drupal } from "@/lib/drupal" -import type { Metadata } from "next" +import type { Metadata, NextPage } from "next" import type { DrupalNode, JsonApiParams } from "next-drupal" import "@measured/puck/puck.css" import { Client } from "./client" @@ -24,6 +23,7 @@ import { formatDrupalField, drupalFieldPrefix, } from "@powerstack/utils" +import { unstable_noStore as noStore } from "next/cache" export async function generateMetadata({ params: { puckPath = [] }, @@ -42,12 +42,6 @@ async function getNode(slug: string[]) { const params: JsonApiParams = {} - const draftData = getDraftData() - - if (draftData.path === path) { - params.resourceVersion = draftData.resourceVersion - } - // Translating the path also allows us to discover the entity type. const translatedPath = await drupal.translatePath(path) @@ -65,7 +59,7 @@ async function getNode(slug: string[]) { if (type === "node--page") { params.include = "field_page_builder,uid" } - + noStore() const resource = await drupal.getResource(type, uuid, { params, }) diff --git a/frontend/starters/development/components/misc/DraftAlert/Client.tsx b/frontend/starters/development/components/misc/DraftAlert/Client.tsx deleted file mode 100644 index 00932ae..0000000 --- a/frontend/starters/development/components/misc/DraftAlert/Client.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client" - -import { useEffect, useState } from "react" - -export function DraftAlertClient({ - isDraftEnabled, -}: { - isDraftEnabled: boolean -}) { - const [showDraftAlert, setShowDraftAlert] = useState(false) - - useEffect(() => { - setShowDraftAlert(isDraftEnabled && window.top === window.self) - }, [isDraftEnabled]) - - if (!showDraftAlert) { - return null - } - - function buttonHandler() { - void fetch("/api/disable-draft") - setShowDraftAlert(false) - } - - return ( -
-

- This page is a draft. - -

-
- ) -} diff --git a/frontend/starters/development/components/misc/DraftAlert/index.tsx b/frontend/starters/development/components/misc/DraftAlert/index.tsx deleted file mode 100644 index a07f0d6..0000000 --- a/frontend/starters/development/components/misc/DraftAlert/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Suspense } from "react" -import { draftMode } from "next/headers" -import { DraftAlertClient } from "./Client" - -export function DraftAlert() { - const isDraftEnabled = draftMode().isEnabled - - return ( - - - - ) -} diff --git a/frontend/starters/development/components/ui/dropdown-menu.tsx b/frontend/starters/development/components/ui/dropdown-menu.tsx index 9484b9d..597ff4a 100644 --- a/frontend/starters/development/components/ui/dropdown-menu.tsx +++ b/frontend/starters/development/components/ui/dropdown-menu.tsx @@ -8,6 +8,7 @@ import { cn } from "@/lib/utils" import { toast } from "sonner" import { drupal } from "@/lib/drupal" import { useRouter } from "next/navigation" +import { DrupalNode } from "next-drupal/types" const DropdownMenu = DropdownMenuPrimitive.Root @@ -94,10 +95,13 @@ const DropdownMenuItem = React.forwardRef< /> )) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + const DropdownMenuDeleteItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean + node: DrupalNode } >(({ className, inset, node, ...props }, ref) => { const router = useRouter() @@ -129,7 +133,7 @@ const DropdownMenuDeleteItem = React.forwardRef< ) }) -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName +DropdownMenuDeleteItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, diff --git a/frontend/starters/development/lib/trigger-revalidation.ts b/frontend/starters/development/lib/trigger-revalidation.ts index 4a1ebf8..ff8fcde 100644 --- a/frontend/starters/development/lib/trigger-revalidation.ts +++ b/frontend/starters/development/lib/trigger-revalidation.ts @@ -25,7 +25,7 @@ export const triggerRevalidation = async (path) => { }) const result = await response if (result.ok) { - console.log("Page revalidated successfully") + console.log("Edit page revalidated successfully") } else { console.error("Failed to revalidate") } diff --git a/frontend/starters/development/puck.config.tsx b/frontend/starters/development/puck.config.tsx index 9a6537a..b18ebb1 100644 --- a/frontend/starters/development/puck.config.tsx +++ b/frontend/starters/development/puck.config.tsx @@ -6,7 +6,8 @@ import { TextBlock } from "./app/blocks/Text/Text" import { StatsBlock } from "./app/blocks/Stats/Stats" type Props = { - HeadingBlock: { title: string } + Hero: { title?: string; subtitle?: string; description?: string } + Text: { title?: string; text?: string } } export const config: Config = { diff --git a/frontend/starters/development/tsconfig.json b/frontend/starters/development/tsconfig.json index ec0af9f..35c2af8 100644 --- a/frontend/starters/development/tsconfig.json +++ b/frontend/starters/development/tsconfig.json @@ -4,7 +4,7 @@ "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": true, + "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, @@ -27,5 +27,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "../../packages/next-drupal/src/*"] }