add nextjs style routes

This commit is contained in:
Rami Bitar
2026-06-06 13:46:56 -04:00
parent ef550e9b55
commit aa58af410d
23 changed files with 1183 additions and 855 deletions

View File

@@ -1,17 +0,0 @@
"use client";
import { useParams } from "next/navigation";
import { Render } from "@reacteditor/core";
import { appConfig } from "@/editor.config";
import resolveRoute from "@/lib/resolve-route";
import schema from "@/app.schema.json";
import globals from "@/app.globals.json";
export default function Page() {
const params = useParams();
const segments = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
const { key } = resolveRoute(segments);
const { root, content } = (schema as any)[key] ?? {};
const data = { root, content, globals };
return <Render config={appConfig as any} data={data} />;
}

View File

@@ -0,0 +1,6 @@
import PageEditor from "@/components/page-editor";
import page from "../page.json";
export default function EditorPage() {
return <PageEditor page={page} routeKey="/app/about" />;
}

214
app/about/page.json Normal file
View File

@@ -0,0 +1,214 @@
{
"root": {
"props": {
"title": "About — Pulse",
"headerFont": "Archivo Black",
"headerFontWeight": "400",
"bodyFont": "Inter",
"primaryColor": "#111111",
"secondaryColor": "#707072",
"accentColor": "#f5f5f5",
"bgColor": "#ffffff",
"fgColor": "#111111",
"mutedColor": "#f5f5f5",
"radius": "xl",
"buttonRadius": "full",
"shadow": "none",
"maxWidth": "2xl"
}
},
"content": [
{
"type": "navigation",
"props": {
"id": "nav-about",
"brand": "PULSE",
"links": [
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Recover",
"href": "/collections/recover"
},
{
"label": "Sale",
"href": "/collections/sale"
},
{
"label": "About",
"href": "/about"
}
],
"showSearch": "yes",
"showAccount": "yes",
"showCart": "yes",
"sticky": "yes",
"tone": "default"
}
},
{
"type": "hero",
"props": {
"id": "hero-about",
"tagline": "The lab",
"heading": "We don't make activewear.",
"subheading": "Pulse started in a converted warehouse with three coaches, two athletes, and a whiteboard full of things they wished worked better. Eight years later, the whiteboard is still there. So is the standard.",
"buttons": [
{
"label": "Shop the kit",
"href": "/search",
"variant": "primary"
}
],
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/hero-about.png",
"align": "left",
"height": "lg",
"tone": "light"
}
},
{
"type": "features",
"props": {
"id": "features-about",
"tagline": "What we believe",
"heading": "Three rules. No exceptions.",
"subheading": "",
"columns": "3",
"items": [
{
"title": "Engineered",
"body": "Every fabric, every seam, every fit decision starts with athletes on a track. Lab work comes after, if at all."
},
{
"title": "Field tested",
"body": "Twelve weeks of training. Two race blocks. One full season. Nothing leaves the lab without that on the record."
},
{
"title": "Guaranteed",
"body": "If it fails before its time, we replace it. If it fails on race day, we apologize and replace it. That's the deal."
}
]
}
},
{
"type": "hero",
"props": {
"id": "cover-about-performance",
"tagline": "Used up is the goal",
"heading": "Designed to be worn out, not preserved.",
"subheading": "If you're babying your kit, we did something wrong. Bring it back when it's done — we'll send the next one.",
"buttons": [],
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/cover-performance.jpeg",
"align": "left",
"height": "lg",
"tone": "light"
}
},
{
"type": "newsletter-cta",
"props": {
"id": "newsletter-about",
"tagline": "Field notes",
"heading": "Weekly notes from the lab.",
"subheading": "Test results, training blocks, gear we cut and gear we kept. Every Monday at 5:30am.",
"buttonLabel": "Subscribe",
"endpoint": "",
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/newsletter.png",
"layout": "split"
}
},
{
"type": "footer",
"props": {
"id": "footer-about",
"brand": "PULSE",
"tagline": "Performance gear for athletes who break their kit before they break themselves.",
"columns": [
{
"title": "Shop",
"links": [
{
"label": "All gear",
"href": "/search"
},
{
"label": "New",
"href": "/collections/new"
},
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
}
]
},
{
"title": "About",
"links": [
{
"label": "Our story",
"href": "/about"
},
{
"label": "The lab",
"href": "/lab"
},
{
"label": "Athletes",
"href": "/athletes"
}
]
},
{
"title": "Help",
"links": [
{
"label": "Shipping",
"href": "/help/shipping"
},
{
"label": "Returns",
"href": "/help/returns"
},
{
"label": "Lifetime guarantee",
"href": "/help/guarantee"
},
{
"label": "Contact",
"href": "/contact"
}
]
}
],
"social": [
{
"label": "Instagram",
"href": "#"
},
{
"label": "Strava",
"href": "#"
},
{
"label": "YouTube",
"href": "#"
}
],
"showNewsletter": "no",
"newsletterHeading": "",
"newsletterEndpoint": "",
"copyright": "© 2026 Pulse. Built to be replaced when used up."
}
}
]
}

6
app/about/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import PageRender from "@/components/page-render";
import page from "./page.json";
export default function Page() {
return <PageRender page={page} />;
}

View File

@@ -0,0 +1,6 @@
import PageEditor from "@/components/page-editor";
import page from "../page.json";
export default function EditorPage() {
return <PageEditor page={page} routeKey="/app/collections/[handle]" />;
}

View File

@@ -0,0 +1,195 @@
{
"root": {
"props": {
"title": "Collection — Pulse",
"headerFont": "Archivo Black",
"headerFontWeight": "400",
"bodyFont": "Inter",
"primaryColor": "#111111",
"secondaryColor": "#707072",
"accentColor": "#f5f5f5",
"bgColor": "#ffffff",
"fgColor": "#111111",
"mutedColor": "#f5f5f5",
"radius": "xl",
"buttonRadius": "full",
"shadow": "none",
"maxWidth": "2xl"
}
},
"content": [
{
"type": "navigation",
"props": {
"id": "nav-collection",
"brand": "PULSE",
"links": [
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Recover",
"href": "/collections/recover"
},
{
"label": "Sale",
"href": "/collections/sale"
},
{
"label": "About",
"href": "/about"
}
],
"showSearch": "yes",
"showAccount": "yes",
"showCart": "yes",
"sticky": "yes",
"tone": "default"
}
},
{
"type": "collection",
"props": {
"id": "collection-detail",
"collection": null,
"showDescription": "yes",
"showCoverImage": "yes",
"customCoverImage": "",
"columns": "4",
"limit": 24,
"defaultSort": "BEST_SELLING",
"showAvailability": "yes",
"showPriceRange": "yes",
"showProductType": "yes",
"productTypeOptions": [
{
"label": "Tops"
},
{
"label": "Shorts"
},
{
"label": "Tights"
},
{
"label": "Outerwear"
},
{
"label": "Shoes"
},
{
"label": "Accessories"
}
],
"showVendor": "no",
"vendorOptions": [],
"showTags": "no",
"tagOptions": [],
"showColor": "yes",
"colorOptions": [
{
"label": "Black",
"color": "#111111"
},
{
"label": "White",
"color": "#ffffff"
},
{
"label": "Grey",
"color": "#707072"
},
{
"label": "Volt",
"color": "#d6ff3f"
},
{
"label": "Sodium",
"color": "#fa5400"
}
],
"showStyle": "no",
"styleOptions": [],
"showSize": "yes",
"sizeOptions": [
{
"label": "XS"
},
{
"label": "S"
},
{
"label": "M"
},
{
"label": "L"
},
{
"label": "XL"
},
{
"label": "XXL"
}
],
"showMaterial": "no",
"materialOptions": [],
"metafieldFilters": []
}
},
{
"type": "footer",
"props": {
"id": "footer-collection",
"brand": "PULSE",
"tagline": "Performance gear for athletes who break their kit before they break themselves.",
"columns": [
{
"title": "Shop",
"links": [
{
"label": "All gear",
"href": "/search"
},
{
"label": "New",
"href": "/collections/new"
}
]
},
{
"title": "Help",
"links": [
{
"label": "Shipping",
"href": "/help/shipping"
},
{
"label": "Returns",
"href": "/help/returns"
}
]
}
],
"social": [
{
"label": "Instagram",
"href": "#"
},
{
"label": "Strava",
"href": "#"
}
],
"showNewsletter": "no",
"newsletterHeading": "",
"newsletterEndpoint": "",
"copyright": "© 2026 Pulse. Built to be replaced when used up."
}
}
]
}

View File

@@ -0,0 +1,6 @@
import PageRender from "@/components/page-render";
import page from "./page.json";
export default function Page() {
return <PageRender page={page} />;
}

View File

@@ -1,61 +0,0 @@
"use client";
import { useMemo } from "react";
import { useParams } from "next/navigation";
import { Editor, blocksPlugin, outlinePlugin } from "@reacteditor/core";
import createTailwindCdnPlugin from "@reacteditor/plugin-tailwind-cdn";
import { createShopifyPlugin } from "@reacteditor/plugin-shopify";
import { mediaPlugin } from "@reacteditor/plugin-media";
import { mediaAdapter } from "@/lib/adapters/media-adapter";
import { appConfig } from "@/editor.config";
import resolveRoute from "@/lib/resolve-route";
import schema from "@/app.schema.json";
import globals from "@/app.globals.json";
export default function EditorPage() {
const params = useParams();
const segments = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
const { key, path, params: routeParams } = resolveRoute(segments);
const { root, content } = (schema as any)[key] ?? {};
const data = { root, content, globals };
const plugins = useMemo(
() => [
blocksPlugin(),
outlinePlugin(),
mediaPlugin({
adapter: mediaAdapter,
}),
createTailwindCdnPlugin(),
createShopifyPlugin({
storeDomain: process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN ?? "mock.shop",
publicAccessToken:
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
}),
],
[],
);
const handlePublish = async (nextData: any, route?: any) => {
const resolved = route ?? { key };
if (typeof window !== "undefined" && window.parent !== window) {
window.parent.postMessage(
{ type: "PUBLISH", data: { data: nextData, route: resolved } },
"*",
);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
};
return (
<div className="h-screen w-screen">
<Editor
config={appConfig as any}
data={data}
route={{ key, path, params: routeParams }}
plugins={plugins}
onPublish={handlePublish}
/>
</div>
);
}

6
app/editor/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import PageEditor from "@/components/page-editor";
import page from "../page.json";
export default function EditorPage() {
return <PageEditor page={page} routeKey="/app" />;
}

302
app/page.json Normal file
View File

@@ -0,0 +1,302 @@
{
"root": {
"props": {
"title": "Pulse — Made to Move",
"headerFont": "Archivo Black",
"headerFontWeight": "400",
"bodyFont": "Inter",
"primaryColor": "#111111",
"secondaryColor": "#707072",
"accentColor": "#f5f5f5",
"bgColor": "#ffffff",
"fgColor": "#111111",
"mutedColor": "#f5f5f5",
"radius": "xl",
"buttonRadius": "full",
"shadow": "none",
"maxWidth": "2xl"
}
},
"content": [
{
"type": "navigation",
"props": {
"id": "nav-home",
"brand": "PULSE",
"links": [
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Recover",
"href": "/collections/recover"
},
{
"label": "Sale",
"href": "/collections/sale"
},
{
"label": "About",
"href": "/about"
}
],
"showSearch": "yes",
"showAccount": "yes",
"showCart": "yes",
"sticky": "yes",
"tone": "default"
}
},
{
"type": "hero",
"props": {
"id": "hero-home",
"tagline": "Spring 2026",
"heading": "Made to move.",
"subheading": "Performance gear engineered for athletes who train like they mean it. Built light. Built honest. Built for the next mile.",
"buttons": [
{
"label": "Shop the kit",
"href": "/search",
"variant": "primary"
},
{
"label": "Our story",
"href": "/about",
"variant": "secondary"
}
],
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/athletic-assortment.jpeg",
"align": "left",
"height": "lg",
"tone": "light"
}
},
{
"type": "products-carousel",
"props": {
"id": "carousel-home",
"tagline": "Just dropped",
"heading": "New arrivals",
"subheading": "Fresh kit for the season ahead.",
"limit": 12,
"slidesPerView": "4",
"ctaLabel": "Shop new",
"ctaHref": "/collections/new"
}
},
{
"type": "hero",
"props": {
"id": "cover-home-performance",
"tagline": "Performance Series",
"heading": "Engineered to outlast you.",
"subheading": "Tested on track, trail, and treadmill. Every piece earns its place.",
"buttons": [],
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/cover-performance.jpeg",
"align": "left",
"height": "lg",
"tone": "light"
}
},
{
"type": "features",
"props": {
"id": "features-home",
"tagline": "Why Pulse",
"heading": "Three rules. No exceptions.",
"subheading": "",
"columns": "3",
"items": [
{
"title": "Engineered",
"body": "Every fabric, every seam, every fit decision starts with athletes on a track. Lab work comes after."
},
{
"title": "Field tested",
"body": "Twelve weeks. Two race blocks. One full season. Nothing ships without that on the record."
},
{
"title": "Guaranteed",
"body": "If it fails before its time, we replace it. If it fails on race day, we apologize and replace it."
}
]
}
},
{
"type": "hero",
"props": {
"id": "cover-home-discipline",
"tagline": "Discipline",
"heading": "The work happens before sunrise.",
"subheading": "We make the kit. You do the work. Five-thirty isn't early — it's the only honest hour.",
"buttons": [],
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/cover-discipline.jpeg",
"align": "left",
"height": "lg",
"tone": "light"
}
},
{
"type": "collection-grid",
"props": {
"id": "collections-home",
"tagline": "Shop by sport",
"heading": "Built for what you actually do.",
"subheading": "",
"layout": "tiles",
"limit": 6
}
},
{
"type": "testimonials",
"props": {
"id": "testimonials-home",
"tagline": "From the field",
"heading": "Athletes on Pulse",
"items": [
{
"quote": "Took the shell to Chamonix and back. Still smells, still works, still cheaper than therapy.",
"author": "Mateo S.",
"role": "Sub-3 marathoner / Boulder",
"avatar": ""
},
{
"quote": "I have one drawer of running gear. The Pulse half is the half I actually wear.",
"author": "Priya N.",
"role": "Ultra runner / Portland",
"avatar": ""
},
{
"quote": "Wore the shorts for an entire 16-week block. They held. That's the whole review.",
"author": "Jonas R.",
"role": "Track coach / Berlin",
"avatar": ""
}
]
}
},
{
"type": "newsletter-cta",
"props": {
"id": "newsletter-home",
"tagline": "Field notes",
"heading": "Weekly notes from the lab.",
"subheading": "Test results, training blocks, gear we cut and gear we kept. Every Monday at 5:30am.",
"buttonLabel": "Subscribe",
"endpoint": "",
"imageUrl": "https://supabase.frontend-ai.com/storage/v1/object/public/assets/themes/pulse-nike/newsletter.png",
"layout": "split"
}
},
{
"type": "footer",
"props": {
"id": "footer-home",
"brand": "PULSE",
"tagline": "Performance gear for athletes who break their kit before they break themselves.",
"columns": [
{
"title": "Shop",
"links": [
{
"label": "All gear",
"href": "/search"
},
{
"label": "New",
"href": "/collections/new"
},
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Sale",
"href": "/collections/sale"
}
]
},
{
"title": "About",
"links": [
{
"label": "Our story",
"href": "/about"
},
{
"label": "The lab",
"href": "/lab"
},
{
"label": "Athletes",
"href": "/athletes"
}
]
},
{
"title": "Help",
"links": [
{
"label": "Shipping",
"href": "/help/shipping"
},
{
"label": "Returns",
"href": "/help/returns"
},
{
"label": "Lifetime guarantee",
"href": "/help/guarantee"
},
{
"label": "Contact",
"href": "/contact"
}
]
},
{
"title": "Legal",
"links": [
{
"label": "Terms",
"href": "/terms"
},
{
"label": "Privacy",
"href": "/privacy"
}
]
}
],
"social": [
{
"label": "Instagram",
"href": "#"
},
{
"label": "Strava",
"href": "#"
},
{
"label": "YouTube",
"href": "#"
}
],
"showNewsletter": "no",
"newsletterHeading": "",
"newsletterEndpoint": "",
"copyright": "© 2026 Pulse. Built to be replaced when used up."
}
}
]
}

6
app/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import PageRender from "@/components/page-render";
import page from "./page.json";
export default function Page() {
return <PageRender page={page} />;
}

View File

@@ -0,0 +1,6 @@
import PageEditor from "@/components/page-editor";
import page from "../page.json";
export default function EditorPage() {
return <PageEditor page={page} routeKey="/app/products/[handle]" />;
}

View File

@@ -0,0 +1,129 @@
{
"root": {
"props": {
"title": "Pulse",
"headerFont": "Archivo Black",
"headerFontWeight": "400",
"bodyFont": "Inter",
"primaryColor": "#111111",
"secondaryColor": "#707072",
"accentColor": "#f5f5f5",
"bgColor": "#ffffff",
"fgColor": "#111111",
"mutedColor": "#f5f5f5",
"radius": "xl",
"buttonRadius": "full",
"shadow": "none",
"maxWidth": "2xl"
}
},
"content": [
{
"type": "navigation",
"props": {
"id": "nav-product",
"brand": "PULSE",
"links": [
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Recover",
"href": "/collections/recover"
},
{
"label": "Sale",
"href": "/collections/sale"
},
{
"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": "Pairs well with",
"heading": "Complete the kit",
"limit": 8,
"slidesPerView": "4",
"ctaLabel": "Shop all",
"ctaHref": "/collections"
}
},
{
"type": "footer",
"props": {
"id": "footer-product",
"brand": "PULSE",
"tagline": "Performance gear for athletes who break their kit before they break themselves.",
"columns": [
{
"title": "Shop",
"links": [
{
"label": "All gear",
"href": "/search"
},
{
"label": "New",
"href": "/collections/new"
}
]
},
{
"title": "Help",
"links": [
{
"label": "Shipping",
"href": "/help/shipping"
},
{
"label": "Returns",
"href": "/help/returns"
},
{
"label": "Lifetime guarantee",
"href": "/help/guarantee"
}
]
}
],
"social": [
{
"label": "Instagram",
"href": "#"
},
{
"label": "Strava",
"href": "#"
}
],
"showNewsletter": "no",
"newsletterHeading": "",
"newsletterEndpoint": "",
"copyright": "© 2026 Pulse. Built to be replaced when used up."
}
}
]
}

View File

@@ -0,0 +1,6 @@
import PageRender from "@/components/page-render";
import page from "./page.json";
export default function Page() {
return <PageRender page={page} />;
}

View File

@@ -0,0 +1,6 @@
import PageEditor from "@/components/page-editor";
import page from "../page.json";
export default function EditorPage() {
return <PageEditor page={page} routeKey="/app/search" />;
}

162
app/search/page.json Normal file
View File

@@ -0,0 +1,162 @@
{
"root": {
"props": {
"title": "Shop — Pulse",
"headerFont": "Archivo Black",
"headerFontWeight": "400",
"bodyFont": "Inter",
"primaryColor": "#111111",
"secondaryColor": "#707072",
"accentColor": "#f5f5f5",
"bgColor": "#ffffff",
"fgColor": "#111111",
"mutedColor": "#f5f5f5",
"radius": "xl",
"buttonRadius": "full",
"shadow": "none",
"maxWidth": "2xl"
}
},
"content": [
{
"type": "navigation",
"props": {
"id": "nav-search",
"brand": "PULSE",
"links": [
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
},
{
"label": "Recover",
"href": "/collections/recover"
},
{
"label": "Sale",
"href": "/collections/sale"
},
{
"label": "About",
"href": "/about"
}
],
"showSearch": "yes",
"showAccount": "yes",
"showCart": "yes",
"sticky": "yes",
"tone": "default"
}
},
{
"type": "search-products",
"props": {
"id": "search-products",
"heading": "All gear.",
"subheading": "Filter by sport, fabric, fit, color. Or sort by what's been beaten the hardest.",
"columns": "4",
"limit": 24,
"showAvailability": "yes",
"showPriceRange": "yes",
"showProductType": "yes",
"showVendor": "no",
"showTags": "yes",
"metafieldFilters": [],
"defaultSort": "BEST_SELLING",
"productTypeOptions": [
{
"label": "Tops"
},
{
"label": "Shorts"
},
{
"label": "Tights"
},
{
"label": "Outerwear"
},
{
"label": "Shoes"
},
{
"label": "Accessories"
}
],
"vendorOptions": [],
"tagOptions": [
{
"label": "New"
},
{
"label": "Race day"
},
{
"label": "Field tested"
},
{
"label": "Sale"
}
]
}
},
{
"type": "footer",
"props": {
"id": "footer-search",
"brand": "PULSE",
"tagline": "Performance gear for athletes who break their kit before they break themselves.",
"columns": [
{
"title": "Shop",
"links": [
{
"label": "All gear",
"href": "/search"
},
{
"label": "Run",
"href": "/collections/run"
},
{
"label": "Train",
"href": "/collections/train"
}
]
},
{
"title": "Help",
"links": [
{
"label": "Shipping",
"href": "/help/shipping"
},
{
"label": "Returns",
"href": "/help/returns"
}
]
}
],
"social": [
{
"label": "Instagram",
"href": "#"
},
{
"label": "Strava",
"href": "#"
}
],
"showNewsletter": "no",
"newsletterHeading": "",
"newsletterEndpoint": "",
"copyright": "© 2026 Pulse. Built to be replaced when used up."
}
}
]
}

6
app/search/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import PageRender from "@/components/page-render";
import page from "./page.json";
export default function Page() {
return <PageRender page={page} />;
}