@pantheon-systems/create-p1-starter-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/index.js +5 -0
  2. package/lib/cli.js +149 -0
  3. package/lib/copy-template.js +67 -0
  4. package/lib/install-deps.js +68 -0
  5. package/lib/messages.js +28 -0
  6. package/package.json +38 -0
  7. package/template/.env.example +10 -0
  8. package/template/README.md +53 -0
  9. package/template/__tests__/editor-integration.test.ts +53 -0
  10. package/template/__tests__/remote-datasource-fetchers.test.ts +226 -0
  11. package/template/app/[...puckPath]/client.tsx +9 -0
  12. package/template/app/[...puckPath]/page.tsx +131 -0
  13. package/template/app/collection-nav.tsx +47 -0
  14. package/template/app/layout.tsx +13 -0
  15. package/template/app/p1/[[...p1]]/editor-client.tsx +116 -0
  16. package/template/app/p1/[[...p1]]/page.tsx +19 -0
  17. package/template/app/p1/[[...p1]]/render-client.tsx +9 -0
  18. package/template/app/p1/api/[...p1]/route.ts +16 -0
  19. package/template/app/p1/auth/[...action]/route.ts +9 -0
  20. package/template/app/p1/merge/merge-client.tsx +635 -0
  21. package/template/app/p1/merge/merge.css +257 -0
  22. package/template/app/p1/merge/page.tsx +12 -0
  23. package/template/app/page.tsx +130 -0
  24. package/template/app/styles.css +18 -0
  25. package/template/components/puck/block-padding.ts +2 -0
  26. package/template/components/puck/button-block.tsx +41 -0
  27. package/template/components/puck/divider-block.tsx +10 -0
  28. package/template/components/puck/grid-block.tsx +80 -0
  29. package/template/components/puck/heading-block.tsx +38 -0
  30. package/template/components/puck/image-block.tsx +33 -0
  31. package/template/components/puck/list-block.tsx +72 -0
  32. package/template/components/puck/paragraph-block.tsx +44 -0
  33. package/template/components/puck/quote-block.tsx +23 -0
  34. package/template/components/puck/root.tsx +20 -0
  35. package/template/components/puck/spacer-block.tsx +19 -0
  36. package/template/eslint.config.js +161 -0
  37. package/template/lib/content-publisher.ts +128 -0
  38. package/template/lib/fetcher-helpers.ts +17 -0
  39. package/template/lib/monsters-api.ts +125 -0
  40. package/template/lib/remote-datasource-fetchers.ts +10 -0
  41. package/template/lib/remote-datasources.ts +154 -0
  42. package/template/lib/swapi.ts +75 -0
  43. package/template/next-env.d.ts +6 -0
  44. package/template/next.config.mjs +13 -0
  45. package/template/package.json +42 -0
  46. package/template/postcss.config.mjs +8 -0
  47. package/template/public/sw.js +8 -0
  48. package/template/puck.config.tsx +51 -0
  49. package/template/tsconfig/base.json +20 -0
  50. package/template/tsconfig/nextjs.json +21 -0
  51. package/template/tsconfig.json +16 -0
  52. package/template/vitest.config.ts +7 -0
@@ -0,0 +1,226 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@pantheon-systems/cpub-react-sdk/server", () => ({
4
+ PCCConvenienceFunctions: {
5
+ getPaginatedArticles: vi.fn(),
6
+ getArticleBySlugOrId: vi.fn(),
7
+ },
8
+ }));
9
+
10
+ // We need to mock the user-remote-datasource-store since loadRemoteDatasourceContext uses it
11
+ vi.mock("@pantheon-systems/puck-css/server", async (importOriginal) => {
12
+ const actual = await importOriginal<Record<string, unknown>>();
13
+ return actual;
14
+ });
15
+
16
+ import { REMOTE_DATASOURCE_FETCHERS } from "../lib/remote-datasource-fetchers";
17
+ import type { RemoteDatasourceFetcherParams } from "@pantheon-systems/puck-css/server";
18
+
19
+ const { PCCConvenienceFunctions } = await import(
20
+ "@pantheon-systems/cpub-react-sdk/server"
21
+ );
22
+ const mockGetPaginatedArticles = PCCConvenienceFunctions
23
+ .getPaginatedArticles as ReturnType<typeof vi.fn>;
24
+ const mockGetArticleBySlugOrId = PCCConvenienceFunctions
25
+ .getArticleBySlugOrId as ReturnType<typeof vi.fn>;
26
+
27
+ function makeFetcherParams(
28
+ overrides: Partial<RemoteDatasourceFetcherParams> = {},
29
+ ): RemoteDatasourceFetcherParams {
30
+ return {
31
+ searchParams: {},
32
+ urlParams: {},
33
+ savedPreviewParams: {},
34
+ fetchImpl: vi.fn() as unknown as typeof fetch,
35
+ ...overrides,
36
+ };
37
+ }
38
+
39
+ function getFetcher(id: string) {
40
+ const f = REMOTE_DATASOURCE_FETCHERS.find((f) => f.id === id);
41
+ if (!f) throw new Error(`No fetcher with id "${id}"`);
42
+ return f;
43
+ }
44
+
45
+ describe("swapi fetcher", () => {
46
+ const fetcher = getFetcher("swapi");
47
+
48
+ it("returns {} when no id is available", async () => {
49
+ const result = await fetcher.fetch(makeFetcherParams());
50
+ expect(result).toEqual({});
51
+ });
52
+
53
+ it("returns {} on non-OK response", async () => {
54
+ const fetchImpl = vi.fn().mockResolvedValue({
55
+ ok: false,
56
+ status: 404,
57
+ json: async () => ({}),
58
+ });
59
+ const result = await fetcher.fetch(makeFetcherParams({
60
+ searchParams: { id: "1" },
61
+ fetchImpl,
62
+ }));
63
+ expect(result).toEqual({});
64
+ expect(fetchImpl).toHaveBeenCalledWith("https://swapi.info/api/people/1");
65
+ });
66
+
67
+ it("returns parsed JSON on success", async () => {
68
+ const payload = { name: "Luke Skywalker", height: "172" };
69
+ const fetchImpl = vi.fn().mockResolvedValue({
70
+ ok: true,
71
+ json: async () => payload,
72
+ });
73
+ const result = await fetcher.fetch(makeFetcherParams({
74
+ searchParams: { id: "1" },
75
+ fetchImpl,
76
+ }));
77
+ expect(result).toEqual(payload);
78
+ });
79
+
80
+ it("uses urlParams.id when searchParams has no id", async () => {
81
+ const fetchImpl = vi.fn().mockResolvedValue({
82
+ ok: true,
83
+ json: async () => ({ name: "FromPath" }),
84
+ });
85
+ const result = await fetcher.fetch(makeFetcherParams({
86
+ urlParams: { id: "5" },
87
+ fetchImpl,
88
+ }));
89
+ expect(result).toEqual({ name: "FromPath" });
90
+ expect(fetchImpl).toHaveBeenCalledWith("https://swapi.info/api/people/5");
91
+ });
92
+
93
+ it("prefers query id over path id", async () => {
94
+ const fetchImpl = vi.fn().mockResolvedValue({
95
+ ok: true,
96
+ json: async () => ({ name: "Q" }),
97
+ });
98
+ await fetcher.fetch(makeFetcherParams({
99
+ searchParams: { id: "2" },
100
+ urlParams: { id: "9" },
101
+ fetchImpl,
102
+ }));
103
+ expect(fetchImpl).toHaveBeenCalledWith("https://swapi.info/api/people/2");
104
+ });
105
+ });
106
+
107
+ describe("swapi_list fetcher", () => {
108
+ const fetcher = getFetcher("swapi_list");
109
+
110
+ it("maps results to { items: [...] }", async () => {
111
+ const fetchImpl = vi.fn().mockResolvedValue({
112
+ ok: true,
113
+ json: async () => [
114
+ { name: "Luke", url: "https://swapi.info/api/people/1" },
115
+ ],
116
+ });
117
+ const result = await fetcher.fetch(makeFetcherParams({ fetchImpl }));
118
+ expect(result).toEqual({
119
+ items: [{ id: "1", name: "Luke", url: "https://swapi.info/api/people/1" }],
120
+ });
121
+ expect(fetchImpl).toHaveBeenCalledWith("https://swapi.info/api/people");
122
+ });
123
+ });
124
+
125
+ describe("monster fetcher", () => {
126
+ const fetcher = getFetcher("monster");
127
+
128
+ it("returns {} when no index available", async () => {
129
+ const result = await fetcher.fetch(makeFetcherParams());
130
+ expect(result).toEqual({});
131
+ });
132
+
133
+ it("returns parsed pokemon on success", async () => {
134
+ const payload = {
135
+ data: {
136
+ getPokemon: {
137
+ key: "bulbasaur",
138
+ species: "Bulbasaur",
139
+ num: 1,
140
+ types: ["Grass", "Poison"],
141
+ },
142
+ },
143
+ };
144
+ const fetchImpl = vi.fn().mockResolvedValue({
145
+ ok: true,
146
+ json: async () => payload,
147
+ });
148
+ const result = await fetcher.fetch(makeFetcherParams({
149
+ searchParams: { monster: "bulbasaur" },
150
+ fetchImpl,
151
+ }));
152
+ expect(result).toEqual({
153
+ key: "bulbasaur",
154
+ species: "Bulbasaur",
155
+ num: 1,
156
+ types: ["Grass", "Poison"],
157
+ index: "bulbasaur",
158
+ name: "Bulbasaur",
159
+ url: "/pokemon/bulbasaur",
160
+ });
161
+ });
162
+ });
163
+
164
+ describe("monster_list fetcher", () => {
165
+ const fetcher = getFetcher("monster_list");
166
+
167
+ it("maps results to { items: [...] }", async () => {
168
+ const fetchImpl = vi.fn().mockResolvedValue({
169
+ ok: true,
170
+ json: async () => ({
171
+ data: {
172
+ getAllPokemon: [
173
+ { key: "bulbasaur", species: "Bulbasaur" },
174
+ ],
175
+ },
176
+ }),
177
+ });
178
+ const result = await fetcher.fetch(makeFetcherParams({ fetchImpl }));
179
+ expect(result).toEqual({
180
+ items: [{ index: "bulbasaur", name: "Bulbasaur", url: "/pokemon/bulbasaur" }],
181
+ });
182
+ });
183
+ });
184
+
185
+ describe("article fetcher", () => {
186
+ const fetcher = getFetcher("article");
187
+
188
+ it("returns {} when no article id", async () => {
189
+ const result = await fetcher.fetch(makeFetcherParams());
190
+ expect(result).toEqual({});
191
+ });
192
+
193
+ it("loads a single article", async () => {
194
+ mockGetArticleBySlugOrId.mockResolvedValueOnce({
195
+ id: "first-article",
196
+ title: "First Article",
197
+ });
198
+ const result = await fetcher.fetch(makeFetcherParams({
199
+ searchParams: { article: "first-article" },
200
+ }));
201
+ expect(result).toEqual({ id: "first-article", title: "First Article" });
202
+ expect(mockGetArticleBySlugOrId).toHaveBeenCalledWith("first-article", {
203
+ contentType: "TEXT_MARKDOWN",
204
+ });
205
+ });
206
+ });
207
+
208
+ describe("article_list fetcher", () => {
209
+ const fetcher = getFetcher("article_list");
210
+
211
+ it("maps list payload items to normalized article rows", async () => {
212
+ mockGetPaginatedArticles.mockResolvedValueOnce({
213
+ data: [
214
+ { id: "a1", title: "First Article", slug: "first-article" },
215
+ { id: 2, attributes: { title: "Second Article", slug: "second-article" } },
216
+ ],
217
+ });
218
+ const result = await fetcher.fetch(makeFetcherParams());
219
+ expect(result).toEqual({
220
+ items: [
221
+ { id: "a1", title: "First Article", slug: "first-article", url: undefined },
222
+ { id: "2", title: "Second Article", slug: "second-article", url: undefined },
223
+ ],
224
+ });
225
+ });
226
+ });
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ import type { Data } from "@puckeditor/core";
4
+ import { RenderClient } from "@pantheon-systems/puck-css";
5
+ import config from "../../puck.config";
6
+
7
+ export function Client({ data }: { data: Data }) {
8
+ return <RenderClient config={config} data={data} />;
9
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Catch-all route that renders user-facing pages generated by Puck.
3
+ */
4
+
5
+ import { Client } from "./client";
6
+ import { Metadata } from "next";
7
+ import {
8
+ loadRemoteDatasourceContext,
9
+ extractReferencedDatasourceIds,
10
+ getPage,
11
+ listRouteTemplateKeysFromDatabase,
12
+ resolveDataTemplates,
13
+ resolveStringTemplates,
14
+ ensureInitialized,
15
+ pagePathFromCatchAllSegments,
16
+ } from "@pantheon-systems/puck-css/server";
17
+ import { REMOTE_DATASOURCE_FETCHERS } from "../../lib/remote-datasource-fetchers";
18
+
19
+ const initPromise = ensureInitialized({
20
+ p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
21
+ p1ApiKey: process.env.P1_CSS_API_KEY,
22
+ p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
23
+ p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID,
24
+ });
25
+
26
+ export async function generateMetadata({
27
+ params,
28
+ searchParams,
29
+ }: {
30
+ params: Promise<{ puckPath: string[] }>;
31
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
32
+ }): Promise<Metadata> {
33
+ await initPromise;
34
+ const { puckPath = [] } = await params;
35
+ const path = pagePathFromCatchAllSegments(puckPath);
36
+
37
+ const [pageData, sp, routeTemplateKeys] = await Promise.all([
38
+ getPage(path),
39
+ searchParams,
40
+ listRouteTemplateKeysFromDatabase(),
41
+ ]);
42
+
43
+ const rawTitle = pageData?.root.props?.title;
44
+ if (typeof rawTitle !== "string") {
45
+ return { title: rawTitle };
46
+ }
47
+ if (!rawTitle.includes("{{")) {
48
+ return { title: rawTitle };
49
+ }
50
+ const referencedDatasourceIds = extractReferencedDatasourceIds(pageData);
51
+ const context = await loadRemoteDatasourceContext({
52
+ searchParams: sp,
53
+ fetchImpl: fetch,
54
+ pagePath: path,
55
+ routeTemplateKeys,
56
+ builtinFetchers: REMOTE_DATASOURCE_FETCHERS,
57
+ referencedDatasourceIds,
58
+ });
59
+ return { title: await resolveStringTemplates(rawTitle, context) };
60
+ }
61
+
62
+ export default async function Page({
63
+ params,
64
+ searchParams,
65
+ }: {
66
+ params: Promise<{ puckPath: string[] }>;
67
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
68
+ }) {
69
+ await initPromise;
70
+ const { puckPath = [] } = await params;
71
+ const path = pagePathFromCatchAllSegments(puckPath);
72
+
73
+ const [data, searchParamData, routeTemplateKeys] = await Promise.all([
74
+ getPage(path),
75
+ searchParams,
76
+ listRouteTemplateKeysFromDatabase(),
77
+ ]);
78
+
79
+ if (!data) {
80
+ return (
81
+ <main className="flex min-h-screen items-center justify-center bg-gray-50">
82
+ <div className="mx-auto max-w-lg px-6 py-16 text-center">
83
+ <h1 className="text-3xl font-bold tracking-tight text-gray-900">
84
+ 404 &ndash; This page doesn&apos;t exist yet
85
+ </h1>
86
+ <p className="mt-4 text-gray-600">
87
+ This page hasn&apos;t been created. Use the editor to build it.
88
+ </p>
89
+
90
+ <nav className="mt-10 flex flex-col gap-3">
91
+ <a
92
+ href={`/p1${path}`}
93
+ className="rounded-lg bg-gray-900 px-5 py-3 text-sm font-medium text-white hover:bg-gray-700"
94
+ >
95
+ Edit this page
96
+ </a>
97
+ <a
98
+ href="/p1"
99
+ className="rounded-lg border border-gray-300 px-5 py-3 text-sm font-medium text-gray-900 hover:bg-gray-100"
100
+ >
101
+ Open the Page Editor
102
+ </a>
103
+ <a
104
+ href="https://staging.content.pantheon.io/dashboard/sites"
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ className="rounded-lg border border-gray-300 px-5 py-3 text-sm font-medium text-gray-900 hover:bg-gray-100"
108
+ >
109
+ P1 Dashboard &rarr;
110
+ </a>
111
+ </nav>
112
+ </div>
113
+ </main>
114
+ );
115
+ }
116
+
117
+ const referencedDatasourceIds = extractReferencedDatasourceIds(data);
118
+ const context = await loadRemoteDatasourceContext({
119
+ searchParams: searchParamData,
120
+ fetchImpl: fetch,
121
+ pagePath: path,
122
+ routeTemplateKeys,
123
+ builtinFetchers: REMOTE_DATASOURCE_FETCHERS,
124
+ referencedDatasourceIds,
125
+ });
126
+ const resolvedData = await resolveDataTemplates(data, context);
127
+
128
+ return <Client data={resolvedData} />;
129
+ }
130
+
131
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { useState } from "react";
5
+
6
+ export function CollectionNav({ templatePath }: { templatePath: string }) {
7
+ const router = useRouter();
8
+ const segments = templatePath.split("/");
9
+ const params = segments
10
+ .filter((s) => s.startsWith(":"))
11
+ .map((s) => s.slice(1));
12
+
13
+ const [values, setValues] = useState<Record<string, string>>(
14
+ Object.fromEntries(params.map((p) => [p, ""])),
15
+ );
16
+
17
+ function handleGo() {
18
+ if (!params.every((p) => values[p])) return;
19
+ const resolved = segments
20
+ .map((s) => (s.startsWith(":") ? encodeURIComponent(values[s.slice(1)]) : s))
21
+ .join("/");
22
+ router.push(resolved);
23
+ }
24
+
25
+ return (
26
+ <div className="flex flex-wrap items-center gap-2">
27
+ <span className="text-sm text-gray-500 font-mono">{templatePath}</span>
28
+ {params.map((param) => (
29
+ <input
30
+ key={param}
31
+ type="text"
32
+ placeholder={param}
33
+ value={values[param]}
34
+ onChange={(e) => setValues({ ...values, [param]: e.target.value })}
35
+ onKeyDown={(e) => e.key === "Enter" && handleGo()}
36
+ className="rounded border border-gray-300 px-2 py-1 text-sm w-24"
37
+ />
38
+ ))}
39
+ <button
40
+ onClick={handleGo}
41
+ className="rounded bg-gray-900 px-3 py-1 text-sm text-white hover:bg-gray-700"
42
+ >
43
+ Go
44
+ </button>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,13 @@
1
+ import "./styles.css";
2
+
3
+ export default function RootLayout({
4
+ children,
5
+ }: {
6
+ children: React.ReactNode;
7
+ }) {
8
+ return (
9
+ <html lang="en">
10
+ <body>{children}</body>
11
+ </html>
12
+ );
13
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Puck } from "@puckeditor/core";
6
+ import {
7
+ P1App,
8
+ createNextConfig,
9
+ useP1Editor,
10
+ useP1Plugins,
11
+ wrapConfigForEditorPreview,
12
+ P1QueryProvider,
13
+ } from "@pantheon-systems/puck-css";
14
+ import { P1NextRouterProvider } from "@pantheon-systems/p1-next-sdk";
15
+ import type { Checkpoint } from "@pantheon-systems/puck-css";
16
+
17
+ import "@pantheon-systems/puck-css/styles.css";
18
+ import "@pantheon-systems/puck-css/pds/styles.css";
19
+
20
+ import config from "../../../puck.config";
21
+
22
+ let p1Config: ReturnType<typeof createNextConfig> | null = null;
23
+ let p1ConfigError: string | null = null;
24
+
25
+ try {
26
+ p1Config = createNextConfig();
27
+ } catch (e) {
28
+ p1ConfigError = e instanceof Error ? e.message : String(e);
29
+ }
30
+
31
+ const editorConfig = wrapConfigForEditorPreview(config);
32
+
33
+ export function EditorClientWrapper({ path }: { path: string }) {
34
+ if (!p1Config) {
35
+ return (
36
+ <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
37
+ <h3>Editor unavailable</h3>
38
+ <p style={{ color: "#666" }}>
39
+ {p1ConfigError ?? "P1 configuration is missing."}
40
+ </p>
41
+ <p style={{ color: "#888", fontSize: "14px" }}>
42
+ Set NEXT_PUBLIC_CSS_BASE_URL and NEXT_PUBLIC_CSS_SITE_ID environment
43
+ variables to enable the editor.
44
+ </p>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ return (
50
+ <P1QueryProvider>
51
+ <P1NextRouterProvider>
52
+ <P1App
53
+ config={p1Config}
54
+ loginPageProps={{ title: "P1 Starter", subtitle: "Sign in to edit" }}
55
+ >
56
+ <EditorContent path={path} />
57
+ </P1App>
58
+ </P1NextRouterProvider>
59
+ </P1QueryProvider>
60
+ );
61
+ }
62
+
63
+ function EditorContent({ path }: { path: string }) {
64
+ const router = useRouter();
65
+ const p1Plugins = useP1Plugins(path, config);
66
+
67
+ const handleDocumentSelect = useCallback(
68
+ (docPath: string) => {
69
+ router.push(`/p1/${docPath}`);
70
+ },
71
+ [router],
72
+ );
73
+
74
+ const { loading, error, puckKey, puckProps } = useP1Editor({
75
+ documentPath: path,
76
+ puckConfig: editorConfig,
77
+ additionalPlugins: p1Plugins,
78
+ pluginOptions: {
79
+ onDocumentSelect: handleDocumentSelect,
80
+ selectedDocumentPath: path,
81
+ },
82
+ overrideOptions: {
83
+ showDefaultPublish: false,
84
+ onPublishSuccess: (checkpoint: Checkpoint) => {
85
+ alert(`Published: ${checkpoint.name ?? checkpoint.id}`);
86
+ },
87
+ onPublishError: (err: Error) => {
88
+ alert(`Publish failed: ${err.message}`);
89
+ },
90
+ },
91
+ });
92
+
93
+ if (loading) {
94
+ return (
95
+ <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
96
+ Loading editor...
97
+ </div>
98
+ );
99
+ }
100
+
101
+ if (error) {
102
+ return (
103
+ <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
104
+ <h3>Error loading document</h3>
105
+ <p style={{ color: "#666" }}>{error.message}</p>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <div className="puck-editor-theme">
112
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
113
+ <Puck key={puckKey} {...puckProps as any} _experimentalFullScreenCanvas={true} />
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,19 @@
1
+ import "@puckeditor/core/puck.css";
2
+ import { createP1Pages } from "@pantheon-systems/p1-next-sdk/server";
3
+ import config from "../../../puck.config";
4
+ import { EditorClientWrapper } from "./editor-client";
5
+ import { RenderClientWrapper } from "./render-client";
6
+
7
+ const pages = createP1Pages({
8
+ config,
9
+ p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
10
+ p1ApiKey: process.env.P1_CSS_API_KEY,
11
+ p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
12
+ p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID,
13
+ EditorClient: EditorClientWrapper,
14
+ RenderClient: RenderClientWrapper,
15
+ });
16
+
17
+ export default pages.Page;
18
+ export const generateMetadata = pages.generateMetadata;
19
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ import type { Data } from "@puckeditor/core";
4
+ import { RenderClient } from "@pantheon-systems/puck-css";
5
+ import config from "../../../puck.config";
6
+
7
+ export function RenderClientWrapper({ data }: { data: Data }) {
8
+ return <RenderClient config={config} data={data} />;
9
+ }
@@ -0,0 +1,16 @@
1
+ import { createP1Handler } from "@pantheon-systems/p1-next-sdk/server";
2
+ import config from "../../../../puck.config";
3
+ import { REMOTE_DATASOURCE_FETCHERS } from "../../../../lib/remote-datasource-fetchers";
4
+ import { REMOTE_DATASOURCE_REGISTRY } from "../../../../lib/remote-datasources";
5
+
6
+ const handler = createP1Handler({
7
+ config,
8
+ p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
9
+ p1ApiKey: process.env.P1_CSS_API_KEY,
10
+ p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
11
+ p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID,
12
+ builtinFetchers: REMOTE_DATASOURCE_FETCHERS,
13
+ builtinDatasourceRegistry: REMOTE_DATASOURCE_REGISTRY,
14
+ });
15
+
16
+ export const { GET, POST, DELETE } = handler;
@@ -0,0 +1,9 @@
1
+ import { createP1AuthHandler } from "@pantheon-systems/p1-next-sdk/server";
2
+
3
+ const handler = createP1AuthHandler({
4
+ p1ApiKey: process.env.P1_CSS_API_KEY,
5
+ p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
6
+ prompt: 'login',
7
+ });
8
+
9
+ export const { POST } = handler;