added resolve schema
This commit is contained in:
@@ -1,4 +1,92 @@
|
|||||||
{
|
{
|
||||||
|
"/products/*": {
|
||||||
|
"root": {
|
||||||
|
"props": {
|
||||||
|
"title": "Default product",
|
||||||
|
"headerFont": "Playfair Display",
|
||||||
|
"bodyFont": "Inter",
|
||||||
|
"primaryColor": "#0a0a0a",
|
||||||
|
"accentColor": "#f5f5f5",
|
||||||
|
"bgColor": "#ffffff",
|
||||||
|
"fgColor": "#0a0a0a",
|
||||||
|
"mutedColor": "#f5f5f5",
|
||||||
|
"roundedness": "md",
|
||||||
|
"shadowLevel": "sm",
|
||||||
|
"maxWidth": "xl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "navigation",
|
||||||
|
"props": {
|
||||||
|
"id": "nav-product",
|
||||||
|
"brand": "Maison",
|
||||||
|
"links": [
|
||||||
|
{ "label": "Shop", "href": "/collections" },
|
||||||
|
{ "label": "Lookbook", "href": "/lookbook" },
|
||||||
|
{ "label": "Journal", "href": "/journal" },
|
||||||
|
{ "label": "About", "href": "/about" }
|
||||||
|
],
|
||||||
|
"showSearch": "yes",
|
||||||
|
"showAccount": "yes",
|
||||||
|
"showCart": "yes",
|
||||||
|
"sticky": "yes",
|
||||||
|
"tone": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "product-details",
|
||||||
|
"props": {
|
||||||
|
"id": "product-details",
|
||||||
|
"product": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "products-carousel",
|
||||||
|
"props": {
|
||||||
|
"id": "carousel-related",
|
||||||
|
"tagline": "You might also like",
|
||||||
|
"heading": "Related pieces",
|
||||||
|
"limit": 8,
|
||||||
|
"slidesPerView": "4",
|
||||||
|
"ctaLabel": "Shop all",
|
||||||
|
"ctaHref": "/collections"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "footer",
|
||||||
|
"props": {
|
||||||
|
"id": "footer-product",
|
||||||
|
"brand": "Maison",
|
||||||
|
"tagline": "Considered essentials, made in small batches.",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"title": "Shop",
|
||||||
|
"links": [
|
||||||
|
{ "label": "All", "href": "/collections" },
|
||||||
|
{ "label": "New", "href": "/collections/new" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Help",
|
||||||
|
"links": [
|
||||||
|
{ "label": "Shipping", "href": "/help/shipping" },
|
||||||
|
{ "label": "Returns", "href": "/help/returns" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"social": [
|
||||||
|
{ "label": "Instagram", "href": "#" },
|
||||||
|
{ "label": "Pinterest", "href": "#" }
|
||||||
|
],
|
||||||
|
"showNewsletter": "no",
|
||||||
|
"newsletterHeading": "Stay in touch",
|
||||||
|
"newsletterEndpoint": "",
|
||||||
|
"copyright": "© 2026 Maison. All rights reserved."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"/": {
|
"/": {
|
||||||
"root": {
|
"root": {
|
||||||
"props": {
|
"props": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { readSchema } from "@/lib/schema.server";
|
import { readSchema } from "@/lib/schema.server";
|
||||||
|
import { resolveSchemaEntry } from "@/lib/schema-resolver";
|
||||||
import RenderClient from "@/components/RenderClient";
|
import RenderClient from "@/components/RenderClient";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -19,8 +20,8 @@ export default async function Page({
|
|||||||
const lookup = route === "/" ? "/" : route.replace(/\/$/, "");
|
const lookup = route === "/" ? "/" : route.replace(/\/$/, "");
|
||||||
|
|
||||||
const schema = await readSchema();
|
const schema = await readSchema();
|
||||||
const data = schema[lookup];
|
const resolved = resolveSchemaEntry(schema, lookup);
|
||||||
if (!data) return notFound();
|
if (!resolved) return notFound();
|
||||||
|
|
||||||
return <RenderClient data={data} route={lookup} />;
|
return <RenderClient data={resolved.data} route={lookup} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import "@/editor/vendor/plugin-ai.css";
|
|||||||
import { createConfig } from "@/editor/config";
|
import { createConfig } from "@/editor/config";
|
||||||
import { ShopifyProvider } from "@/editor/contexts/shopify-context";
|
import { ShopifyProvider } from "@/editor/contexts/shopify-context";
|
||||||
import { Button } from "@/editor/components/ui/button";
|
import { Button } from "@/editor/components/ui/button";
|
||||||
|
import { resolveSchemaEntry, templateKeyForRoute } from "@/lib/schema-resolver";
|
||||||
|
|
||||||
type Schema = Record<string, { root: any; content: any[] }>;
|
type Schema = Record<string, { root: any; content: any[] }>;
|
||||||
|
|
||||||
@@ -61,8 +62,17 @@ export default function EditorClient({
|
|||||||
const [schema, setSchema] = useState<Schema>(initialSchema);
|
const [schema, setSchema] = useState<Schema>(initialSchema);
|
||||||
const [currentPath, setCurrentPath] = useState<string>(initialPath);
|
const [currentPath, setCurrentPath] = useState<string>(initialPath);
|
||||||
|
|
||||||
|
const editKey = useMemo(
|
||||||
|
() => templateKeyForRoute(currentPath) ?? currentPath,
|
||||||
|
[currentPath],
|
||||||
|
);
|
||||||
|
|
||||||
const data = useMemo(
|
const data = useMemo(
|
||||||
() => schema[currentPath] ?? { root: { props: {} }, content: [] },
|
() =>
|
||||||
|
resolveSchemaEntry(schema, currentPath)?.data ?? {
|
||||||
|
root: { props: {} },
|
||||||
|
content: [],
|
||||||
|
},
|
||||||
[schema, currentPath],
|
[schema, currentPath],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -125,12 +135,12 @@ export default function EditorClient({
|
|||||||
router.push(`/edit${next === "/" ? "" : next}`);
|
router.push(`/edit${next === "/" ? "" : next}`);
|
||||||
}}
|
}}
|
||||||
onPublish={async (next) => {
|
onPublish={async (next) => {
|
||||||
await persist(currentPath, next);
|
await persist(editKey, next);
|
||||||
}}
|
}}
|
||||||
iframe={{ enabled: true }}
|
iframe={{ enabled: true }}
|
||||||
overrides={{
|
overrides={{
|
||||||
headerActions: () => (
|
headerActions: () => (
|
||||||
<SaveButton currentPath={currentPath} onSave={persist} />
|
<SaveButton currentPath={editKey} onSave={persist} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ export const footerEditor: ComponentConfig<FooterProps> = {
|
|||||||
label: "Footer",
|
label: "Footer",
|
||||||
icon: <LayoutGrid size={16} />,
|
icon: <LayoutGrid size={16} />,
|
||||||
category: "footer",
|
category: "footer",
|
||||||
|
global: true,
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
brand: "Maison",
|
brand: "Maison",
|
||||||
tagline:
|
tagline:
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ export const navigationEditor: ComponentConfig<NavigationProps> = {
|
|||||||
label: "Navigation",
|
label: "Navigation",
|
||||||
icon: <MenuIcon size={16} />,
|
icon: <MenuIcon size={16} />,
|
||||||
category: "navigation",
|
category: "navigation",
|
||||||
|
global: true,
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
brand: "Maison",
|
brand: "Maison",
|
||||||
links: [
|
links: [
|
||||||
|
|||||||
58
lib/schema-resolver.ts
Normal file
58
lib/schema-resolver.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Schema } from "@/lib/schema.server";
|
||||||
|
|
||||||
|
export type ResolvedEntry = {
|
||||||
|
key: string;
|
||||||
|
data: { root: any; content: any[] };
|
||||||
|
params: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TEMPLATE_PATTERNS: { key: string; prefix: string; param: string }[] = [
|
||||||
|
{ key: "/products/*", prefix: "/products/", param: "handle" },
|
||||||
|
{ key: "/collections/*", prefix: "/collections/", param: "handle" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function resolveSchemaEntry(
|
||||||
|
schema: Schema,
|
||||||
|
route: string,
|
||||||
|
): ResolvedEntry | null {
|
||||||
|
if (schema[route]) {
|
||||||
|
return { key: route, data: schema[route], params: {} };
|
||||||
|
}
|
||||||
|
for (const { key, prefix, param } of TEMPLATE_PATTERNS) {
|
||||||
|
if (route.startsWith(prefix) && route.length > prefix.length && schema[key]) {
|
||||||
|
const value = route.slice(prefix.length);
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
data: applyParams(schema[key], { [param]: value }),
|
||||||
|
params: { [param]: value },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function templateKeyForRoute(route: string): string | null {
|
||||||
|
for (const { key, prefix } of TEMPLATE_PATTERNS) {
|
||||||
|
if (route.startsWith(prefix) && route.length > prefix.length) return key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyParams(
|
||||||
|
data: { root: any; content: any[] },
|
||||||
|
params: Record<string, string>,
|
||||||
|
): { root: any; content: any[] } {
|
||||||
|
const handle = params.handle;
|
||||||
|
if (!handle) return data;
|
||||||
|
const content = data.content.map((block) => {
|
||||||
|
const props = block?.props ?? {};
|
||||||
|
if ("product" in props && (props.product == null || !props.product?.handle)) {
|
||||||
|
return { ...block, props: { ...props, product: { handle } } };
|
||||||
|
}
|
||||||
|
if ("collection" in props && (props.collection == null || !props.collection?.handle)) {
|
||||||
|
return { ...block, props: { ...props, collection: { handle } } };
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
});
|
||||||
|
return { ...data, content };
|
||||||
|
}
|
||||||
1
tsconfig.tsbuildinfo
Normal file
1
tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user