@pantheon-systems/create-p1-starter-kit 0.4.4 → 0.5.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.
- package/package.json +1 -1
- package/template/.env.example +5 -0
- package/template/CHANGELOG.md +8 -0
- package/template/__tests__/auth-route.test.ts +28 -0
- package/template/__tests__/editor-integration.test.ts +4 -0
- package/template/app/[...puckPath]/client.tsx +175 -0
- package/template/app/[...puckPath]/page.tsx +4 -2
- package/template/app/p1/[[...p1]]/editor-client.tsx +109 -2
- package/template/app/p1/[[...p1]]/page.tsx +3 -3
- package/template/app/p1/auth/[...action]/route.ts +1 -1
- package/template/app/page.tsx +18 -60
- package/template/components/p1-lockup.tsx +33 -0
- package/template/components/puck/welcome-block-render.tsx +110 -0
- package/template/components/puck/welcome-block.tsx +45 -0
- package/template/package.json +2 -3
- package/template/puck.config.tsx +6 -0
- package/template/app/p1/[[...p1]]/render-client.tsx +0 -9
package/package.json
CHANGED
package/template/.env.example
CHANGED
|
@@ -14,3 +14,8 @@ CSS_API_KEY=your-api-key
|
|
|
14
14
|
|
|
15
15
|
# Branch is auto-detected (defaults to main) unless specified:
|
|
16
16
|
# NEXT_PUBLIC_CSS_BRANCH_ID=branch-456
|
|
17
|
+
|
|
18
|
+
# --- Dev tools (disabled by default) ---
|
|
19
|
+
# Show the RoleSwitcher dropdown in the P1 editor for local testing of
|
|
20
|
+
# admin/editor/junior-editor permissions. Never enable this in production.
|
|
21
|
+
# NEXT_PUBLIC_ENABLE_ROLE_SWITCHER=true
|
package/template/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const appDir = resolve(__dirname, "..");
|
|
8
|
+
|
|
9
|
+
describe("auth route does not force re-authentication on every login", () => {
|
|
10
|
+
const content = readFileSync(
|
|
11
|
+
resolve(appDir, "app/p1/auth/[...action]/route.ts"),
|
|
12
|
+
"utf-8",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
it("does not hardcode an OAuth prompt override", () => {
|
|
16
|
+
// prompt: 'login' forces Google's full re-auth screen on every broker
|
|
17
|
+
// login, even with a live Google session — see PCC-3391.
|
|
18
|
+
expect(content).not.toMatch(/prompt\s*:\s*['"]login['"]/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("uses select_account so users can still switch Google accounts", () => {
|
|
22
|
+
// Omitting `prompt` entirely silently re-authenticates whichever Google
|
|
23
|
+
// account has a live session, with no way to pick a different one on
|
|
24
|
+
// logout/login. select_account shows a lightweight account chooser
|
|
25
|
+
// (one click if already signed in) without forcing full re-auth.
|
|
26
|
+
expect(content).toMatch(/prompt\s*:\s*['"]select_account['"]/);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -27,6 +27,10 @@ describe("editor-client uses P1 plugins", () => {
|
|
|
27
27
|
it("wraps with P1QueryProvider for TanStack React Query", () => {
|
|
28
28
|
expect(content).toContain("P1QueryProvider");
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
it("gates RoleSwitcher behind NEXT_PUBLIC_ENABLE_ROLE_SWITCHER", () => {
|
|
32
|
+
expect(content).toContain("NEXT_PUBLIC_ENABLE_ROLE_SWITCHER");
|
|
33
|
+
});
|
|
30
34
|
});
|
|
31
35
|
|
|
32
36
|
describe("API handler passes fetcher config", () => {
|
|
@@ -1,9 +1,183 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import type { Data } from "@puckeditor/core";
|
|
4
5
|
import { RenderClient } from "@pantheon-systems/puck-css";
|
|
5
6
|
import config from "../../puck.config";
|
|
6
7
|
|
|
8
|
+
function EditIcon() {
|
|
9
|
+
return (
|
|
10
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" width={16} height={16} aria-hidden>
|
|
11
|
+
<path d="M12 20h9" />
|
|
12
|
+
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ChevronIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" width={14} height={14} aria-hidden>
|
|
20
|
+
<path d="M6 9l6 6 6-6" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function LogoutIcon() {
|
|
26
|
+
return (
|
|
27
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" width={16} height={16} aria-hidden>
|
|
28
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
29
|
+
<polyline points="16 17 21 12 16 7" />
|
|
30
|
+
<line x1="21" y1="12" x2="9" y2="12" />
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function P1EditWidget({ route }: { route: string }) {
|
|
36
|
+
const [hasToken, setHasToken] = useState(false);
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const flag = typeof localStorage !== "undefined"
|
|
41
|
+
? localStorage.getItem("p1_logged_in")
|
|
42
|
+
: null;
|
|
43
|
+
setHasToken(!!flag);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!open) return;
|
|
48
|
+
const onDoc = (e: PointerEvent) => {
|
|
49
|
+
if (!(e.target as HTMLElement).closest("[data-p1-widget]")) setOpen(false);
|
|
50
|
+
};
|
|
51
|
+
const onKey = (e: KeyboardEvent) => {
|
|
52
|
+
if (e.key === "Escape") setOpen(false);
|
|
53
|
+
};
|
|
54
|
+
document.addEventListener("pointerdown", onDoc);
|
|
55
|
+
document.addEventListener("keydown", onKey);
|
|
56
|
+
return () => {
|
|
57
|
+
document.removeEventListener("pointerdown", onDoc);
|
|
58
|
+
document.removeEventListener("keydown", onKey);
|
|
59
|
+
};
|
|
60
|
+
}, [open]);
|
|
61
|
+
|
|
62
|
+
if (!hasToken) return null;
|
|
63
|
+
|
|
64
|
+
const editHref = `/p1${route === "/" ? "" : route}`;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
data-p1-widget
|
|
69
|
+
style={{
|
|
70
|
+
position: "fixed",
|
|
71
|
+
top: 16,
|
|
72
|
+
right: 16,
|
|
73
|
+
zIndex: 99999,
|
|
74
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => setOpen((o) => !o)}
|
|
79
|
+
aria-expanded={open}
|
|
80
|
+
aria-haspopup="menu"
|
|
81
|
+
style={{
|
|
82
|
+
display: "inline-flex",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
gap: 8,
|
|
85
|
+
height: 38,
|
|
86
|
+
padding: "4px 12px 4px 12px",
|
|
87
|
+
background: "#fff",
|
|
88
|
+
border: "1px solid #e0e0e0",
|
|
89
|
+
borderRadius: 999,
|
|
90
|
+
cursor: "pointer",
|
|
91
|
+
fontSize: 14,
|
|
92
|
+
fontWeight: 500,
|
|
93
|
+
color: "#1a1a1a",
|
|
94
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.06)",
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<span style={{ fontWeight: 600 }}>P1</span>
|
|
98
|
+
<span
|
|
99
|
+
style={{
|
|
100
|
+
display: "inline-flex",
|
|
101
|
+
color: "#888",
|
|
102
|
+
transition: "transform 200ms ease",
|
|
103
|
+
transform: open ? "rotate(180deg)" : "none",
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<ChevronIcon />
|
|
107
|
+
</span>
|
|
108
|
+
</button>
|
|
109
|
+
|
|
110
|
+
{open && (
|
|
111
|
+
<div
|
|
112
|
+
role="menu"
|
|
113
|
+
style={{
|
|
114
|
+
position: "absolute",
|
|
115
|
+
top: "calc(100% + 8px)",
|
|
116
|
+
right: 0,
|
|
117
|
+
minWidth: 200,
|
|
118
|
+
background: "#fff",
|
|
119
|
+
border: "1px solid #e0e0e0",
|
|
120
|
+
borderRadius: 8,
|
|
121
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
122
|
+
padding: 6,
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
<a
|
|
126
|
+
href={editHref}
|
|
127
|
+
role="menuitem"
|
|
128
|
+
style={{
|
|
129
|
+
display: "flex",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
gap: 10,
|
|
132
|
+
width: "100%",
|
|
133
|
+
padding: "9px 10px",
|
|
134
|
+
borderRadius: 4,
|
|
135
|
+
fontSize: 14,
|
|
136
|
+
color: "#1a1a1a",
|
|
137
|
+
textDecoration: "none",
|
|
138
|
+
}}
|
|
139
|
+
onMouseEnter={(e) => { e.currentTarget.style.background = "#f5f5f5"; }}
|
|
140
|
+
onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
|
|
141
|
+
>
|
|
142
|
+
<span style={{ color: "#888", flex: "0 0 auto" }}><EditIcon /></span>
|
|
143
|
+
Edit this page
|
|
144
|
+
</a>
|
|
145
|
+
<div style={{ height: 1, background: "#e0e0e0", margin: "6px 4px" }} />
|
|
146
|
+
<button
|
|
147
|
+
role="menuitem"
|
|
148
|
+
onClick={() => {
|
|
149
|
+
localStorage.removeItem("p1_logged_in");
|
|
150
|
+
localStorage.removeItem("p1_auth_token");
|
|
151
|
+
localStorage.removeItem("css_broker_token");
|
|
152
|
+
window.location.reload();
|
|
153
|
+
}}
|
|
154
|
+
style={{
|
|
155
|
+
display: "flex",
|
|
156
|
+
alignItems: "center",
|
|
157
|
+
gap: 10,
|
|
158
|
+
width: "100%",
|
|
159
|
+
padding: "9px 10px",
|
|
160
|
+
borderRadius: 4,
|
|
161
|
+
fontSize: 14,
|
|
162
|
+
color: "#1a1a1a",
|
|
163
|
+
textDecoration: "none",
|
|
164
|
+
background: "transparent",
|
|
165
|
+
border: "none",
|
|
166
|
+
cursor: "pointer",
|
|
167
|
+
fontFamily: "inherit",
|
|
168
|
+
}}
|
|
169
|
+
onMouseEnter={(e) => { e.currentTarget.style.background = "#f5f5f5"; }}
|
|
170
|
+
onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
|
|
171
|
+
>
|
|
172
|
+
<span style={{ color: "#888", flex: "0 0 auto" }}><LogoutIcon /></span>
|
|
173
|
+
Log out
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
7
181
|
export function Client({
|
|
8
182
|
data,
|
|
9
183
|
pageMetadata,
|
|
@@ -18,6 +192,7 @@ export function Client({
|
|
|
18
192
|
return (
|
|
19
193
|
<>
|
|
20
194
|
<RenderClient config={config} data={data} />
|
|
195
|
+
{pageMetadata && <P1EditWidget route={pageMetadata.route} />}
|
|
21
196
|
{pageMetadata && (
|
|
22
197
|
<footer className="mt-16 border-t border-gray-200 py-4 text-center text-sm text-gray-500">
|
|
23
198
|
Rendered with{" "}
|
|
@@ -20,7 +20,9 @@ const initPromise = ensureInitialized({
|
|
|
20
20
|
p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
|
|
21
21
|
p1ApiKey: process.env.CSS_API_KEY,
|
|
22
22
|
p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
|
|
23
|
-
|
|
23
|
+
// Default to "main" when unset: server components (no user token) need a
|
|
24
|
+
// branch to list/read documents (e.g. the /structure routes table).
|
|
25
|
+
p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID ?? "main",
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
export async function generateMetadata({
|
|
@@ -110,7 +112,7 @@ export default async function Page({
|
|
|
110
112
|
Open the Page Editor
|
|
111
113
|
</a>
|
|
112
114
|
<a
|
|
113
|
-
href="https://
|
|
115
|
+
href={`${process.env.NEXT_PUBLIC_P1_ADMIN_DASHBOARD_URL || "https://content.pantheon.io"}/dashboard/sites`}
|
|
114
116
|
target="_blank"
|
|
115
117
|
rel="noopener noreferrer"
|
|
116
118
|
className="rounded-lg border border-gray-300 px-5 py-3 text-sm font-medium text-gray-900 hover:bg-gray-100"
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createNextConfig,
|
|
9
9
|
useP1Editor,
|
|
10
10
|
useP1Plugins,
|
|
11
|
+
useP1Auth,
|
|
11
12
|
wrapConfigForEditorPreview,
|
|
12
13
|
P1QueryProvider,
|
|
13
14
|
editorPathHref,
|
|
@@ -19,8 +20,40 @@ import type { ContentRole } from "@pantheon-systems/puck-css";
|
|
|
19
20
|
import "@pantheon-systems/puck-css/styles.css";
|
|
20
21
|
import "@pantheon-systems/puck-css/pds/styles.css";
|
|
21
22
|
|
|
23
|
+
import { P1Lockup } from "../../../components/p1-lockup";
|
|
22
24
|
import config from "../../../puck.config";
|
|
23
25
|
|
|
26
|
+
const DEFAULT_PAGE_DATA = {
|
|
27
|
+
root: { props: { title: "New page" } },
|
|
28
|
+
content: [],
|
|
29
|
+
zones: {},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DEFAULT_ROOT_PAGE_DATA = {
|
|
33
|
+
root: { props: { title: "Welcome | P1 site" } },
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "P1WelcomeBlock",
|
|
37
|
+
props: {
|
|
38
|
+
id: "seed-welcome",
|
|
39
|
+
heading: "Welcome to your new Pantheon P1 Site.",
|
|
40
|
+
description: "You just created this new site from Pantheon P1 starter kit, congrats! You'll need a Pantheon P1 user account to edit it and create new pages.",
|
|
41
|
+
ctaLabel: "Sign-in to P1",
|
|
42
|
+
ctaHref: "/p1",
|
|
43
|
+
footnote: "Visit [P1 documentation](https://docs.pantheon.io) for more information.",
|
|
44
|
+
loggedInHeading: "Welcome to your new Pantheon P1 Site.",
|
|
45
|
+
loggedInDescription: "You just created this new site from Pantheon P1 starter kit, congrats! Start editing this page or visit the P1 dashboard to manage your site.",
|
|
46
|
+
loggedInCtaLabel: "Edit this page with P1 Visual Editor",
|
|
47
|
+
loggedInCtaHref: "/p1",
|
|
48
|
+
loggedInSecondaryLabel: "Go to P1 Dashboard",
|
|
49
|
+
loggedInFootnote: "Visit [P1 documentation](https://docs.pantheon.io) for more information.",
|
|
50
|
+
showLogo: true,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
zones: {},
|
|
55
|
+
};
|
|
56
|
+
|
|
24
57
|
let p1Config: ReturnType<typeof createNextConfig> | null = null;
|
|
25
58
|
let p1ConfigError: string | null = null;
|
|
26
59
|
|
|
@@ -34,6 +67,41 @@ const editorConfig = wrapConfigForEditorPreview(config);
|
|
|
34
67
|
|
|
35
68
|
const ROLES: ContentRole[] = ['admin', 'editor', 'junior-editor'];
|
|
36
69
|
|
|
70
|
+
function P1SignInPage() {
|
|
71
|
+
const { login, isLoading, error } = useP1Auth();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="w-full max-w-[620px] mx-auto flex flex-col items-center text-center px-8 py-16 font-['Inter',system-ui,sans-serif] text-[#1a1a2e] min-h-screen justify-center">
|
|
75
|
+
<P1Lockup />
|
|
76
|
+
|
|
77
|
+
<h1 className="text-[2.5rem] leading-[1.08] font-semibold m-0 mb-3" style={{ fontSize: '2.5rem' }}>
|
|
78
|
+
Your Collaborative Website Management Workspace.
|
|
79
|
+
</h1>
|
|
80
|
+
<p className="text-base leading-6 text-[#5a5a6e] max-w-[54ch] m-0">
|
|
81
|
+
Log in to your Pantheon P1 account to edit your P1 powered website.
|
|
82
|
+
If you don't have yet a Pantheon P1 account, contact us{" "}
|
|
83
|
+
<a href="https://pantheon.io/contact-us" className="text-blue-600 underline">here</a>.
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
<div className="flex gap-3 mt-8 justify-center">
|
|
87
|
+
<button
|
|
88
|
+
className="inline-flex items-center justify-center h-12 px-6 gap-2 rounded-full border border-[#1a1a2e] bg-[#1a1a2e] text-white font-['Inter',system-ui,sans-serif] text-lg font-medium leading-none whitespace-nowrap cursor-pointer transition-colors duration-200 hover:bg-[#2d2d44] hover:border-[#2d2d44] focus-visible:outline focus-visible:outline-1 focus-visible:outline-blue-600 focus-visible:outline-offset-1 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
89
|
+
onClick={() => void login()}
|
|
90
|
+
disabled={isLoading}
|
|
91
|
+
>
|
|
92
|
+
{isLoading ? "Signing in..." : "Continue"}
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{error && (
|
|
97
|
+
<p className="mt-4 text-sm text-red-600 bg-red-50 px-3 py-2 rounded-md">
|
|
98
|
+
{error}
|
|
99
|
+
</p>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
37
105
|
export function EditorClientWrapper({ path }: { path: string }) {
|
|
38
106
|
const [userRole, setUserRole] = useState<ContentRole>('editor');
|
|
39
107
|
const lastGoodStateRef = React.useRef<{ puckKey: string; puckProps: any } | null>(null);
|
|
@@ -58,11 +126,13 @@ export function EditorClientWrapper({ path }: { path: string }) {
|
|
|
58
126
|
<P1NextRouterProvider>
|
|
59
127
|
<P1App
|
|
60
128
|
config={{ ...p1Config, userRole }}
|
|
61
|
-
|
|
129
|
+
loginFallback={<P1SignInPage />}
|
|
62
130
|
>
|
|
63
131
|
<EditorContent path={path} lastGoodStateRef={lastGoodStateRef} />
|
|
64
132
|
</P1App>
|
|
65
|
-
|
|
133
|
+
{process.env.NEXT_PUBLIC_ENABLE_ROLE_SWITCHER === 'true' && (
|
|
134
|
+
<RoleSwitcher currentRole={userRole} onRoleChange={setUserRole} />
|
|
135
|
+
)}
|
|
66
136
|
</P1NextRouterProvider>
|
|
67
137
|
</P1QueryProvider>
|
|
68
138
|
);
|
|
@@ -126,8 +196,20 @@ function EditorContent({
|
|
|
126
196
|
lastGoodStateRef: React.MutableRefObject<{ puckKey: string; puckProps: any } | null>;
|
|
127
197
|
}) {
|
|
128
198
|
const router = useRouter();
|
|
199
|
+
const { getToken } = useP1Auth();
|
|
129
200
|
const p1Plugins = useP1Plugins(path, config);
|
|
130
201
|
|
|
202
|
+
const [redirecting, setRedirecting] = React.useState(false);
|
|
203
|
+
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
const returnTo = localStorage.getItem("p1_return_to");
|
|
206
|
+
if (returnTo) {
|
|
207
|
+
localStorage.removeItem("p1_return_to");
|
|
208
|
+
setRedirecting(true);
|
|
209
|
+
router.push(returnTo);
|
|
210
|
+
}
|
|
211
|
+
}, [router]);
|
|
212
|
+
|
|
131
213
|
const handleDocumentSelect = useCallback(
|
|
132
214
|
(docPath: string) => {
|
|
133
215
|
router.push(editorPathHref(docPath));
|
|
@@ -135,10 +217,27 @@ function EditorContent({
|
|
|
135
217
|
[router],
|
|
136
218
|
);
|
|
137
219
|
|
|
220
|
+
const handleDocumentNotFound = useCallback(
|
|
221
|
+
async (docPath: string, _error: Error) => {
|
|
222
|
+
const initialData = docPath === "/" ? DEFAULT_ROOT_PAGE_DATA : DEFAULT_PAGE_DATA;
|
|
223
|
+
const token = await getToken();
|
|
224
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
225
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
226
|
+
const res = await fetch("/p1/api/structure/page", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers,
|
|
229
|
+
body: JSON.stringify({ path: docPath, initialData }),
|
|
230
|
+
});
|
|
231
|
+
return res.ok;
|
|
232
|
+
},
|
|
233
|
+
[getToken],
|
|
234
|
+
);
|
|
235
|
+
|
|
138
236
|
const { loading, error, puckKey, puckProps } = useP1Editor({
|
|
139
237
|
documentPath: path,
|
|
140
238
|
puckConfig: editorConfig,
|
|
141
239
|
additionalPlugins: p1Plugins,
|
|
240
|
+
onDocumentNotFound: handleDocumentNotFound,
|
|
142
241
|
pluginOptions: {
|
|
143
242
|
onDocumentSelect: handleDocumentSelect,
|
|
144
243
|
selectedDocumentPath: path,
|
|
@@ -163,6 +262,14 @@ function EditorContent({
|
|
|
163
262
|
}
|
|
164
263
|
}, [loading, error, puckKey, puckProps]);
|
|
165
264
|
|
|
265
|
+
if (redirecting) {
|
|
266
|
+
return (
|
|
267
|
+
<div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
|
|
268
|
+
Redirecting...
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
166
273
|
// Show full loading screen only on first load (no previous state)
|
|
167
274
|
if (loading && !lastGoodStateRef.current) {
|
|
168
275
|
return (
|
|
@@ -2,16 +2,16 @@ import "@puckeditor/core/puck.css";
|
|
|
2
2
|
import { createP1Pages } from "@pantheon-systems/p1-next-sdk/server";
|
|
3
3
|
import config from "../../../puck.config";
|
|
4
4
|
import { EditorClientWrapper } from "./editor-client";
|
|
5
|
-
import { RenderClientWrapper } from "./render-client";
|
|
6
5
|
|
|
7
6
|
const pages = createP1Pages({
|
|
8
7
|
config,
|
|
9
8
|
p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
|
|
10
9
|
p1ApiKey: process.env.CSS_API_KEY,
|
|
11
10
|
p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
|
|
12
|
-
|
|
11
|
+
// Default to "main" when unset: server components (no user token) need a
|
|
12
|
+
// branch to list/read documents (e.g. the /p1/structure routes table).
|
|
13
|
+
p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID ?? "main",
|
|
13
14
|
EditorClient: EditorClientWrapper,
|
|
14
|
-
RenderClient: RenderClientWrapper,
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
export default pages.Page;
|
|
@@ -3,7 +3,7 @@ import { createP1AuthHandler } from "@pantheon-systems/p1-next-sdk/server";
|
|
|
3
3
|
const handler = createP1AuthHandler({
|
|
4
4
|
p1ApiKey: process.env.CSS_API_KEY,
|
|
5
5
|
p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
|
|
6
|
-
prompt:
|
|
6
|
+
prompt: "select_account",
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
export const { POST } = handler;
|
package/template/app/page.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WelcomeBlockRender } from "../components/puck/welcome-block-render";
|
|
2
2
|
import {
|
|
3
|
-
listRoutes,
|
|
4
3
|
ensureInitialized,
|
|
5
4
|
getPage,
|
|
6
5
|
listRouteTemplateKeysFromDatabase,
|
|
@@ -12,13 +11,14 @@ import {
|
|
|
12
11
|
import type { Metadata } from "next";
|
|
13
12
|
import { REMOTE_DATASOURCE_FETCHERS } from "../lib/remote-datasource-fetchers";
|
|
14
13
|
import { Client } from "./[...puckPath]/client";
|
|
15
|
-
import { CollectionNav } from "./collection-nav";
|
|
16
14
|
|
|
17
15
|
const initPromise = ensureInitialized({
|
|
18
16
|
p1BaseUrl: process.env.NEXT_PUBLIC_CSS_BASE_URL,
|
|
19
17
|
p1ApiKey: process.env.CSS_API_KEY,
|
|
20
18
|
p1SiteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
|
|
21
|
-
|
|
19
|
+
// Default to "main" when unset: server components (no user token) need a
|
|
20
|
+
// branch to list/read documents (e.g. the /structure routes table).
|
|
21
|
+
p1BranchId: process.env.NEXT_PUBLIC_CSS_BRANCH_ID ?? "main",
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
export async function generateMetadata(): Promise<Metadata> {
|
|
@@ -76,63 +76,21 @@ export default async function HomePage() {
|
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const routes = await listRoutes();
|
|
80
|
-
|
|
81
|
-
const staticPages = routes.filter((r) => r.kind === "static");
|
|
82
|
-
const templates = routes.filter((r) => r.kind === "template");
|
|
83
|
-
|
|
84
79
|
return (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
>
|
|
100
|
-
Open the Page Editor
|
|
101
|
-
</Link>
|
|
102
|
-
<a
|
|
103
|
-
href="https://staging.content.pantheon.io/dashboard/sites"
|
|
104
|
-
target="_blank"
|
|
105
|
-
rel="noopener noreferrer"
|
|
106
|
-
className="rounded-lg border border-gray-300 px-5 py-3 text-sm font-medium text-gray-900 hover:bg-gray-100"
|
|
107
|
-
>
|
|
108
|
-
P1 Dashboard →
|
|
109
|
-
</a>
|
|
110
|
-
</nav>
|
|
111
|
-
|
|
112
|
-
{(staticPages.length > 0 || templates.length > 0) && (
|
|
113
|
-
<div className="mt-10 text-left">
|
|
114
|
-
<h2 className="text-lg font-semibold text-gray-900 mb-4">Pages</h2>
|
|
115
|
-
<ul className="space-y-3">
|
|
116
|
-
{staticPages.map((route) => (
|
|
117
|
-
<li key={route.path}>
|
|
118
|
-
<Link
|
|
119
|
-
href={route.path}
|
|
120
|
-
className="text-sm font-mono text-blue-600 hover:underline"
|
|
121
|
-
>
|
|
122
|
-
{route.path}
|
|
123
|
-
</Link>
|
|
124
|
-
</li>
|
|
125
|
-
))}
|
|
126
|
-
{templates.map((route) => (
|
|
127
|
-
<li key={route.path}>
|
|
128
|
-
<CollectionNav templatePath={route.path} />
|
|
129
|
-
</li>
|
|
130
|
-
))}
|
|
131
|
-
</ul>
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
</div>
|
|
135
|
-
</main>
|
|
80
|
+
<WelcomeBlockRender
|
|
81
|
+
heading="Welcome to your new Pantheon P1 Site."
|
|
82
|
+
description="You just created this new site from Pantheon P1 starter kit, congrats! You'll need a Pantheon P1 user account to edit it and create new pages."
|
|
83
|
+
ctaLabel="Sign-in to P1"
|
|
84
|
+
ctaHref="/p1"
|
|
85
|
+
footnote="Visit [P1 documentation](https://docs.pantheon.io) for more information."
|
|
86
|
+
loggedInHeading="Welcome to your new Pantheon P1 Site."
|
|
87
|
+
loggedInDescription="You just created this new site from Pantheon P1 starter kit, congrats! Start editing this page or visit the P1 dashboard to manage your site."
|
|
88
|
+
loggedInCtaLabel="Edit this page with P1 Visual Editor"
|
|
89
|
+
loggedInCtaHref="/p1"
|
|
90
|
+
loggedInSecondaryLabel="Go to P1 Dashboard"
|
|
91
|
+
loggedInFootnote="Visit [P1 documentation](https://docs.pantheon.io) for more information."
|
|
92
|
+
showLogo={true}
|
|
93
|
+
/>
|
|
136
94
|
);
|
|
137
95
|
}
|
|
138
96
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export function PantheonMark() {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
className="h-7 w-auto block"
|
|
7
|
+
viewBox="0 0 105 230"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
>
|
|
11
|
+
<polygon fill="#FFDC28" points="17.8,13.4 35.7,56.4 13,56.4 20.5,75.4 66.6,75.4" />
|
|
12
|
+
<polygon fill="#FFDC28" points="78.4,170.1 70.8,151.2 60.3,151.2 38.3,97.9 28.9,97.9 50.8,151.2 24,151.2 73.6,213.2 55.7,170.1" />
|
|
13
|
+
<path fill="#23232D" d="M84.6,94.3c0.6,0,1.9-0.7,1.9-7.3s-1.3-7.3-1.9-7.3H52.8l6,14.6C58.8,94.3,84.6,94.3,84.6,94.3z" />
|
|
14
|
+
<path fill="#23232D" d="M66.1,111.8l21.3,0c0.6,0,1.9-0.7,1.9-7.3s-1.3-7.3-1.9-7.3l-27.4,0L66.1,111.8z" />
|
|
15
|
+
<path fill="#23232D" d="M84.6,132.2H55.9l6,14.6h22.7c0.6,0,1.9-0.7,1.9-7.3S85.1,132.2,84.6,132.2z" />
|
|
16
|
+
<path fill="#23232D" d="M87.4,114.7H48.7l6,14.6h32.7c0.6,0,1.9-0.7,1.9-7.3S88,114.7,87.4,114.7L87.4,114.7z" />
|
|
17
|
+
<path fill="#23232D" d="M31.1,111.9l-6.8-17.6h15.9l7.4,17.6l15.2-0.1L49.5,79.7H16.5c-2.5,0-3.9,0-5.1,3.8c-1.4,4.5-1.5,13.1-1.5,29.7s0.2,25.2,1.5,29.7c1.1,3.8,2.5,3.8,5.1,3.8l29,0l-14.4-35L31.1,111.9L31.1,111.9z" />
|
|
18
|
+
<path fill="#23232D" d="M91.7,143h-1.2v-0.8h3.4v0.8h-1.2v3.5h-1L91.7,143L91.7,143z M96.3,146.5l-1.1-3.3v3.3h-0.9v-4.3h1.3l1.1,3.3l1.1-3.3H99v4.3h-0.9v-3.3l-1,3.3H96.3L96.3,146.5z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function P1Lockup() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="inline-flex items-center gap-2.5 mb-6 px-2 -mx-2 rounded-full">
|
|
26
|
+
<PantheonMark />
|
|
27
|
+
<span className="w-px h-5 bg-gray-200" />
|
|
28
|
+
<span className="font-['Inter_Tight','Inter',system-ui,sans-serif] font-semibold text-[19px] tracking-[0.01em] text-[#1a1a2e] leading-none">
|
|
29
|
+
P1
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { P1Lockup } from "../p1-lockup";
|
|
5
|
+
|
|
6
|
+
export interface WelcomeBlockRenderProps {
|
|
7
|
+
heading?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
ctaLabel?: string;
|
|
10
|
+
ctaHref?: string;
|
|
11
|
+
footnote?: string;
|
|
12
|
+
loggedInHeading?: string;
|
|
13
|
+
loggedInDescription?: string;
|
|
14
|
+
loggedInCtaLabel?: string;
|
|
15
|
+
loggedInCtaHref?: string;
|
|
16
|
+
loggedInSecondaryLabel?: string;
|
|
17
|
+
loggedInFootnote?: string;
|
|
18
|
+
showLogo?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const LOGGED_IN_DEFAULTS = {
|
|
22
|
+
heading: "Welcome to your new Pantheon P1 Site.",
|
|
23
|
+
description:
|
|
24
|
+
"You just created this new site from Pantheon P1 starter kit, congrats! Start editing this page or visit the P1 dashboard to manage your site.",
|
|
25
|
+
ctaLabel: "Edit this page with P1 Visual Editor",
|
|
26
|
+
ctaHref: "/p1",
|
|
27
|
+
secondaryLabel: "Go to P1 Dashboard",
|
|
28
|
+
secondaryHref: process.env.NEXT_PUBLIC_P1_ADMIN_DASHBOARD_URL || "https://content.pantheon.io",
|
|
29
|
+
footnote:
|
|
30
|
+
"Visit [P1 documentation](https://docs.pantheon.io) for more information.",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const BTN_PRIMARY =
|
|
34
|
+
"inline-flex items-center justify-center h-12 px-6 gap-2 rounded-full border border-[#1a1a2e] bg-[#1a1a2e] text-white font-['Inter',system-ui,sans-serif] text-lg font-medium leading-none whitespace-nowrap cursor-pointer transition-colors duration-200 hover:bg-[#2d2d44] hover:border-[#2d2d44] focus-visible:outline focus-visible:outline-1 focus-visible:outline-blue-600 focus-visible:outline-offset-1 disabled:opacity-40 disabled:cursor-not-allowed";
|
|
35
|
+
|
|
36
|
+
const BTN_SECONDARY =
|
|
37
|
+
"inline-flex items-center justify-center h-12 px-6 gap-2 rounded-full border border-[#d0d0d8] bg-transparent text-[#1a1a2e] font-['Inter',system-ui,sans-serif] text-lg font-medium leading-none whitespace-nowrap cursor-pointer transition-colors duration-200 hover:bg-[rgba(26,26,46,0.06)] focus-visible:outline focus-visible:outline-1 focus-visible:outline-blue-600 focus-visible:outline-offset-1";
|
|
38
|
+
|
|
39
|
+
export function WelcomeBlockRender(props: WelcomeBlockRenderProps) {
|
|
40
|
+
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setIsLoggedIn(!!localStorage.getItem("p1_logged_in"));
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const activeHeading = isLoggedIn
|
|
47
|
+
? (props.loggedInHeading || LOGGED_IN_DEFAULTS.heading)
|
|
48
|
+
: props.heading;
|
|
49
|
+
const activeDescription = isLoggedIn
|
|
50
|
+
? (props.loggedInDescription || LOGGED_IN_DEFAULTS.description)
|
|
51
|
+
: props.description;
|
|
52
|
+
const activeCtaLabel = isLoggedIn
|
|
53
|
+
? (props.loggedInCtaLabel || LOGGED_IN_DEFAULTS.ctaLabel)
|
|
54
|
+
: props.ctaLabel;
|
|
55
|
+
const activeCtaHref = isLoggedIn
|
|
56
|
+
? (props.loggedInCtaHref || LOGGED_IN_DEFAULTS.ctaHref)
|
|
57
|
+
: props.ctaHref;
|
|
58
|
+
const activeFootnote = isLoggedIn
|
|
59
|
+
? (props.loggedInFootnote || LOGGED_IN_DEFAULTS.footnote)
|
|
60
|
+
: props.footnote;
|
|
61
|
+
const secondaryLabel = isLoggedIn
|
|
62
|
+
? (props.loggedInSecondaryLabel || LOGGED_IN_DEFAULTS.secondaryLabel)
|
|
63
|
+
: null;
|
|
64
|
+
|
|
65
|
+
const footnoteHtml = (activeFootnote ?? "").replace(
|
|
66
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
67
|
+
'<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-600 underline">$1</a>',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="w-full max-w-[620px] mx-auto flex flex-col items-center text-center px-8 py-16 font-['Inter',system-ui,sans-serif] text-[#1a1a2e] min-h-screen justify-center">
|
|
72
|
+
{props.showLogo !== false && <P1Lockup />}
|
|
73
|
+
<h1 className="text-[2.5rem] leading-[1.08] font-semibold m-0 mb-4">
|
|
74
|
+
{activeHeading}
|
|
75
|
+
</h1>
|
|
76
|
+
<p className="text-base leading-6 text-[#5a5a6e] max-w-[54ch] m-0">
|
|
77
|
+
{activeDescription}
|
|
78
|
+
</p>
|
|
79
|
+
<div className="flex gap-3 mt-8 justify-center">
|
|
80
|
+
<button
|
|
81
|
+
className={BTN_PRIMARY}
|
|
82
|
+
onClick={() => {
|
|
83
|
+
if (!isLoggedIn) {
|
|
84
|
+
localStorage.setItem("p1_return_to", window.location.pathname);
|
|
85
|
+
}
|
|
86
|
+
window.location.href = activeCtaHref || "/";
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{activeCtaLabel}
|
|
90
|
+
</button>
|
|
91
|
+
{secondaryLabel && (
|
|
92
|
+
<button
|
|
93
|
+
className={BTN_SECONDARY}
|
|
94
|
+
onClick={() => {
|
|
95
|
+
window.open(LOGGED_IN_DEFAULTS.secondaryHref, "_blank", "noopener,noreferrer");
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
{secondaryLabel}
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
{activeFootnote && (
|
|
103
|
+
<p
|
|
104
|
+
className="mt-12 text-sm text-[#5a5a6e]"
|
|
105
|
+
dangerouslySetInnerHTML={{ __html: footnoteHtml }}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WelcomeBlockRender } from "./welcome-block-render";
|
|
2
|
+
|
|
3
|
+
export const welcomeBlock = {
|
|
4
|
+
label: "P1 Welcome",
|
|
5
|
+
fields: {
|
|
6
|
+
heading: { type: "text" as const, label: "Heading (signed out)" },
|
|
7
|
+
description: { type: "textarea" as const, label: "Description (signed out)" },
|
|
8
|
+
ctaLabel: { type: "text" as const, label: "Primary button label (signed out)" },
|
|
9
|
+
ctaHref: { type: "text" as const, label: "Primary button link (signed out)" },
|
|
10
|
+
footnote: { type: "textarea" as const, label: "Footnote (signed out)" },
|
|
11
|
+
loggedInHeading: { type: "text" as const, label: "Heading (signed in)" },
|
|
12
|
+
loggedInDescription: { type: "textarea" as const, label: "Description (signed in)" },
|
|
13
|
+
loggedInCtaLabel: { type: "text" as const, label: "Primary button label (signed in)" },
|
|
14
|
+
loggedInCtaHref: { type: "text" as const, label: "Primary button link (signed in)" },
|
|
15
|
+
loggedInSecondaryLabel: { type: "text" as const, label: "Secondary button label (signed in)" },
|
|
16
|
+
loggedInFootnote: { type: "textarea" as const, label: "Footnote (signed in)" },
|
|
17
|
+
showLogo: {
|
|
18
|
+
type: "radio" as const,
|
|
19
|
+
label: "Show P1 logo",
|
|
20
|
+
options: [
|
|
21
|
+
{ label: "Yes", value: true },
|
|
22
|
+
{ label: "No", value: false },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultProps: {
|
|
27
|
+
heading: "Welcome to your new Pantheon P1 Site.",
|
|
28
|
+
description:
|
|
29
|
+
"You just created this new site from Pantheon P1 starter kit, congrats! You'll need a Pantheon P1 user account to edit it and create new pages.",
|
|
30
|
+
ctaLabel: "Sign-in to P1",
|
|
31
|
+
ctaHref: "/p1",
|
|
32
|
+
footnote:
|
|
33
|
+
"Visit [P1 documentation](https://docs.pantheon.io) for more information.",
|
|
34
|
+
loggedInHeading: "Welcome to your new Pantheon P1 Site.",
|
|
35
|
+
loggedInDescription:
|
|
36
|
+
"You just created this new site from Pantheon P1 starter kit, congrats! Start editing this page or visit the P1 dashboard to manage your site.",
|
|
37
|
+
loggedInCtaLabel: "Edit this page with P1 Visual Editor",
|
|
38
|
+
loggedInCtaHref: "/p1",
|
|
39
|
+
loggedInSecondaryLabel: "Go to P1 Dashboard",
|
|
40
|
+
loggedInFootnote:
|
|
41
|
+
"Visit [P1 documentation](https://docs.pantheon.io) for more information.",
|
|
42
|
+
showLogo: true,
|
|
43
|
+
},
|
|
44
|
+
render: WelcomeBlockRender,
|
|
45
|
+
};
|
package/template/package.json
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@pantheon-systems/cpub-react-sdk": "^5.2.1",
|
|
14
|
-
"@pantheon-systems/
|
|
15
|
-
"@pantheon-systems/
|
|
16
|
-
"@pantheon-systems/puck-css": "^0.4.4",
|
|
14
|
+
"@pantheon-systems/p1-next-sdk": "^0.5.0",
|
|
15
|
+
"@pantheon-systems/puck-css": "^0.5.0",
|
|
17
16
|
"@puckeditor/core": "^0.21.1",
|
|
18
17
|
"@tailwindcss/postcss": "^4.2.2",
|
|
19
18
|
"classnames": "^2.5.1",
|
package/template/puck.config.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { paragraphBlock } from "./components/puck/paragraph-block";
|
|
|
10
10
|
import { quoteBlock } from "./components/puck/quote-block";
|
|
11
11
|
import { puckRoot } from "./components/puck/root";
|
|
12
12
|
import { spacerBlock } from "./components/puck/spacer-block";
|
|
13
|
+
import { welcomeBlock } from "./components/puck/welcome-block";
|
|
13
14
|
|
|
14
15
|
export const config = {
|
|
15
16
|
categories: {
|
|
@@ -33,6 +34,10 @@ export const config = {
|
|
|
33
34
|
title: "Actions",
|
|
34
35
|
components: ["ButtonBlock"],
|
|
35
36
|
},
|
|
37
|
+
pages: {
|
|
38
|
+
title: "Page Sections",
|
|
39
|
+
components: ["P1WelcomeBlock"],
|
|
40
|
+
},
|
|
36
41
|
},
|
|
37
42
|
root: puckRoot,
|
|
38
43
|
components: {
|
|
@@ -45,6 +50,7 @@ export const config = {
|
|
|
45
50
|
DividerBlock: dividerBlock,
|
|
46
51
|
SpacerBlock: spacerBlock,
|
|
47
52
|
ButtonBlock: buttonBlock,
|
|
53
|
+
P1WelcomeBlock: welcomeBlock,
|
|
48
54
|
},
|
|
49
55
|
} as Config;
|
|
50
56
|
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
}
|