From 346fcb470e20d52d3b9aa7c869973be3b85213e6 Mon Sep 17 00:00:00 2001 From: Rami Bitar Date: Tue, 5 May 2026 14:04:51 -0400 Subject: [PATCH] update git commit and push --- components/landing/newsletter-cta.editor.tsx | 73 +++++++++++++++----- components/landing/newsletter-cta.tsx | 64 ++++++++++++++++- components/navigation/navigation.editor.tsx | 4 ++ components/navigation/navigation.tsx | 14 +++- 4 files changed, 135 insertions(+), 20 deletions(-) diff --git a/components/landing/newsletter-cta.editor.tsx b/components/landing/newsletter-cta.editor.tsx index 9e2d63c..00a0ee9 100644 --- a/components/landing/newsletter-cta.editor.tsx +++ b/components/landing/newsletter-cta.editor.tsx @@ -1,9 +1,34 @@ -import { ComponentConfig } from "@reacteditor/core"; +import { ComponentConfig, Fields } from "@reacteditor/core"; import { imageField } from "@reacteditor/plugin-media/field"; import { Mail } from "lucide-react"; import { NewsletterCta, type NewsletterCtaProps } from "@/components/landing/newsletter-cta"; import { frontendAiMediaAdapter } from "@/services/media-adapter"; +const baseFields: Fields = { + tagline: { label: "Tagline", type: "text", contentEditable: true }, + heading: { label: "Heading", type: "text", contentEditable: true }, + subheading: { label: "Subheading", type: "textarea", contentEditable: true }, + buttonLabel: { label: "Button label", type: "text", contentEditable: true }, + emailProvider: { + label: "Email provider", + type: "select", + options: [ + { label: "None (custom endpoint)", value: "none" }, + { label: "Mailchimp", value: "mailchimp" }, + { label: "Klaviyo", value: "klaviyo" }, + ], + }, + imageUrl: { label: "Image", ...imageField({ adapter: frontendAiMediaAdapter }) }, + layout: { + label: "Layout", + type: "radio", + options: [ + { label: "Split (image + form)", value: "split" }, + { label: "Stacked (centered)", value: "stacked" }, + ], + }, +}; + export const newsletterCtaEditor: ComponentConfig = { label: "Newsletter", icon: , @@ -14,26 +39,42 @@ export const newsletterCtaEditor: ComponentConfig = { subheading: "New collections, mill stories, and the occasional invitation to in-person events. Twice a month.", buttonLabel: "Subscribe", + emailProvider: "none", endpoint: "", + mailchimpApiKey: "", + mailchimpServerPrefix: "", + mailchimpAudienceId: "", + klaviyoCompanyId: "", + klaviyoListId: "", imageUrl: "https://images.unsplash.com/photo-1469334031218-e382a71b716b?auto=format&fit=crop&w=1800&q=80", layout: "split", }, - fields: { - tagline: { label: "Tagline", type: "text", contentEditable: true }, - heading: { label: "Heading", type: "text", contentEditable: true }, - subheading: { label: "Subheading", type: "textarea", contentEditable: true }, - buttonLabel: { label: "Button label", type: "text", contentEditable: true }, - endpoint: { label: "Submit endpoint", type: "text" }, - imageUrl: { label: "Image", ...imageField({ adapter: frontendAiMediaAdapter }) }, - layout: { - label: "Layout", - type: "radio", - options: [ - { label: "Split (image + form)", value: "split" }, - { label: "Stacked (centered)", value: "stacked" }, - ], - }, + fields: baseFields, + resolveFields: (data) => { + const provider = data.props.emailProvider; + if (provider === "mailchimp") { + return { + ...baseFields, + mailchimpApiKey: { label: "Mailchimp API key", type: "text" }, + mailchimpServerPrefix: { + label: "Server prefix (e.g. us21)", + type: "text", + }, + mailchimpAudienceId: { label: "Audience ID", type: "text" }, + }; + } + if (provider === "klaviyo") { + return { + ...baseFields, + klaviyoCompanyId: { label: "Company ID (public API key)", type: "text" }, + klaviyoListId: { label: "List ID", type: "text" }, + }; + } + return { + ...baseFields, + endpoint: { label: "Submit endpoint", type: "text" }, + }; }, render: (props) => , }; diff --git a/components/landing/newsletter-cta.tsx b/components/landing/newsletter-cta.tsx index 3957c20..b372709 100644 --- a/components/landing/newsletter-cta.tsx +++ b/components/landing/newsletter-cta.tsx @@ -2,12 +2,20 @@ import { useState } from "react"; import { cn } from "@/lib/utils"; import { Typography } from "@/components/Typography"; +export type EmailProvider = "none" | "mailchimp" | "klaviyo"; + export type NewsletterCtaProps = { tagline: string; heading: string; subheading: string; buttonLabel: string; - endpoint: string; + emailProvider: EmailProvider; + endpoint?: string; + mailchimpApiKey?: string; + mailchimpServerPrefix?: string; + mailchimpAudienceId?: string; + klaviyoCompanyId?: string; + klaviyoListId?: string; imageUrl: string; layout: "split" | "stacked"; }; @@ -17,7 +25,13 @@ export function NewsletterCta({ heading, subheading, buttonLabel, + emailProvider, endpoint, + mailchimpApiKey, + mailchimpServerPrefix, + mailchimpAudienceId, + klaviyoCompanyId, + klaviyoListId, imageUrl, layout, }: NewsletterCtaProps) { @@ -30,7 +44,53 @@ export function NewsletterCta({ if (!email) return; setSubmitting(true); try { - if (endpoint) { + if (emailProvider === "mailchimp") { + if (mailchimpServerPrefix && mailchimpAudienceId && mailchimpApiKey) { + await fetch( + `https://${mailchimpServerPrefix}.api.mailchimp.com/3.0/lists/${mailchimpAudienceId}/members`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${mailchimpApiKey}`, + }, + body: JSON.stringify({ + email_address: email, + status: "subscribed", + }), + }, + ); + } + } else if (emailProvider === "klaviyo") { + if (klaviyoCompanyId && klaviyoListId) { + await fetch( + `https://a.klaviyo.com/client/subscriptions/?company_id=${klaviyoCompanyId}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + revision: "2024-10-15", + }, + body: JSON.stringify({ + data: { + type: "subscription", + attributes: { + profile: { + data: { + type: "profile", + attributes: { email }, + }, + }, + }, + relationships: { + list: { data: { type: "list", id: klaviyoListId } }, + }, + }, + }), + }, + ); + } + } else if (endpoint) { await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/components/navigation/navigation.editor.tsx b/components/navigation/navigation.editor.tsx index 5648e0a..ff4767d 100644 --- a/components/navigation/navigation.editor.tsx +++ b/components/navigation/navigation.editor.tsx @@ -1,6 +1,8 @@ import { ComponentConfig } from "@reacteditor/core"; import { Menu as MenuIcon } from "lucide-react"; +import { imageField } from "@reacteditor/plugin-media/field"; import { Navigation, type NavigationProps } from "@/components/navigation/navigation"; +import { frontendAiMediaAdapter } from "@/services/media-adapter"; export const navigationEditor: ComponentConfig = { label: "Navigation", @@ -9,6 +11,7 @@ export const navigationEditor: ComponentConfig = { global: true, defaultProps: { brand: "Maison", + logo: "", links: [ { label: "Shop", href: "/collections" }, { label: "Lookbook", href: "/lookbook" }, @@ -24,6 +27,7 @@ export const navigationEditor: ComponentConfig = { }, fields: { brand: { label: "Brand", type: "text", contentEditable: true }, + logo: { label: "Logo", ...imageField({ adapter: frontendAiMediaAdapter }) }, links: { label: "Links", type: "array", diff --git a/components/navigation/navigation.tsx b/components/navigation/navigation.tsx index bd9fe81..ec8eef4 100644 --- a/components/navigation/navigation.tsx +++ b/components/navigation/navigation.tsx @@ -7,6 +7,7 @@ import { cn } from "@/lib/utils"; export type NavigationProps = { brand: string; + logo?: string; links: Array<{ label: string; href: string }>; showSearch: "yes" | "no"; showCart: "yes" | "no"; @@ -18,6 +19,7 @@ export type NavigationProps = { export function Navigation({ brand, + logo, links, showSearch, showCart, @@ -70,10 +72,18 @@ export function Navigation({
- {brand} + {logo ? ( + {brand + ) : ( + brand || "Brand Logo" + )}