export function productMarketFit(form: FormTs) {
// Product-Market Fit Survey - Sean Ellis methodology
// Demonstrates: RadioButton, Slider, ThumbRating, SuggestionChips, EmojiRating, CheckboxList, dynamic styling, conditional logic
// State for product name
const productName = form.state('Your Product');
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Product-Market Fit Survey',
computedValue: () => 'Help us understand how our product fits your needs',
customStyles: {
backgroundColor: '#8b5cf6',
color: 'white',
padding: '24px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// SECTION 1: The Core PMF Question (Sean Ellis Test)
// ============================================
const pmfSection = form.addSubform('pmfSection', {
title: 'The Key Question',
customStyles: () => {
const answer = pmfSection.radioButton('disappointment')?.value();
if (answer === 'very') return { backgroundColor: '#d1fae5', padding: '16px', borderRadius: '8px', border: '2px solid #10b981' };
if (answer === 'somewhat') return { backgroundColor: '#fef3c7', padding: '16px', borderRadius: '8px' };
if (answer === 'not') return { backgroundColor: '#fee2e2', padding: '16px', borderRadius: '8px' };
return { padding: '16px', borderRadius: '8px', border: '1px dashed #cbd5e1' };
}
});
pmfSection.addRow(row => {
row.addTextPanel('pmfIntro', {
computedValue: () => 'This is the most important question in the survey. Please answer honestly.',
customStyles: {
backgroundColor: '#f5f3ff',
padding: '12px 16px',
borderRadius: '8px',
borderLeft: '4px solid #8b5cf6',
fontSize: '14px',
marginBottom: '16px'
}
});
});
pmfSection.addRow(row => {
row.addRadioButton('disappointment', {
label: () => `How would you feel if you could no longer use ${productName()}?`,
options: [
{ id: 'very', name: 'Very disappointed' },
{ id: 'somewhat', name: 'Somewhat disappointed' },
{ id: 'not', name: 'Not disappointed (it is not really that useful)' }
],
orientation: 'vertical',
isRequired: true
});
});
// Dynamic feedback based on answer
pmfSection.addRow(row => {
row.addTextPanel('pmfFeedback', {
computedValue: () => {
const answer = pmfSection.radioButton('disappointment')?.value();
if (answer === 'very') return '🎯 You\'re in our target segment! Your feedback is especially valuable.';
if (answer === 'somewhat') return '💡 Thanks! We want to understand how to move you to "very disappointed".';
if (answer === 'not') return '🤔 We appreciate your honesty. Help us understand what would make this more valuable.';
return '';
},
customStyles: () => {
const answer = pmfSection.radioButton('disappointment')?.value();
if (!answer) return { display: 'none' };
return {
padding: '12px',
borderRadius: '8px',
fontSize: '14px',
textAlign: 'center',
backgroundColor: answer === 'very' ? '#d1fae5' : answer === 'somewhat' ? '#fef3c7' : '#fee2e2'
};
},
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
});
// ============================================
// SECTION 2: User Persona
// ============================================
const personaSection = form.addSubform('personaSection', {
title: 'About You',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
personaSection.addRow(row => {
row.addDropdown('role', {
label: 'What best describes your role?',
options: [
{ id: 'founder', name: 'Founder / CEO' },
{ id: 'executive', name: 'Executive / VP' },
{ id: 'manager', name: 'Manager / Team Lead' },
{ id: 'individual', name: 'Individual Contributor' },
{ id: 'freelancer', name: 'Freelancer / Consultant' },
{ id: 'student', name: 'Student' },
{ id: 'other', name: 'Other' }
],
placeholder: 'Select your role'
}, '1fr');
row.addDropdown('companySize', {
label: 'Company size',
options: [
{ id: '1', name: 'Just me' },
{ id: '2-10', name: '2-10 employees' },
{ id: '11-50', name: '11-50 employees' },
{ id: '51-200', name: '51-200 employees' },
{ id: '201-1000', name: '201-1000 employees' },
{ id: '1000+', name: '1000+ employees' }
],
placeholder: 'Select size'
}, '1fr');
});
personaSection.addRow(row => {
row.addDropdown('usageFrequency', {
label: () => `How often do you use ${productName()}?`,
options: [
{ id: 'daily', name: 'Daily' },
{ id: 'weekly', name: 'Several times a week' },
{ id: 'biweekly', name: 'Once a week' },
{ id: 'monthly', name: 'A few times a month' },
{ id: 'rarely', name: 'Less than monthly' }
],
placeholder: 'Select frequency'
}, '1fr');
row.addDropdown('usageDuration', {
label: 'How long have you been using it?',
options: [
{ id: 'week', name: 'Less than a week' },
{ id: 'month', name: '1-4 weeks' },
{ id: '1-3months', name: '1-3 months' },
{ id: '3-6months', name: '3-6 months' },
{ id: '6-12months', name: '6-12 months' },
{ id: '1year+', name: 'More than a year' }
],
placeholder: 'Select duration'
}, '1fr');
});
// ============================================
// SECTION 3: Main Benefit
// ============================================
const benefitSection = form.addSubform('benefitSection', {
title: 'Value Perception',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
benefitSection.addRow(row => {
row.addRadioButton('mainBenefit', {
label: () => `What is the main benefit you get from ${productName()}?`,
options: [
{ id: 'time', name: 'Saves me time' },
{ id: 'money', name: 'Saves me money' },
{ id: 'quality', name: 'Improves quality of my work' },
{ id: 'capability', name: 'Enables something I couldn\'t do before' },
{ id: 'organize', name: 'Helps me stay organized' },
{ id: 'collaborate', name: 'Improves collaboration with others' },
{ id: 'insights', name: 'Provides valuable insights/data' },
{ id: 'other', name: 'Other' }
],
orientation: 'vertical'
});
});
benefitSection.addRow(row => {
row.addSlider('valueRating', {
label: () => {
const val = benefitSection.slider('valueRating')?.value() || 0;
if (val >= 80) return 'How valuable is this product to you? Essential';
if (val >= 60) return 'How valuable is this product to you? Very Valuable';
if (val >= 40) return 'How valuable is this product to you? Moderately Valuable';
if (val >= 20) return 'How valuable is this product to you? Slightly Valuable';
return 'How valuable is this product to you? Not Very Valuable';
},
min: 0,
max: 100,
step: 5,
defaultValue: 50,
showValue: true,
unit: '%'
});
});
// ============================================
// SECTION 4: Alternatives & Uniqueness
// ============================================
const alternativesSection = form.addSubform('alternativesSection', {
title: 'Alternatives & Competition',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
alternativesSection.addRow(row => {
row.addRadioButton('alternative', {
label: () => `What would you use if ${productName()} was no longer available?`,
options: [
{ id: 'competitor', name: 'A competing product' },
{ id: 'manual', name: 'Do it manually / spreadsheets' },
{ id: 'build', name: 'Build something myself' },
{ id: 'nothing', name: 'Nothing - I would go without' },
{ id: 'unsure', name: 'Not sure' }
],
orientation: 'vertical'
});
});
alternativesSection.addRow(row => {
row.addTextbox('alternativeProduct', {
label: 'If competitor, which product?',
placeholder: 'Name the alternative product...',
isVisible: () => alternativesSection.radioButton('alternative')?.value() === 'competitor'
});
});
alternativesSection.addRow(row => {
row.addSuggestionChips('uniqueFeatures', {
label: () => `What makes ${productName()} stand out from alternatives?`,
suggestions: [
{ id: 'ease', name: 'Ease of use' },
{ id: 'price', name: 'Better price' },
{ id: 'features', name: 'More features' },
{ id: 'speed', name: 'Faster / Performance' },
{ id: 'design', name: 'Better design' },
{ id: 'integrations', name: 'Integrations' },
{ id: 'support', name: 'Customer support' },
{ id: 'reliability', name: 'More reliable' }
],
max: 4,
alignment: 'left'
});
});
// ============================================
// SECTION 5: Improvements (especially for non-very-disappointed)
// ============================================
const improvementSection = form.addSubform('improvementSection', {
title: 'How Can We Improve?',
isVisible: () => {
const answer = pmfSection.radioButton('disappointment')?.value();
return answer === 'somewhat' || answer === 'not';
},
customStyles: {
backgroundColor: '#fef3c7',
padding: '16px',
borderRadius: '8px'
}
});
improvementSection.addRow(row => {
row.addTextPanel('improvementIntro', {
computedValue: () => 'Help us understand what would make this product more valuable to you.',
customStyles: {
fontSize: '14px',
marginBottom: '12px'
}
});
});
improvementSection.addRow(row => {
row.addCheckboxList('missingFeatures', {
label: 'What\'s missing or could be better?',
options: [
{ id: 'features', name: 'Missing key features I need' },
{ id: 'complexity', name: 'Too complex / hard to use' },
{ id: 'simple', name: 'Too simple / needs more power' },
{ id: 'price', name: 'Price is too high' },
{ id: 'slow', name: 'Too slow / performance issues' },
{ id: 'buggy', name: 'Too buggy / unreliable' },
{ id: 'integrations', name: 'Missing integrations I need' },
{ id: 'support', name: 'Support is not helpful' }
],
orientation: 'vertical'
});
});
improvementSection.addSpacer();
improvementSection.addRow(row => {
row.addTextarea('improvementIdeas', {
label: 'What specific changes would make you "very disappointed" to lose this product?',
placeholder: 'Share your ideas...',
rows: 3,
autoExpand: true
});
});
// ============================================
// SECTION 6: What They Love (for very disappointed)
// ============================================
const loveSection = form.addSubform('loveSection', {
title: 'What You Love',
isVisible: () => pmfSection.radioButton('disappointment')?.value() === 'very',
customStyles: {
backgroundColor: '#d1fae5',
padding: '16px',
borderRadius: '8px'
}
});
loveSection.addRow(row => {
row.addTextarea('whatYouLove', {
label: () => `What do you love most about ${productName()}?`,
placeholder: 'Tell us what makes this product essential for you...',
rows: 3,
autoExpand: true
});
});
loveSection.addRow(row => {
row.addTextarea('useCases', {
label: 'Describe your main use case',
placeholder: 'What problem does this solve for you? How do you use it?',
rows: 2,
autoExpand: true
});
});
// ============================================
// SECTION 7: Recommendation
// ============================================
const recommendSection = form.addSubform('recommendSection', {
title: 'Recommendation',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
recommendSection.addRow(row => {
row.addThumbRating('wouldRecommend', {
label: 'Would you recommend this product to others?',
showLabels: true,
upLabel: 'Yes, definitely',
downLabel: 'Probably not',
alignment: 'center',
size: 'lg'
});
});
recommendSection.addRow(row => {
row.addTextarea('recommendTo', {
label: 'Who would benefit most from this product?',
placeholder: 'Describe the ideal user...',
rows: 2,
autoExpand: true,
isVisible: () => recommendSection.thumbRating('wouldRecommend')?.value() === 'up'
});
});
// ============================================
// SECTION 8: Summary
// ============================================
const summarySection = form.addSubform('summarySection', {
title: 'PMF Survey Summary',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const disappointment = pmfSection.radioButton('disappointment')?.value();
const mainBenefit = benefitSection.radioButton('mainBenefit')?.value();
const valueRating = benefitSection.slider('valueRating')?.value() || 0;
const role = personaSection.dropdown('role')?.value();
const frequency = personaSection.dropdown('usageFrequency')?.value();
const wouldRecommend = recommendSection.thumbRating('wouldRecommend')?.value();
if (!disappointment) return '';
const disappointmentLabels: Record<string, string> = {
'very': 'Very disappointed',
'somewhat': 'Somewhat disappointed',
'not': 'Not disappointed'
};
const benefitLabels: Record<string, string> = {
'time': 'Saves time',
'money': 'Saves money',
'quality': 'Improves quality',
'capability': 'New capabilities',
'organize': 'Organization',
'collaborate': 'Collaboration',
'insights': 'Insights/Data',
'other': 'Other'
};
const roleLabels: Record<string, string> = {
'founder': 'Founder/CEO',
'executive': 'Executive',
'manager': 'Manager',
'individual': 'IC',
'freelancer': 'Freelancer',
'student': 'Student',
'other': 'Other'
};
const frequencyLabels: Record<string, string> = {
'daily': 'Daily',
'weekly': 'Several times/week',
'biweekly': 'Weekly',
'monthly': 'Few times/month',
'rarely': 'Less than monthly'
};
let summary = `PMF SURVEY RESPONSE\n`;
summary += `${'═'.repeat(25)}\n\n`;
// PMF Answer with indicator
const pmfEmoji = disappointment === 'very' ? '🎯' : disappointment === 'somewhat' ? '💡' : '⚠️';
summary += `${pmfEmoji} PMF Answer: ${disappointmentLabels[disappointment]}\n`;
summary += ` Value Score: ${valueRating}%\n`;
if (role || frequency) {
summary += `\n${'─'.repeat(25)}\n`;
if (role) summary += `Role: ${roleLabels[role] || role}\n`;
if (frequency) summary += `Usage: ${frequencyLabels[frequency] || frequency}\n`;
}
if (mainBenefit) {
summary += `\nMain Benefit: ${benefitLabels[mainBenefit] || mainBenefit}`;
}
if (wouldRecommend) {
summary += `\nWould Recommend: ${wouldRecommend === 'up' ? 'Yes' : 'No'}`;
}
return summary;
},
customStyles: () => {
const disappointment = pmfSection.radioButton('disappointment')?.value();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '13px'
};
if (disappointment === 'very') {
return { ...baseStyles, backgroundColor: '#d1fae5', borderLeft: '4px solid #10b981' };
} else if (disappointment === 'somewhat') {
return { ...baseStyles, backgroundColor: '#fef3c7', borderLeft: '4px solid #f59e0b' };
} else if (disappointment === 'not') {
return { ...baseStyles, backgroundColor: '#fee2e2', borderLeft: '4px solid #ef4444' };
}
return { ...baseStyles, backgroundColor: '#f8fafc', borderLeft: '4px solid #8b5cf6' };
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Survey',
isVisible: () => pmfSection.radioButton('disappointment')?.value() !== null
});
form.configureCompletionScreen({
type: 'text',
title: 'Thank You!',
message: 'Your feedback is invaluable in helping us build a better product. We read every response and use it to guide our development priorities.'
});
}