Skip to content

Commit

Permalink
[providers] Improve and group providers (#6069)
Browse files Browse the repository at this point in the history
Co-authored-by: Piero Nicolli <[email protected]>
Co-authored-by: Steve Piercy <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2024
1 parent 04402e5 commit 5660ae4
Show file tree
Hide file tree
Showing 55 changed files with 2,446 additions and 976 deletions.
5 changes: 5 additions & 0 deletions apps/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

This is a proof of concept of a [Next.js](https://nextjs.org) app, using the app router and the `@plone/client` and `@plone/components` library. This is intended to serve as both a playground for the development of both packages and as demo of Plone using Next.js.

> [!WARNING]
> This package or app is experimental.
> The community offers no support whatsoever for it.
> Breaking changes may occur without notice.
## Development

To start, from the root of the monorepo:
Expand Down
3 changes: 3 additions & 0 deletions apps/nextjs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import path from 'path';

/** @type {import('next').NextConfig} */
const nextConfig = {
typescript: {
ignoreBuildErrors: true,
},
// sassOptions: {
// includePaths: [path.join(__dirname, 'src/lib/components/src/styles')],
// },
Expand Down
53 changes: 38 additions & 15 deletions apps/nextjs/src/app/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
'use client';
import React from 'react';
import { useRouter } from 'next/navigation';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PloneClientProvider } from '@plone/providers';
import {
useRouter,
usePathname,
useSearchParams,
useParams,
} from 'next/navigation';
import { QueryClient } from '@tanstack/react-query';
import { PloneProvider } from '@plone/providers';
import PloneClient from '@plone/client';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { RouterProvider } from 'react-aria-components';
import { FlattenToAppURLProvider } from '@plone/components';
import { flattenToAppURL } from './utils';
import config from './config';

// Custom hook to unify the location object between NextJS and Plone
function useLocation() {
const pathname = usePathname();
const search = useSearchParams();

return {
pathname,
search,
searchStr: '',
hash: (typeof window !== 'undefined' && window.location.hash) || '',
href: (typeof window !== 'undefined' && window.location.href) || '',
};
}

const Providers: React.FC<{
children?: React.ReactNode;
}> = ({ children }) => {
Expand Down Expand Up @@ -39,16 +56,22 @@ const Providers: React.FC<{
const router = useRouter();

return (
<RouterProvider navigate={router.push}>
<PloneClientProvider client={ploneClient}>
<QueryClientProvider client={queryClient}>
<FlattenToAppURLProvider flattenToAppURL={flattenToAppURL}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</FlattenToAppURLProvider>
</QueryClientProvider>
</PloneClientProvider>
</RouterProvider>
<PloneProvider
ploneClient={ploneClient}
queryClient={queryClient}
// NextJS doesn't have a useLocation hook, so we need to unify this
// in a custom hook
useLocation={useLocation}
navigate={(to) => {
router.push(to);
}}
useParams={useParams}
useHref={(to) => flattenToAppURL(to)}
flattenToAppURL={flattenToAppURL}
>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</PloneProvider>
);
};

Expand Down
13 changes: 10 additions & 3 deletions apps/nextjs/src/app/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import config from '@plone/registry';
import { slate } from '@plone/blocks';
import { blocksConfig } from '@plone/blocks';

const settings = {
apiPath: process.env.NEXT_PUBLIC_VERCEL_URL
? // Vercel does not prepend the schema to the NEXT_PUBLIC_VERCEL_URL automatic env var
`https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
: 'http://localhost:3000',
slate,
};

const config = {
settings,
};
// @ts-expect-error Improve typings
config.set('settings', settings);

// @ts-expect-error Improve typings
config.set('blocks', { blocksConfig });

export default config;
32 changes: 8 additions & 24 deletions apps/nextjs/src/app/content.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use client';

import { useQuery } from '@tanstack/react-query';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
import { flattenToAppURL } from './utils';
import { usePloneClient } from '@plone/providers';
import { Breadcrumbs } from '@plone/components';
import { Breadcrumbs, RenderBlocks } from '@plone/components';
import config from '@plone/registry';

import '@plone/components/dist/basic.css';

export default function Content() {
Expand All @@ -16,31 +15,16 @@ export default function Content() {
if (data) {
return (
<div style={{ fontSize: '20px' }}>
<h1>{data.title}</h1>
{/* <Input
name="field1"
title="field 1 title"
placeholder="Type something…"
description="Optional help text"
isRequired
/> */}
<Breadcrumbs
items={data['@components'].breadcrumbs.items || []}
root={data['@components'].breadcrumbs.root}
includeRoot
/>
<ul>
{data?.['@components']?.navigation?.items?.map((item) => (
<li key={item['@id']}>
<Link href={flattenToAppURL(item['@id'])}>
{flattenToAppURL(item['@id'])}
</Link>
</li>
))}
</ul>
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
<RenderBlocks
content={data}
blocksConfig={config.blocks.blocksConfig}
pathname={pathname}
/>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Providers from './Providers';
const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
title: 'NextJS-powered Plone',
title: 'Next.js app powered by Plone',
description: '',
};

Expand Down
4 changes: 4 additions & 0 deletions apps/remix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This is a proof of concept of a [Remix](https://remix.run) app, using the `@plone/client` and `@plone/components` libraries.
This is intended to serve as both a playground for the development of both packages and as a demo of Plone using Remix.

> [!WARNING]
> This package or app is experimental.
> The community offers no support whatsoever for it.
> Breaking changes may occur without notice.
## Development

Expand Down
8 changes: 8 additions & 0 deletions apps/remix/app/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ploneClient from '@plone/client';
import config from '@plone/registry';

const cli = ploneClient.initialize({
apiPath: config.settings.apiPath,
});

export { cli as ploneClient };
12 changes: 9 additions & 3 deletions apps/remix/app/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import config from '@plone/registry';
import { blocksConfig, slate } from '@plone/blocks';

const settings = {
apiPath: 'http://localhost:8080/Plone',
slate,
};

const config = {
settings,
};
// @ts-expect-error We need to fix typing
config.set('settings', settings);

// @ts-expect-error We need to fix typing
config.set('blocks', { blocksConfig });

export default config;
1 change: 1 addition & 0 deletions apps/remix/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { RemixBrowser } from '@remix-run/react';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import './config';

startTransition(() => {
hydrateRoot(
Expand Down
1 change: 1 addition & 0 deletions apps/remix/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createReadableStreamFromReadable } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { isbot } from 'isbot';
import { renderToPipeableStream } from 'react-dom/server';
import './config';

const ABORT_DELAY = 5_000;

Expand Down
47 changes: 34 additions & 13 deletions apps/remix/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,33 @@ import { cssBundleHref } from '@remix-run/css-bundle';
import type { LinksFunction } from '@remix-run/node';
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useHref,
useLocation,
useNavigate,
useParams,
} from '@remix-run/react';
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PloneClientProvider } from '@plone/providers';
import { QueryClient } from '@tanstack/react-query';
import PloneClient from '@plone/client';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

import '@plone/components/dist/basic.css';
import { flattenToAppURL } from './utils';
import { PloneProvider } from '@plone/providers';
import config from '@plone/registry';

export const links: LinksFunction = () => [
...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : []),
];

function useHrefLocal(to: string) {
return useHref(flattenToAppURL(to));
}

export default function App() {
const [queryClient] = useState(
() =>
Expand All @@ -34,10 +45,16 @@ export default function App() {

const [ploneClient] = useState(() =>
PloneClient.initialize({
apiPath: 'http://localhost:8080/Plone',
apiPath: config.settings.apiPath,
}),
);

const RRNavigate = useNavigate();

const navigate = (to: string) => {
return RRNavigate(flattenToAppURL(to));
};

return (
<html lang="en">
<head>
Expand All @@ -47,15 +64,19 @@ export default function App() {
<Links />
</head>
<body>
<PloneClientProvider client={ploneClient}>
<QueryClientProvider client={queryClient}>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</PloneClientProvider>
<PloneProvider
ploneClient={ploneClient}
queryClient={queryClient}
useLocation={useLocation}
useParams={useParams}
useHref={useHrefLocal}
navigate={navigate}
>
<Outlet />
<ScrollRestoration />
<Scripts />
<ReactQueryDevtools initialIsOpen={false} />
</PloneProvider>
</body>
</html>
);
Expand Down
79 changes: 79 additions & 0 deletions apps/remix/app/routes/$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
json,
type LoaderFunctionArgs,
type MetaFunction,
} from '@remix-run/node';
import {
dehydrate,
QueryClient,
HydrationBoundary,
useQuery,
} from '@tanstack/react-query';
import { flattenToAppURL } from '../utils';
import { useLoaderData, useLocation } from '@remix-run/react';
import { usePloneClient } from '@plone/providers';
import { Breadcrumbs, RenderBlocks } from '@plone/components';
import config from '@plone/registry';
import { ploneClient } from '../client';

export const meta: MetaFunction = () => {
return [
{ title: 'Plone on Remix' },
{ name: 'description', content: 'Welcome to Plone on Remix!' },
];
};

const expand = ['breadcrumbs', 'navigation'];

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
});

const { getContentQuery } = ploneClient;

await queryClient.prefetchQuery(
getContentQuery({ path: flattenToAppURL(request.url), expand }),
);

return json({ dehydratedState: dehydrate(queryClient) });
};

function Page() {
const { getContentQuery } = usePloneClient();
const pathname = useLocation().pathname;
const { data } = useQuery(getContentQuery({ path: pathname, expand }));

if (!data) return null;
return (
<>
<Breadcrumbs
items={data['@components'].breadcrumbs.items || []}
root={data['@components'].breadcrumbs.root}
includeRoot
/>
<RenderBlocks
content={data}
blocksConfig={config.blocks.blocksConfig}
pathname="/"
/>
</>
);
}

export default function Index() {
const { dehydratedState } = useLoaderData<typeof loader>();

return (
<HydrationBoundary state={dehydratedState}>
<Page />
</HydrationBoundary>
);
}
Loading

0 comments on commit 5660ae4

Please sign in to comment.