Files
react-editor-shopify/components/EditorClient.tsx
2026-05-03 16:22:24 -04:00

150 lines
3.8 KiB
TypeScript

"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import {
Editor,
blocksPlugin,
outlinePlugin,
useGetEditor,
type Route,
} from "@reacteditor/core";
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
import { aiPlugin } from "@reacteditor/plugin-ai";
import "@reacteditor/core/dist/index.css";
import "@/editor/vendor/plugin-ai.css";
import { createConfig } from "@/editor/config";
import { ShopifyProvider } from "@/editor/contexts/shopify-context";
import { Button } from "@/editor/components/ui/button";
import { resolveSchemaEntry, templateKeyForRoute } from "@/lib/schema-resolver";
type Schema = Record<string, { root: any; content: any[] }>;
const SHOPIFY_DOMAIN =
process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop";
const STOREFRONT_TOKEN =
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN ?? "";
function SaveButton({
currentPath,
onSave,
}: {
currentPath: string;
onSave: (route: string, data: any) => Promise<void>;
}) {
const getEditor = useGetEditor();
const [saving, setSaving] = useState(false);
return (
<Button
onClick={async () => {
if (saving) return;
setSaving(true);
const data = (getEditor() as any)?.appState?.data;
if (data) await onSave(currentPath, data);
await new Promise((r) => setTimeout(r, 1000));
setSaving(false);
}}
disabled={saving}
>
{saving ? "Saving…" : "Save"}
</Button>
);
}
export default function EditorClient({
initialSchema,
initialPath,
}: {
initialSchema: Schema;
initialPath: string;
}) {
const router = useRouter();
const [schema, setSchema] = useState<Schema>(initialSchema);
const [currentPath, setCurrentPath] = useState<string>(initialPath);
const editKey = useMemo(
() => templateKeyForRoute(currentPath) ?? currentPath,
[currentPath],
);
const data = useMemo(
() =>
resolveSchemaEntry(schema, currentPath)?.data ?? {
root: { props: {} },
content: [],
},
[schema, currentPath],
);
const routes: Route[] = useMemo(
() =>
Object.entries(schema).map(([path, page]) => ({
path,
title:
(page?.root as any)?.props?.title ??
(path === "/" ? "Home" : path),
})),
[schema],
);
const config = useMemo(
() =>
createConfig({
domain: SHOPIFY_DOMAIN,
token: STOREFRONT_TOKEN || null,
}),
[],
);
const persist = useCallback(async (route: string, next: any) => {
setSchema((s) => ({ ...s, [route]: next }));
if (typeof window !== "undefined" && window.parent !== window) {
window.parent.postMessage(
{ type: "PUBLISH", path: route, data: next },
"*",
);
}
}, []);
const plugins = useMemo(
() => [
createTailwindCdnPlugin(),
aiPlugin({
api: "/api/chat",
attachments: true,
body: { route: currentPath },
}),
blocksPlugin(),
outlinePlugin(),
],
[currentPath],
);
return (
<ShopifyProvider domain={SHOPIFY_DOMAIN} token={STOREFRONT_TOKEN}>
<Editor
key={currentPath}
config={config as any}
data={data as any}
plugins={plugins}
headerPath={currentPath}
routes={routes}
currentPath={currentPath}
onRouteChange={(next) => {
setCurrentPath(next);
router.push(next === "/" ? "/edit" : `${next}/edit`);
}}
onPublish={async (next) => {
await persist(editKey, next);
}}
iframe={{ enabled: true }}
overrides={{
headerActions: () => (
<SaveButton currentPath={editKey} onSave={persist} />
),
}}
/>
</ShopifyProvider>
);
}