Initial commit

This commit is contained in:
Rami Bitar
2026-05-03 20:12:12 -04:00
commit 3a3ca1c72a
169 changed files with 22320 additions and 0 deletions

51
editor/config/icons.tsx Normal file
View File

@@ -0,0 +1,51 @@
import {
ArrowRight,
ArrowUpRight,
Check,
ChevronRight,
Download,
ExternalLink,
Play,
Rocket,
Sparkles,
Star,
Zap,
type LucideIcon,
} from "lucide-react";
export const iconMap: Record<string, LucideIcon> = {
"arrow-right": ArrowRight,
"arrow-up-right": ArrowUpRight,
check: Check,
"chevron-right": ChevronRight,
download: Download,
"external-link": ExternalLink,
play: Play,
rocket: Rocket,
sparkles: Sparkles,
star: Star,
zap: Zap,
};
export const iconOptions = [
{ label: "None", value: "none" },
{ label: "Arrow right", value: "arrow-right" },
{ label: "Arrow up right", value: "arrow-up-right" },
{ label: "Check", value: "check" },
{ label: "Chevron right", value: "chevron-right" },
{ label: "Download", value: "download" },
{ label: "External link", value: "external-link" },
{ label: "Play", value: "play" },
{ label: "Rocket", value: "rocket" },
{ label: "Sparkles", value: "sparkles" },
{ label: "Star", value: "star" },
{ label: "Zap", value: "zap" },
];
export const resolveIcon = (name: string): LucideIcon | null => {
if (!name || name === "none") return null;
return iconMap[name] ?? null;
};

88
editor/config/index.tsx Normal file
View File

@@ -0,0 +1,88 @@
import {
createFieldShopifyProduct,
createFieldShopifyCollection,
} from "@reacteditor/field-shopify";
import { navigationEditor } from "@/editor/components/navigation/navigation.editor";
import { footerEditor } from "@/editor/components/footer/footer.editor";
import { heroEditor } from "@/editor/components/hero/hero.editor";
import { bannerEditor } from "@/editor/components/landing/banner.editor";
import { createFeaturedProductEditor } from "@/editor/components/commerce/featured-product.editor";
import { createProductsGridEditor } from "@/editor/components/commerce/products-grid.editor";
import { createProductsCarouselEditor } from "@/editor/components/commerce/products-carousel.editor";
import { collectionGridEditor } from "@/editor/components/commerce/collection-grid.editor";
import { createCollectionEditor } from "@/editor/components/commerce/collection.editor";
import { createProductDetailsEditor } from "@/editor/components/commerce/product-details.editor";
import { createRecommendedProductsEditor } from "@/editor/components/commerce/recommended-products.editor";
import { featuresEditor } from "@/editor/components/features/features.editor";
import { testimonialsEditor } from "@/editor/components/testimonials/testimonials.editor";
import { imageGalleryEditor } from "@/editor/components/landing/image-gallery.editor";
import { newsletterCtaEditor } from "@/editor/components/landing/newsletter-cta.editor";
import { logosEditor } from "@/editor/components/logos/logos.editor";
import { ctaEditor } from "@/editor/components/cta/cta.editor";
import { faqEditor } from "@/editor/components/faq/faq.editor";
import Root from "./root";
import type { UserConfig } from "./types";
import { initialData } from "./initial-data";
export type CreateConfigOptions = {
domain: string;
token?: string | null;
};
export function createConfig({ domain, token }: CreateConfigOptions): UserConfig {
const productField = createFieldShopifyProduct({
storeDomain: domain,
storefrontAccessToken: token ?? undefined,
}) as any;
const collectionField = createFieldShopifyCollection({
storeDomain: domain,
storefrontAccessToken: token ?? undefined,
}) as any;
return {
root: Root,
categories: {
navigation: { title: "Navigation" },
hero: { title: "Hero & Banners" },
commerce: { title: "Commerce" },
content: { title: "Content" },
footer: { title: "Footer" },
},
components: {
navigation: navigationEditor,
hero: heroEditor,
banner: bannerEditor,
"featured-product": createFeaturedProductEditor({ productField }),
"products-grid": createProductsGridEditor({ collectionField }),
"products-carousel": createProductsCarouselEditor({ collectionField }),
"collection-grid": collectionGridEditor,
collection: createCollectionEditor({ collectionField }),
"product-details": createProductDetailsEditor({ productField }),
"recommended-products": createRecommendedProductsEditor({ productField }),
features: featuresEditor,
testimonials: testimonialsEditor,
"image-gallery": imageGalleryEditor,
"newsletter-cta": newsletterCtaEditor,
logos: logosEditor,
cta: ctaEditor,
faq: faqEditor,
footer: footerEditor,
} as any,
};
}
function toBase64(s: string): string {
if (typeof btoa === "function") return btoa(unescape(encodeURIComponent(s)));
return Buffer.from(s, "utf8").toString("base64");
}
export const componentKey = toBase64(
`commerce-redesign-${JSON.stringify({ initialData })}`,
);
export default createConfig;

View File

@@ -0,0 +1,206 @@
import { UserData } from "./types";
export const getInitialData = (path: string): Partial<UserData> =>
initialData[path] ?? { content: [], root: { props: { title: "Untitled" } } };
export const initialData: Record<string, UserData> = {
"/": {
root: {
props: {
title: "Maison — Considered essentials",
headerFont: "Inter",
bodyFont: "Inter",
},
},
content: [
{
type: "navigation",
props: {
id: "nav-home",
brand: "Maison",
links: [
{ label: "Shop", href: "/collections" },
{ label: "Lookbook", href: "/lookbook" },
{ label: "Journal", href: "/journal" },
{ label: "About", href: "/about" },
],
cta: { label: "", href: "" },
showSearch: "yes",
showCart: "yes",
sticky: "yes",
tone: "default",
bannerText: "",
bannerTone: "accent",
},
},
{
type: "hero",
props: {
id: "hero-home",
tagline: "Spring 2026",
heading: "Made for the way you move",
subheading:
"A considered wardrobe of essentials, cut from natural fibers and designed to last.",
primaryCta: { label: "Shop the collection", href: "/collections" },
secondaryCta: { label: "Our story", href: "/about" },
imageUrl:
"https://images.unsplash.com/photo-1490481651871-ab68de25d43d?auto=format&fit=crop&w=2400&q=80",
align: "left",
height: "lg",
tone: "dark",
},
},
{
type: "products-carousel",
props: {
id: "carousel-home",
tagline: "New",
heading: "Just dropped",
subheading: "Fresh additions to the lineup.",
limit: 12,
slidesPerView: "4",
ctaLabel: "Shop new",
ctaHref: "/collections/new",
},
},
{
type: "featured-product",
props: {
id: "featured-home",
handle: "",
tagline: "Featured",
ctaLabel: "Add to bag",
align: "left",
tone: "muted",
},
},
{
type: "collection-grid",
props: {
id: "collections-home",
tagline: "Shop by collection",
heading: "Curated edits",
subheading: "Bundles built around the way you actually live.",
layout: "tiles",
limit: 6,
},
},
{
type: "features",
props: {
id: "features-home",
tagline: "Why us",
heading: "Built with intention",
subheading: "A small set of values that shape every piece we make.",
columns: "3",
items: [
{
title: "Natural fibers",
body: "Linen, organic cotton, and merino — sourced from mills with traceable supply chains.",
},
{
title: "Small batches",
body: "Made in considered quantities so nothing goes to waste.",
},
{
title: "Built to last",
body: "Reinforced seams and finishes that age into something better.",
},
],
},
},
{
type: "testimonials",
props: {
id: "testimonials-home",
tagline: "Reviews",
heading: "What our customers say",
items: [
{
quote:
"I've been wearing the same linen shirt for two summers now and it's somehow gotten better with every wash.",
author: "Mara K.",
role: "Berlin",
avatar:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=200&q=80",
},
{
quote:
"Considered cuts, neutral palette, real fabric. Exactly what I want when I'm getting dressed in the dark.",
author: "Theo R.",
role: "Brooklyn",
avatar:
"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=200&q=80",
},
],
},
},
{
type: "newsletter-cta",
props: {
id: "newsletter-home",
tagline: "Stay in the loop",
heading: "Letters from the studio",
subheading:
"New collections, mill stories, and the occasional invitation. Twice a month.",
buttonLabel: "Subscribe",
endpoint: "",
imageUrl:
"https://images.unsplash.com/photo-1469334031218-e382a71b716b?auto=format&fit=crop&w=1800&q=80",
layout: "split",
},
},
{
type: "footer",
props: {
id: "footer-home",
brand: "Maison",
tagline:
"Considered essentials, made in small batches and built to last beyond the season.",
columns: [
{
title: "Shop",
links: [
{ label: "All", href: "/collections" },
{ label: "New", href: "/collections/new" },
{ label: "Best sellers", href: "/collections/best" },
],
},
{
title: "About",
links: [
{ label: "Our story", href: "/about" },
{ label: "Materials", href: "/materials" },
{ label: "Journal", href: "/journal" },
],
},
{
title: "Help",
links: [
{ label: "Shipping", href: "/help/shipping" },
{ label: "Returns", href: "/help/returns" },
{ label: "Contact", href: "/contact" },
],
},
{
title: "Legal",
links: [
{ label: "Terms", href: "/terms" },
{ label: "Privacy", href: "/privacy" },
],
},
],
social: [
{ label: "Instagram", href: "#" },
{ label: "Pinterest", href: "#" },
{ label: "TikTok", href: "#" },
],
showNewsletter: "no",
newsletterHeading: "Stay in touch",
newsletterEndpoint: "",
copyright: "© 2026 Maison. All rights reserved.",
},
},
],
},
};

22
editor/config/options.ts Normal file
View File

@@ -0,0 +1,22 @@
export const spacingOptions = [
{ label: "8px", value: "8px" },
{ label: "16px", value: "16px" },
{ label: "24px", value: "24px" },
{ label: "32px", value: "32px" },
{ label: "40px", value: "40px" },
{ label: "48px", value: "48px" },
{ label: "56px", value: "56px" },
{ label: "64px", value: "64px" },
{ label: "72px", value: "72px" },
{ label: "80px", value: "80px" },
{ label: "88px", value: "88px" },
{ label: "96px", value: "96px" },
{ label: "104px", value: "104px" },
{ label: "112px", value: "112px" },
{ label: "120px", value: "120px" },
{ label: "128px", value: "128px" },
{ label: "136px", value: "136px" },
{ label: "144px", value: "144px" },
{ label: "152px", value: "152px" },
{ label: "160px", value: "160px" },
];

112
editor/config/root.tsx Normal file
View File

@@ -0,0 +1,112 @@
import { DefaultRootProps, RootConfig } from "@reacteditor/core";
import { createFieldGoogleFonts } from "@reacteditor/field-google-fonts";
import { ThemeProvider, ThemeProps } from "@/editor/theme/ThemeProvider";
export type RootProps = DefaultRootProps &
ThemeProps & {
description?: string;
ogImage?: string;
};
const headerFontField = createFieldGoogleFonts() as any;
const bodyFontField = createFieldGoogleFonts() as any;
export const Root: RootConfig<{
props: RootProps;
fields: {
userField: { type: "userField"; option: boolean };
};
}> = {
defaultProps: {
title: "Untitled",
headerFont: "Inter",
bodyFont: "Inter",
// Hex defaults so the color picker reads them and any non-picker
// input (typed hex, AI-set value, etc.) is round-trip compatible.
primaryColor: "#0a0a0a",
accentColor: "#f5f5f5",
bgColor: "#ffffff",
fgColor: "#0a0a0a",
mutedColor: "#f5f5f5",
roundedness: "md",
shadowLevel: "sm",
maxWidth: "xl",
},
fields: {
title: { label: "Page title", type: "text" },
description: { label: "Description", type: "textarea" },
ogImage: { label: "OG image URL", type: "text" },
headerFont: { label: "Header font", ...headerFontField },
bodyFont: { label: "Body font", ...bodyFontField },
primaryColor: { label: "Primary color", type: "color", placeholder: "#0a0a0a" },
accentColor: { label: "Accent color", type: "color", placeholder: "#f5f5f5" },
bgColor: { label: "Background color", type: "color", placeholder: "#ffffff" },
fgColor: { label: "Foreground color", type: "color", placeholder: "#0a0a0a" },
mutedColor: { label: "Muted color", type: "color", placeholder: "#f5f5f5" },
roundedness: {
label: "Roundedness",
type: "select",
options: [
{ label: "None", value: "none" },
{ label: "Small", value: "sm" },
{ label: "Medium", value: "md" },
{ label: "Large", value: "lg" },
{ label: "Extra large", value: "xl" },
{ label: "Full (pill)", value: "full" },
],
},
shadowLevel: {
label: "Shadow level",
type: "select",
options: [
{ label: "None", value: "none" },
{ label: "Small", value: "sm" },
{ label: "Medium", value: "md" },
{ label: "Large", value: "lg" },
{ label: "Extra large", value: "xl" },
],
},
maxWidth: {
label: "Max width",
type: "select",
options: [
{ label: "Small", value: "sm" },
{ label: "Medium", value: "md" },
{ label: "Large", value: "lg" },
{ label: "Extra large", value: "xl" },
{ label: "2X large", value: "2xl" },
{ label: "Full bleed", value: "full" },
],
},
},
render: ({
children,
headerFont,
bodyFont,
primaryColor,
accentColor,
bgColor,
fgColor,
mutedColor,
roundedness,
shadowLevel,
}) => {
return (
<ThemeProvider
headerFont={headerFont}
bodyFont={bodyFont}
primaryColor={primaryColor}
accentColor={accentColor}
bgColor={bgColor}
fgColor={fgColor}
mutedColor={mutedColor}
roundedness={roundedness}
shadowLevel={shadowLevel}
>
{children}
</ThemeProvider>
);
},
};
export default Root;

61
editor/config/types.ts Normal file
View File

@@ -0,0 +1,61 @@
import { Config, Data } from "@reacteditor/core";
import { HeroProps } from "@/editor/components/hero/hero";
import { LogosProps } from "@/editor/components/logos/logos";
import { FeaturesProps } from "@/editor/components/features/features";
import { TestimonialsProps } from "@/editor/components/testimonials/testimonials";
import { CTAProps } from "@/editor/components/cta/cta";
import { FAQProps } from "@/editor/components/faq/faq";
import { NavigationProps } from "@/editor/components/navigation/navigation";
import { FooterProps } from "@/editor/components/footer/footer";
import { ProductsGridProps } from "@/editor/components/commerce/products-grid";
import { ProductsCarouselProps } from "@/editor/components/commerce/products-carousel";
import { CollectionGridProps } from "@/editor/components/commerce/collection-grid";
import { CollectionProps } from "@/editor/components/commerce/collection";
import { ProductDetailsProps } from "@/editor/components/commerce/product-details";
import { RecommendedProductsProps } from "@/editor/components/commerce/recommended-products";
import { FeaturedProductProps } from "@/editor/components/commerce/featured-product";
import { BannerProps } from "@/editor/components/landing/banner";
import { NewsletterCtaProps } from "@/editor/components/landing/newsletter-cta";
import { ImageGalleryProps } from "@/editor/components/landing/image-gallery";
import { RootProps } from "./root";
export type { RootProps } from "./root";
export type Components = {
navigation: NavigationProps;
hero: HeroProps;
banner: BannerProps;
"featured-product": FeaturedProductProps;
"products-grid": ProductsGridProps;
"products-carousel": ProductsCarouselProps;
"collection-grid": CollectionGridProps;
collection: CollectionProps;
"product-details": ProductDetailsProps;
"recommended-products": RecommendedProductsProps;
features: FeaturesProps;
testimonials: TestimonialsProps;
"image-gallery": ImageGalleryProps;
"newsletter-cta": NewsletterCtaProps;
logos: LogosProps;
cta: CTAProps;
faq: FAQProps;
footer: FooterProps;
};
export type UserConfig = Config<{
components: Components;
root: RootProps;
categories: ["navigation", "hero", "commerce", "content", "footer"];
fields: {
userField: {
type: "userField";
option: boolean;
};
};
}>;
export type UserData = Data<Components, RootProps>;