add nextjs style routes
This commit is contained in:
705
app.schema.json
705
app.schema.json
@@ -1,705 +0,0 @@
|
|||||||
{
|
|
||||||
"/": {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/products/:handle": {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/collections/:handle": {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/search": {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/about": {
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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} />;
|
|
||||||
}
|
|
||||||
6
app/about/editor/page.tsx
Normal file
6
app/about/editor/page.tsx
Normal 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
214
app/about/page.json
Normal 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
6
app/about/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import PageRender from "@/components/page-render";
|
||||||
|
import page from "./page.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <PageRender page={page} />;
|
||||||
|
}
|
||||||
6
app/collections/[handle]/editor/page.tsx
Normal file
6
app/collections/[handle]/editor/page.tsx
Normal 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]" />;
|
||||||
|
}
|
||||||
195
app/collections/[handle]/page.json
Normal file
195
app/collections/[handle]/page.json
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
app/collections/[handle]/page.tsx
Normal file
6
app/collections/[handle]/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import PageRender from "@/components/page-render";
|
||||||
|
import page from "./page.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <PageRender page={page} />;
|
||||||
|
}
|
||||||
@@ -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
6
app/editor/page.tsx
Normal 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
302
app/page.json
Normal 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
6
app/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import PageRender from "@/components/page-render";
|
||||||
|
import page from "./page.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <PageRender page={page} />;
|
||||||
|
}
|
||||||
6
app/products/[handle]/editor/page.tsx
Normal file
6
app/products/[handle]/editor/page.tsx
Normal 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]" />;
|
||||||
|
}
|
||||||
129
app/products/[handle]/page.json
Normal file
129
app/products/[handle]/page.json
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
app/products/[handle]/page.tsx
Normal file
6
app/products/[handle]/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import PageRender from "@/components/page-render";
|
||||||
|
import page from "./page.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <PageRender page={page} />;
|
||||||
|
}
|
||||||
6
app/search/editor/page.tsx
Normal file
6
app/search/editor/page.tsx
Normal 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
162
app/search/page.json
Normal 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
6
app/search/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import PageRender from "@/components/page-render";
|
||||||
|
import page from "./page.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <PageRender page={page} />;
|
||||||
|
}
|
||||||
92
components/page-editor.tsx
Normal file
92
components/page-editor.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { usePathname, 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 globals from "@/app.globals.json";
|
||||||
|
import type { PageData } from "@/components/page-render";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared editor for a route. In an editor route's `page.tsx`, import the
|
||||||
|
* sibling public route's `page.json` and pass it through with the route key:
|
||||||
|
*
|
||||||
|
* import page from "../page.json";
|
||||||
|
* export default () => <PageEditor page={page} routeKey="/app/products/[handle]" />;
|
||||||
|
*
|
||||||
|
* `routeKey` is the Next.js app-directory path of the route (e.g.
|
||||||
|
* "/app/products/[handle]"); the host resolves it to `<routeKey>/page.json`
|
||||||
|
* to know which file to save. The concrete public path and params are derived
|
||||||
|
* separately from the live URL.
|
||||||
|
*/
|
||||||
|
export default function PageEditor({
|
||||||
|
page,
|
||||||
|
routeKey,
|
||||||
|
}: {
|
||||||
|
page: PageData;
|
||||||
|
routeKey: string;
|
||||||
|
}) {
|
||||||
|
const pathname = usePathname() ?? "/editor";
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
// Each editor route is a `.../editor` child of its public route; the
|
||||||
|
// published path drops that trailing segment.
|
||||||
|
const path = pathname.replace(/\/editor$/, "") || "/";
|
||||||
|
const routeParams: Record<string, string> = {};
|
||||||
|
for (const [key, value] of Object.entries(params ?? {})) {
|
||||||
|
if (typeof value === "string") routeParams[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = { root: page.root, content: page.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) => {
|
||||||
|
// The page we save is always the public route — i.e. the live path with
|
||||||
|
// the trailing `/editor` segment stripped, never the `.../editor` URL.
|
||||||
|
const resolved = {
|
||||||
|
key: routeKey,
|
||||||
|
params: routeParams,
|
||||||
|
...(route ?? {}),
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
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 as any}
|
||||||
|
route={{ key: routeKey, path, params: routeParams }}
|
||||||
|
plugins={plugins}
|
||||||
|
onPublish={handlePublish}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
components/page-render.tsx
Normal file
22
components/page-render.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Render } from "@reacteditor/core";
|
||||||
|
import { appConfig } from "@/editor.config";
|
||||||
|
import globals from "@/app.globals.json";
|
||||||
|
|
||||||
|
export type PageData = {
|
||||||
|
root?: unknown;
|
||||||
|
content?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared renderer for a route. Drop a `page.json` next to a route's
|
||||||
|
* `page.tsx`, import it, and hand it here:
|
||||||
|
*
|
||||||
|
* import page from "./page.json";
|
||||||
|
* export default () => <PageRender page={page} />;
|
||||||
|
*/
|
||||||
|
export default function PageRender({ page }: { page: PageData }) {
|
||||||
|
const data = { root: page.root, content: page.content, globals };
|
||||||
|
return <Render config={appConfig as any} data={data as any} />;
|
||||||
|
}
|
||||||
@@ -3,15 +3,13 @@
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the last segment of the current catch-all slug route, e.g. the
|
* Returns the dynamic handle of the current route, e.g. the `cool-shirt` in
|
||||||
* `cool-shirt` in `/products/cool-shirt`. Components use this to derive the
|
* `/products/cool-shirt` (or its `/products/cool-shirt/editor` editor view).
|
||||||
* resource they should load from the Next.js route segments directly.
|
* Both the public and editor routes share the same `[handle]` segment, so the
|
||||||
|
* value is identical in either context.
|
||||||
*/
|
*/
|
||||||
export function useRouteSegment(): string | undefined {
|
export function useRouteSegment(): string | undefined {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const segments = Array.isArray(params?.slug) ? (params.slug as string[]) : [];
|
const handle = params?.handle;
|
||||||
// Editor routes are served under `/editor/*`; ignore that prefix so the
|
return typeof handle === "string" ? handle : undefined;
|
||||||
// resolved segment matches the public route.
|
|
||||||
const routeSegments = segments[0] === "editor" ? segments.slice(1) : segments;
|
|
||||||
return routeSegments[routeSegments.length - 1];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import schema from "@/app.schema.json";
|
|
||||||
|
|
||||||
export type ResolvedRoute = {
|
|
||||||
key: string;
|
|
||||||
path: string;
|
|
||||||
params: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ROUTE_KEYS = Object.keys(schema as Record<string, unknown>);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches a concrete path's segments against a single express-style pattern
|
|
||||||
* (e.g. `/products/:handle`). Returns the captured params, or null on mismatch.
|
|
||||||
*/
|
|
||||||
const matchPattern = (
|
|
||||||
pattern: string,
|
|
||||||
segments: string[],
|
|
||||||
): Record<string, string> | null => {
|
|
||||||
const patternSegments = pattern === "/" ? [] : pattern.slice(1).split("/");
|
|
||||||
if (patternSegments.length !== segments.length) return null;
|
|
||||||
|
|
||||||
const params: Record<string, string> = {};
|
|
||||||
for (let i = 0; i < patternSegments.length; i++) {
|
|
||||||
const part = patternSegments[i];
|
|
||||||
if (part.startsWith(":")) {
|
|
||||||
params[part.slice(1)] = segments[i];
|
|
||||||
} else if (part !== segments[i]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves catch-all slug segments to a route key defined in the schema,
|
|
||||||
* supporting any express-style pattern (`/products/:handle`, `/blog/:slug`,
|
|
||||||
* etc.). Static segments are preferred over dynamic ones when both match.
|
|
||||||
*/
|
|
||||||
const resolveRoute = (segments: string[] = []): ResolvedRoute => {
|
|
||||||
// Editor routes live under `/editor/*`; the `editor` prefix is not part of
|
|
||||||
// the schema route keys, so strip it before matching.
|
|
||||||
const routeSegments =
|
|
||||||
segments[0] === "editor" ? segments.slice(1) : segments;
|
|
||||||
const path =
|
|
||||||
routeSegments.length === 0 ? "/" : `/${routeSegments.join("/")}`;
|
|
||||||
|
|
||||||
let best: ResolvedRoute | null = null;
|
|
||||||
let bestDynamicCount = Infinity;
|
|
||||||
|
|
||||||
for (const key of ROUTE_KEYS) {
|
|
||||||
const params = matchPattern(key, routeSegments);
|
|
||||||
if (!params) continue;
|
|
||||||
|
|
||||||
const dynamicCount = Object.keys(params).length;
|
|
||||||
if (dynamicCount < bestDynamicCount) {
|
|
||||||
best = { key, path, params };
|
|
||||||
bestDynamicCount = dynamicCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return best ?? { key: path, path, params: {} };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default resolveRoute;
|
|
||||||
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