2026-04-05 18:31:16 -07:00
|
|
|
import React, { CSSProperties } from 'react';
|
|
|
|
|
import { useNode, UserComponent } from '@craftjs/core';
|
|
|
|
|
import { cssPropsToString } from '../../utils/style-helpers';
|
2026-07-12 13:16:53 -07:00
|
|
|
import { CtaButton, normalizeCtas, ctaInlineStyle, ctasToHtml } from './_cta-helpers';
|
2026-07-12 11:49:10 -07:00
|
|
|
import { escapeHtml, escapeAttr } from '../../utils/escape';
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
interface CTASectionProps {
|
|
|
|
|
heading?: string;
|
|
|
|
|
description?: string;
|
2026-05-25 12:43:28 -07:00
|
|
|
ctas?: CtaButton[];
|
|
|
|
|
/** Legacy props kept for backward compat with saved projects. */
|
2026-04-05 18:31:16 -07:00
|
|
|
buttonText?: string;
|
|
|
|
|
buttonHref?: string;
|
|
|
|
|
gradient?: string;
|
2026-05-25 12:43:28 -07:00
|
|
|
anchorId?: string;
|
2026-04-05 18:31:16 -07:00
|
|
|
style?: CSSProperties;
|
2026-07-14 06:47:58 -07:00
|
|
|
animation?: string;
|
|
|
|
|
animationDelay?: string;
|
|
|
|
|
hideOnDesktop?: boolean;
|
|
|
|
|
hideOnTablet?: boolean;
|
|
|
|
|
hideOnMobile?: boolean;
|
2026-04-05 18:31:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defaultGradient = 'linear-gradient(135deg, #2563eb 0%, #7c3aed 100%)';
|
|
|
|
|
|
|
|
|
|
export const CTASection: UserComponent<CTASectionProps> = ({
|
|
|
|
|
heading = 'Ready to Get Started?',
|
|
|
|
|
description = 'Join thousands of satisfied users and start building your dream website today.',
|
2026-05-25 12:43:28 -07:00
|
|
|
ctas,
|
|
|
|
|
buttonText,
|
|
|
|
|
buttonHref,
|
2026-04-05 18:31:16 -07:00
|
|
|
gradient = defaultGradient,
|
2026-05-25 12:43:28 -07:00
|
|
|
anchorId,
|
2026-04-05 18:31:16 -07:00
|
|
|
style = {},
|
|
|
|
|
}) => {
|
|
|
|
|
const {
|
|
|
|
|
connectors: { connect, drag },
|
|
|
|
|
selected,
|
|
|
|
|
} = useNode((node) => ({
|
|
|
|
|
selected: node.events.selected,
|
|
|
|
|
}));
|
|
|
|
|
|
2026-05-25 12:43:28 -07:00
|
|
|
const effectiveCtas = normalizeCtas({ ctas, buttonText, buttonHref });
|
|
|
|
|
const ctaDefaults = { primaryBg: '#ffffff', primaryText: '#18181b', outlineText: '#ffffff' };
|
|
|
|
|
|
2026-04-05 18:31:16 -07:00
|
|
|
return (
|
|
|
|
|
<section
|
|
|
|
|
ref={(ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); }}
|
2026-05-25 12:43:28 -07:00
|
|
|
id={anchorId || undefined}
|
2026-04-05 18:31:16 -07:00
|
|
|
style={{
|
|
|
|
|
background: gradient,
|
|
|
|
|
padding: '80px 20px',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
outline: selected ? '2px solid #3b82f6' : 'none',
|
|
|
|
|
...style,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ maxWidth: '700px', margin: '0 auto' }}>
|
|
|
|
|
<h2 style={{ fontSize: '36px', fontWeight: '700', color: '#ffffff', marginBottom: '12px' }}>
|
|
|
|
|
{heading}
|
|
|
|
|
</h2>
|
|
|
|
|
<p style={{ fontSize: '18px', color: 'rgba(255,255,255,0.85)', marginBottom: '28px', lineHeight: '1.6' }}>
|
|
|
|
|
{description}
|
|
|
|
|
</p>
|
2026-05-25 12:43:28 -07:00
|
|
|
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center', flexWrap: 'wrap' }}>
|
|
|
|
|
{effectiveCtas.map((cta, i) => (
|
|
|
|
|
<a key={i} href={cta.href || '#'} onClick={(e) => e.preventDefault()}
|
|
|
|
|
style={ctaInlineStyle(cta, ctaDefaults)}>
|
|
|
|
|
{cta.text}
|
|
|
|
|
</a>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-04-05 18:31:16 -07:00
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ---------- Craft config ---------- */
|
|
|
|
|
|
|
|
|
|
CTASection.craft = {
|
|
|
|
|
displayName: 'CTA Section',
|
|
|
|
|
props: {
|
|
|
|
|
heading: 'Ready to Get Started?',
|
|
|
|
|
description: 'Join thousands of satisfied users and start building your dream website today.',
|
2026-05-25 12:43:28 -07:00
|
|
|
ctas: [
|
|
|
|
|
{ text: 'Start Free Trial', href: '#', variant: 'primary' },
|
|
|
|
|
] as CtaButton[],
|
2026-04-05 18:31:16 -07:00
|
|
|
gradient: defaultGradient,
|
2026-05-25 12:43:28 -07:00
|
|
|
anchorId: '',
|
2026-04-05 18:31:16 -07:00
|
|
|
style: {},
|
2026-07-14 06:47:58 -07:00
|
|
|
animation: '',
|
|
|
|
|
animationDelay: '',
|
|
|
|
|
hideOnDesktop: false,
|
|
|
|
|
hideOnTablet: false,
|
|
|
|
|
hideOnMobile: false,
|
2026-04-05 18:31:16 -07:00
|
|
|
},
|
|
|
|
|
rules: {
|
|
|
|
|
canDrag: () => true,
|
|
|
|
|
canMoveIn: () => false,
|
|
|
|
|
canMoveOut: () => true,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ---------- HTML export ---------- */
|
|
|
|
|
|
|
|
|
|
(CTASection as any).toHtml = (props: CTASectionProps, _childrenHtml: string) => {
|
|
|
|
|
const sectionStyle = cssPropsToString({
|
|
|
|
|
background: props.gradient || defaultGradient,
|
|
|
|
|
padding: '80px 20px',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
...props.style,
|
|
|
|
|
});
|
2026-05-25 12:43:28 -07:00
|
|
|
const ctas = normalizeCtas(props);
|
|
|
|
|
const buttonsHtml = ctasToHtml(ctas, { primaryBg: '#ffffff', primaryText: '#18181b', outlineText: '#ffffff' });
|
2026-07-12 11:49:10 -07:00
|
|
|
const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : '';
|
2026-04-05 18:31:16 -07:00
|
|
|
return {
|
2026-05-25 12:43:28 -07:00
|
|
|
html: `<section${idAttr}${sectionStyle ? ` style="${sectionStyle}"` : ''}>
|
2026-04-05 18:31:16 -07:00
|
|
|
<div style="max-width:700px;margin:0 auto">
|
2026-07-12 11:49:10 -07:00
|
|
|
<h2 style="font-size:36px;font-weight:700;color:#ffffff;margin-bottom:12px">${escapeHtml(props.heading || '')}</h2>
|
|
|
|
|
<p style="font-size:18px;color:rgba(255,255,255,0.85);margin-bottom:28px;line-height:1.6">${escapeHtml(props.description || '')}</p>
|
2026-05-25 12:43:28 -07:00
|
|
|
<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap">${buttonsHtml}</div>
|
2026-04-05 18:31:16 -07:00
|
|
|
</div>
|
|
|
|
|
</section>`,
|
|
|
|
|
};
|
|
|
|
|
};
|