import React, { CSSProperties } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; interface PricingPlan { name: string; price: string; period: string; features: string[]; buttonText: string; buttonHref: string; isFeatured: boolean; } interface PricingTableProps { plans?: PricingPlan[]; style?: CSSProperties; featuredBg?: string; bulletType?: string; } const bulletChars: Record = { check: '✓', dot: '●', arrow: '→', star: '★', dash: '—', none: '', }; const defaultPlans: PricingPlan[] = [ { name: 'Basic', price: '$9', period: '/month', features: ['1 Website', '10 GB Storage', 'Free SSL Certificate', 'Email Support'], buttonText: 'Get Started', buttonHref: '#', isFeatured: false, }, { name: 'Pro', price: '$29', period: '/month', features: ['10 Websites', '100 GB Storage', 'Free SSL Certificate', 'Priority Support', 'Custom Domain', 'Analytics Dashboard'], buttonText: 'Get Started', buttonHref: '#', isFeatured: true, }, { name: 'Enterprise', price: '$99', period: '/month', features: ['Unlimited Websites', '1 TB Storage', 'Free SSL Certificate', '24/7 Phone Support', 'Custom Domain', 'Advanced Analytics', 'API Access', 'Team Collaboration'], buttonText: 'Contact Sales', buttonHref: '#', isFeatured: false, }, ]; export const PricingTable: UserComponent = ({ plans = defaultPlans, style = {}, featuredBg = '#3b82f6', bulletType = 'check', }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); return (
{ if (ref) connect(drag(ref)); }} style={{ padding: '80px 20px', backgroundColor: '#ffffff', outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} >
{plans.map((plan, i) => (
{plan.isFeatured && (
Most Popular
)}

{plan.name}

{plan.price} {plan.period}
    {(Array.isArray(plan.features) ? plan.features : []).map((feature, fi) => (
  • {bulletChars[bulletType] || '✓'} {feature}
  • ))}
e.preventDefault()} style={{ marginTop: 'auto', display: 'inline-block', padding: '14px 32px', backgroundColor: plan.isFeatured ? '#ffffff' : featuredBg, color: plan.isFeatured ? featuredBg : '#ffffff', textDecoration: 'none', borderRadius: '8px', fontWeight: '600', fontSize: '14px', width: '100%', textAlign: 'center', }} > {plan.buttonText}
))}
); }; /* ---------- Settings panel ---------- */ const PricingTableSettings: React.FC = () => { const { actions: { setProp }, props } = useNode((node) => ({ props: node.data.props as PricingTableProps, })); const plans = props.plans || defaultPlans; const inputStyle: CSSProperties = { width: '100%', padding: '3px 6px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, }; const labelStyle: CSSProperties = { fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }; const updatePlan = (index: number, field: keyof PricingPlan, value: any) => { setProp((p: PricingTableProps) => { const updated = [...(p.plans || defaultPlans)]; updated[index] = { ...updated[index], [field]: value }; p.plans = updated; }); }; const updateFeature = (planIndex: number, featureIndex: number, value: string) => { setProp((p: PricingTableProps) => { const updated = [...(p.plans || defaultPlans)]; const features = [...(Array.isArray(updated[planIndex].features) ? updated[planIndex].features : [])]; features[featureIndex] = value; updated[planIndex] = { ...updated[planIndex], features }; p.plans = updated; }); }; const addFeature = (planIndex: number) => { setProp((p: PricingTableProps) => { const updated = [...(p.plans || defaultPlans)]; updated[planIndex] = { ...updated[planIndex], features: [...updated[planIndex].features, 'New Feature'] }; p.plans = updated; }); }; const removeFeature = (planIndex: number, featureIndex: number) => { setProp((p: PricingTableProps) => { const updated = [...(p.plans || defaultPlans)]; const features = [...(Array.isArray(updated[planIndex].features) ? updated[planIndex].features : [])]; features.splice(featureIndex, 1); updated[planIndex] = { ...updated[planIndex], features }; p.plans = updated; }); }; const addPlan = () => { setProp((p: PricingTableProps) => { p.plans = [...(p.plans || defaultPlans), { name: 'New Plan', price: '$19', period: '/month', features: ['Feature 1', 'Feature 2'], buttonText: 'Get Started', buttonHref: '#', isFeatured: false, }]; }); }; const removePlan = (index: number) => { setProp((p: PricingTableProps) => { const updated = [...(p.plans || defaultPlans)]; updated.splice(index, 1); p.plans = updated; }); }; const bgPresets = ['#3b82f6', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444', '#18181b', '#0f172a', '#7c3aed']; return (
{['#ffffff', '#f8fafc', '#f1f5f9', '#18181b', '#0f172a'].map((c) => (
{bgPresets.map((c) => (
{plans.map((plan, i) => (
updatePlan(i, 'name', e.target.value)} placeholder="Plan Name" style={{ ...inputStyle, flex: 1 }} />
updatePlan(i, 'price', e.target.value)} placeholder="$29" style={{ ...inputStyle, width: '60px', flex: 'none' }} /> updatePlan(i, 'period', e.target.value)} placeholder="/month" style={{ ...inputStyle, width: '70px', flex: 'none' }} />
updatePlan(i, 'buttonText', e.target.value)} placeholder="Button Text" style={{ ...inputStyle, flex: 1 }} /> updatePlan(i, 'buttonHref', e.target.value)} placeholder="URL" style={{ ...inputStyle, flex: 1 }} />
{/* Features */}
Features:
{(Array.isArray(plan.features) ? plan.features : []).map((feat, fi) => (
updateFeature(i, fi, e.target.value)} style={{ ...inputStyle, flex: 1 }} />
))}
))}
); }; /* ---------- Craft config ---------- */ PricingTable.craft = { displayName: 'Pricing Table', props: { plans: defaultPlans, style: { backgroundColor: '#ffffff' }, featuredBg: '#3b82f6', bulletType: 'check', }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, related: { settings: PricingTableSettings, }, }; /* ---------- HTML export ---------- */ (PricingTable as any).toHtml = (props: PricingTableProps, _childrenHtml: string) => { const esc = (s: string) => s.replace(//g, '>'); const bulletType = props.bulletType || 'check'; const sectionStyle = cssPropsToString({ padding: '80px 20px', ...props.style, }); const plans = props.plans || defaultPlans; const featuredBg = props.featuredBg || '#3b82f6'; const cards = plans.map((plan) => { const cardBg = plan.isFeatured ? featuredBg : '#ffffff'; const cardBorder = plan.isFeatured ? 'border:none;' : 'border:1px solid #e2e8f0;'; const textColor = plan.isFeatured ? '#ffffff' : '#18181b'; const subColor = plan.isFeatured ? 'rgba(255,255,255,0.8)' : '#64748b'; const featColor = plan.isFeatured ? 'rgba(255,255,255,0.9)' : '#4b5563'; const checkColor = plan.isFeatured ? '#bbf7d0' : '#10b981'; const btnBg = plan.isFeatured ? '#ffffff' : featuredBg; const btnColor = plan.isFeatured ? featuredBg : '#ffffff'; const scale = plan.isFeatured ? 'transform:scale(1.05);' : ''; const shadow = plan.isFeatured ? 'box-shadow:0 20px 60px rgba(59,130,246,0.3);' : 'box-shadow:0 1px 3px rgba(0,0,0,0.06);'; const featuresHtml = (Array.isArray(plan.features) ? plan.features : []).map((f) => `
  • ${bulletChars[bulletType] || '✓'}${esc(f)}
  • ` ).join('\n '); const badge = plan.isFeatured ? `
    Most Popular
    ` : ''; return `
    ${badge}

    ${esc(plan.name)}

    ${esc(plan.price)} ${esc(plan.period)}
      ${featuresHtml}
    ${esc(plan.buttonText)}
    `; }).join('\n '); return { html: `
    ${cards}
    `, }; };