export function freelanceRateCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Freelance Rate Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addRow(row => {
row.addTextPanel('subheader', {
computedValue: () => 'Calculate your ideal hourly or project rate',
customStyles: { 'font-size': '1rem', 'color': '#64748b' }
});
});
form.addSpacer({ height: 20 });
// Income Goals Section
const incomeSection = form.addSubform('incomeGoals', { title: '๐ฏ Income Goals' });
incomeSection.addRow(row => {
row.addDecimal('annualSalaryGoal', {
label: 'Desired Annual Income ($)',
min: 20000,
max: 1000000,
defaultValue: 80000,
decimalPlaces: 0,
isRequired: true,
tooltip: 'What you want to take home after taxes and expenses'
}, '1fr');
row.addDropdown('taxBracket', {
label: 'Estimated Tax Bracket',
options: [
{ id: '15', name: '15% (Low income)' },
{ id: '22', name: '22% (Middle income)' },
{ id: '24', name: '24% (Upper middle)' },
{ id: '32', name: '32% (High income)' },
{ id: '35', name: '35%+ (Very high income)' }
],
defaultValue: '24',
isRequired: true
}, '1fr');
});
// Business Expenses Section
const expensesSection = form.addSubform('businessExpenses', { title: '๐ธ Monthly Business Expenses' });
expensesSection.addRow(row => {
row.addDecimal('software', {
label: 'Software & Tools ($)',
min: 0,
max: 5000,
defaultValue: 200,
decimalPlaces: 0
}, '1fr');
row.addDecimal('coworking', {
label: 'Office/Coworking ($)',
min: 0,
max: 5000,
defaultValue: 0,
decimalPlaces: 0
}, '1fr');
});
expensesSection.addRow(row => {
row.addDecimal('insurance', {
label: 'Health Insurance ($)',
min: 0,
max: 3000,
defaultValue: 500,
decimalPlaces: 0
}, '1fr');
row.addDecimal('retirement', {
label: 'Retirement Savings ($)',
min: 0,
max: 5000,
defaultValue: 500,
decimalPlaces: 0
}, '1fr');
});
expensesSection.addRow(row => {
row.addDecimal('otherExpenses', {
label: 'Other Expenses ($)',
min: 0,
max: 10000,
defaultValue: 300,
decimalPlaces: 0,
tooltip: 'Marketing, education, equipment, etc.'
}, '1fr');
});
// Working Hours Section
const hoursSection = form.addSubform('workingHours', { title: 'โฐ Working Schedule' });
hoursSection.addRow(row => {
row.addInteger('weeksPerYear', {
label: 'Working Weeks Per Year',
min: 20,
max: 52,
defaultValue: 48,
tooltip: 'Account for vacation, sick days, holidays'
}, '1fr');
row.addInteger('hoursPerWeek', {
label: 'Hours Per Week',
min: 10,
max: 80,
defaultValue: 40
}, '1fr');
});
hoursSection.addRow(row => {
row.addInteger('billablePercent', {
label: 'Billable Hours (%)',
min: 20,
max: 100,
defaultValue: 65,
tooltip: 'Typically 50-70%. Rest goes to admin, marketing, etc.'
}, '1fr');
row.addDropdown('experienceLevel', {
label: 'Experience Level',
options: [
{ id: 'junior', name: 'Junior (0-2 years)' },
{ id: 'mid', name: 'Mid-Level (2-5 years)' },
{ id: 'senior', name: 'Senior (5-10 years)' },
{ id: 'expert', name: 'Expert (10+ years)' }
],
defaultValue: 'mid'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Results Section
const resultsSection = form.addSubform('results', { title: '๐ฐ Your Rates', isCollapsible: false });
const calculateRequiredGross = () => {
const salary = incomeSection.decimal('annualSalaryGoal')?.value() || 80000;
const taxBracket = parseFloat(incomeSection.dropdown('taxBracket')?.value() || '24') / 100;
const software = (expensesSection.decimal('software')?.value() || 0) * 12;
const coworking = (expensesSection.decimal('coworking')?.value() || 0) * 12;
const insurance = (expensesSection.decimal('insurance')?.value() || 0) * 12;
const retirement = (expensesSection.decimal('retirement')?.value() || 0) * 12;
const other = (expensesSection.decimal('otherExpenses')?.value() || 0) * 12;
const totalExpenses = software + coworking + insurance + retirement + other;
// Need to gross up for taxes: net = gross * (1 - tax)
// So gross = (salary + expenses) / (1 - tax)
const selfEmploymentTax = 0.153 * 0.9235; // ~14.1%
const totalTaxRate = taxBracket + selfEmploymentTax;
const grossRequired = (salary + totalExpenses) / (1 - totalTaxRate);
return grossRequired;
};
const calculateBillableHours = () => {
const weeks = hoursSection.integer('weeksPerYear')?.value() || 48;
const hoursPerWeek = hoursSection.integer('hoursPerWeek')?.value() || 40;
const billablePercent = hoursSection.integer('billablePercent')?.value() || 65;
return weeks * hoursPerWeek * (billablePercent / 100);
};
resultsSection.addRow(row => {
row.addPriceDisplay('grossRequired', {
label: 'Required Gross Revenue',
computedValue: () => Math.round(calculateRequiredGross()),
variant: 'default',
suffix: '/year'
}, '1fr');
row.addTextPanel('billableHours', {
computedValue: () => {
const hours = calculateBillableHours();
return `Billable Hours: ${Math.round(hours)}/year`;
},
customStyles: { 'font-size': '0.95rem', 'color': '#475569' }
}, '1fr');
});
resultsSection.addRow(row => {
row.addPriceDisplay('dailyRate', {
label: 'Daily Rate (8 hours)',
computedValue: () => {
const gross = calculateRequiredGross();
const billableHours = calculateBillableHours();
const hourly = gross / billableHours;
return Math.ceil(hourly * 8);
},
variant: 'default',
suffix: '/day'
}, '1fr');
row.addPriceDisplay('weeklyRate', {
label: 'Weekly Rate',
computedValue: () => {
const gross = calculateRequiredGross();
const billableHours = calculateBillableHours();
const hourly = gross / billableHours;
const billablePercent = hoursSection.integer('billablePercent')?.value() || 65;
const hoursPerWeek = hoursSection.integer('hoursPerWeek')?.value() || 40;
return Math.ceil(hourly * hoursPerWeek * (billablePercent / 100));
},
variant: 'default',
suffix: '/week'
}, '1fr');
});
resultsSection.addRow(row => {
row.addTextPanel('experienceAdjustment', {
computedValue: () => {
const level = hoursSection.dropdown('experienceLevel')?.value() || 'mid';
const gross = calculateRequiredGross();
const billableHours = calculateBillableHours();
const baseRate = gross / billableHours;
const multipliers: Record<string, { low: number; high: number; label: string }> = {
'junior': { low: 0.7, high: 1.0, label: 'Junior rates are typically 70-100% of calculated rate' },
'mid': { low: 1.0, high: 1.3, label: 'Mid-level can charge 100-130% of base rate' },
'senior': { low: 1.3, high: 1.8, label: 'Senior professionals charge 130-180% premium' },
'expert': { low: 1.8, high: 3.0, label: 'Experts can command 180-300%+ premium rates' }
};
const mult = multipliers[level] || multipliers['mid'];
const lowRate = Math.ceil(baseRate * mult.low);
const highRate = Math.ceil(baseRate * mult.high);
return `${mult.label}: $${lowRate} - $${highRate}/hour`;
},
customStyles: { 'font-size': '0.9rem', 'color': '#059669', 'text-align': 'center' }
});
});
const finalSection = form.addSubform('final', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('hourlyRate', {
label: 'Minimum Hourly Rate',
computedValue: () => {
const gross = calculateRequiredGross();
const billableHours = calculateBillableHours();
return Math.ceil(gross / billableHours);
},
variant: 'large',
suffix: '/hour'
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Market rates vary by industry and location.',
customStyles: { 'font-size': '0.85rem', 'color': '#94a3b8', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Save My Rate'
});
}