import React, { CSSProperties } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr } from '../../utils/escape'; interface Testimonial { quote: string; name: string; title: string; rating: number; } interface TestimonialsProps { testimonials?: Testimonial[]; layout?: 'grid' | 'single'; columns?: number; style?: CSSProperties; cardBg?: string; starColor?: string; anchorId?: string; } const defaultTestimonials: Testimonial[] = [ { quote: 'This product has completely transformed our workflow. Highly recommended for any team.', name: 'Sarah Johnson', title: 'Marketing Director', rating: 5 }, { quote: 'Outstanding support and an incredibly intuitive interface. We saw results from day one.', name: 'Michael Chen', title: 'CTO, TechStart', rating: 5 }, { quote: 'The best investment we have made this year. Simple, powerful, and reliable.', name: 'Emily Rodriguez', title: 'Founder, DesignLab', rating: 4 }, ]; function renderStars(count: number, color: string): React.ReactNode { return (
{[1, 2, 3, 4, 5].map((i) => ( ))}
); } function starsHtml(count: number, color: string): string { const stars = [1, 2, 3, 4, 5].map((i) => `` ).join(''); return `
${stars}
`; } export const Testimonials: UserComponent = ({ testimonials = defaultTestimonials, layout = 'grid', columns = 3, style = {}, cardBg = '#f8fafc', starColor = '#f59e0b', anchorId, }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); const cardStyle: CSSProperties = { backgroundColor: cardBg, borderRadius: '12px', padding: '32px 24px', textAlign: 'center', border: '1px solid #e2e8f0', }; const renderCard = (t: Testimonial, i: number) => (
{renderStars(t.rating, starColor)}

“{t.quote}”

{t.name}
{t.title}
); const items = testimonials.length > 0 ? testimonials : defaultTestimonials; return (
{ if (ref) connect(drag(ref)); }} id={anchorId || undefined} style={{ padding: '80px 20px', backgroundColor: '#ffffff', outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} >
{layout === 'grid' ? (
{items.map((t, i) => renderCard(t, i))}
) : ( // Static single testimonial (parity with the static toHtml export -- // no carousel controls, since the published site has no JS for this // component). Always shows the first testimonial.
{renderCard(items[0], 0)}
)}
); }; /* ---------- Craft config ---------- */ Testimonials.craft = { displayName: 'Testimonials', props: { testimonials: defaultTestimonials, layout: 'grid', columns: 3, style: { backgroundColor: '#ffffff' }, cardBg: '#f8fafc', starColor: '#f59e0b', anchorId: '', }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, }; /* ---------- HTML export ---------- */ (Testimonials as any).toHtml = (props: TestimonialsProps, _childrenHtml: string) => { const { testimonials = defaultTestimonials, layout = 'grid', columns = 3, style = {}, cardBg = '#f8fafc', starColor = '#f59e0b', } = props; const items = testimonials.length > 0 ? testimonials : defaultTestimonials; const sectionStyle = cssPropsToString({ padding: '80px 20px', backgroundColor: '#ffffff', ...style, }); const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : ''; const cardCss = `background-color:${cardBg};border-radius:12px;padding:32px 24px;text-align:center;border:1px solid #e2e8f0`; const cardHtml = (t: Testimonial): string => `
${starsHtml(t.rating, starColor)}

“${escapeHtml(t.quote)}”

${escapeHtml(t.name)}
${escapeHtml(t.title)}
`; if (layout === 'single') { // Static parity with the editor's single-layout render: exactly ONE // testimonial card (the first), no carousel controls -- the published // export has no JS for this component. return { html: `
${cardHtml(items[0])}
`, }; } const cards = items.map(cardHtml).join('\n '); return { html: `
${cards}
`, }; };