export function dogWalkingCalculator(form: FormTs) {
// Walk duration base prices
const walkPrices: Record<string, number> = {
'15': 15,
'30': 22,
'45': 30,
'60': 38
};
// Dog size adjustments
const sizeAdjustments: Record<string, number> = {
'small': 0,
'medium': 2,
'large': 4,
'giant': 6
};
// Frequency discounts (percentage)
const frequencyDiscounts: Record<string, number> = {
'once': 0,
'2-3': 5,
'4-5': 10,
'daily': 15
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Dog Walking Price Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Service Location Section
const locationSection = form.addSubform('serviceLocation', { title: '๐ Your Location' });
locationSection.addRow(row => {
row.addAddress('pickupAddress', {
label: 'Pickup Address',
placeholder: 'Enter your address...',
showMap: true,
showDistance: true,
referenceAddress: {
formattedAddress: 'Service Center, Denver, CO',
coordinates: { lat: 39.7392, lng: -104.9903 }
},
restrictToCountries: ['US', 'CA'],
distanceUnit: 'miles',
isRequired: true
});
});
locationSection.addRow(row => {
row.addTextPanel('serviceAreaInfo', {
computedValue: () => {
const addressField = locationSection.address('pickupAddress');
const miles = addressField?.distance();
if (miles == null) return '๐ Enter address to check service area';
if (miles <= 5) return '๐ Within service area - No travel fee';
if (miles <= 10) return '๐ Extended area - $5 travel fee per walk';
if (miles <= 15) return '๐ Remote area - $10 travel fee per walk';
return '๐ Outside service area - Please contact us';
},
customStyles: { 'font-size': '0.9rem', 'color': '#7c3aed', 'background': '#ede9fe', 'padding': '10px', 'border-radius': '6px' }
});
});
// Dog Details Section
const dogSection = form.addSubform('dogDetails', { title: '๐ Your Dog' });
dogSection.addRow(row => {
row.addInteger('numberOfDogs', {
label: 'Number of Dogs',
min: 1,
max: 4,
defaultValue: 1,
isRequired: true,
tooltip: 'Maximum 4 dogs per walk'
}, '1fr');
row.addDropdown('dogSize', {
label: 'Dog Size',
options: [
{ id: 'small', name: 'Small (under 20 lbs)' },
{ id: 'medium', name: 'Medium (20-50 lbs)' },
{ id: 'large', name: 'Large (50-90 lbs)' },
{ id: 'giant', name: 'Giant (90+ lbs)' }
],
defaultValue: 'medium',
isRequired: true
}, '1fr');
});
dogSection.addRow(row => {
row.addDropdown('energyLevel', {
label: 'Energy Level',
options: [
{ id: 'low', name: 'Low (senior, calm)' },
{ id: 'medium', name: 'Medium (moderate activity)' },
{ id: 'high', name: 'High (very active, puppy)' }
],
defaultValue: 'medium',
tooltip: 'High energy dogs may need longer walks'
}, '1fr');
row.addDropdown('specialNeeds', {
label: 'Special Needs',
options: [
{ id: 'none', name: 'None' },
{ id: 'leash-reactive', name: 'Leash Reactive (+$5)' },
{ id: 'medical', name: 'Medical Needs (+$5)' },
{ id: 'puppy', name: 'Puppy Training (+$8)' }
],
defaultValue: 'none'
}, '1fr');
});
// Walk Options Section
const walkSection = form.addSubform('walkOptions', { title: '๐ถ Walk Options' });
walkSection.addRow(row => {
row.addRadioButton('walkDuration', {
label: 'Walk Duration',
options: [
{ id: '15', name: '15 Minutes (Quick potty break)' },
{ id: '30', name: '30 Minutes (Standard walk)' },
{ id: '45', name: '45 Minutes (Extended walk)' },
{ id: '60', name: '60 Minutes (Adventure walk)' }
],
defaultValue: '30',
orientation: 'vertical',
isRequired: true
});
});
walkSection.addRow(row => {
row.addDropdown('walkType', {
label: 'Walk Type',
options: [
{ id: 'neighborhood', name: 'Neighborhood Walk' },
{ id: 'park', name: 'Park Walk (+$5)' },
{ id: 'trail', name: 'Trail/Nature Walk (+$10)' },
{ id: 'beach', name: 'Beach Walk (+$15)' }
],
defaultValue: 'neighborhood',
isRequired: true
}, '1fr');
row.addDropdown('timeSlot', {
label: 'Preferred Time',
options: [
{ id: 'morning', name: 'Morning (7am-11am)' },
{ id: 'midday', name: 'Midday (11am-2pm)' },
{ id: 'afternoon', name: 'Afternoon (2pm-5pm)' },
{ id: 'evening', name: 'Evening (5pm-8pm) (+$3)' }
],
defaultValue: 'midday'
}, '1fr');
});
// Frequency Section
const frequencySection = form.addSubform('frequency', { title: '๐
Schedule' });
frequencySection.addRow(row => {
row.addRadioButton('frequency', {
label: 'How often?',
options: [
{ id: 'once', name: 'One-time / On-call' },
{ id: '2-3', name: '2-3 times per week (-5%)' },
{ id: '4-5', name: '4-5 times per week (-10%)' },
{ id: 'daily', name: 'Daily walks (-15%)' }
],
defaultValue: 'once',
orientation: 'vertical',
isRequired: true
});
});
frequencySection.addRow(row => {
row.addInteger('weeksPerMonth', {
label: 'Weeks per Month',
min: 1,
max: 5,
defaultValue: 4,
tooltip: 'For monthly cost calculation',
isVisible: () => frequencySection.radioButton('frequency')?.value() !== 'once'
}, '1fr');
});
// Add-ons Section
const addonsSection = form.addSubform('addons', { title: 'โจ Add-on Services' });
addonsSection.addRow(row => {
row.addCheckbox('photoUpdates', {
label: 'Photo Updates (+$2)',
defaultValue: true,
tooltip: 'Receive photos during the walk'
}, '1fr');
row.addCheckbox('gpsTracking', {
label: 'GPS Route Tracking (+$3)',
defaultValue: false,
tooltip: 'Share walk route and stats'
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('feeding', {
label: 'Feeding Service (+$5)',
defaultValue: false,
tooltip: 'Feed your dog during visit'
}, '1fr');
row.addCheckbox('playtime', {
label: 'Extra Playtime (+$8)',
defaultValue: false,
tooltip: '15 min of play/training after walk'
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('freshWater', {
label: 'Fresh Water Refill (+$2)',
defaultValue: false
}, '1fr');
row.addCheckbox('medicationAdmin', {
label: 'Medication Administration (+$5)',
defaultValue: false,
tooltip: 'Administer prescribed medication'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Helper to calculate travel fee per walk
const getTravelFeePerWalk = () => {
const addressField = locationSection.address('pickupAddress');
const miles = addressField?.distance();
if (miles == null || miles <= 5) return 0;
if (miles <= 10) return 5;
if (miles <= 15) return 10;
return 15;
};
// Price Summary Section
const summarySection = form.addSubform('summary', { title: '๐ฐ Your Quote', isCollapsible: false });
summarySection.addRow(row => {
row.addPriceDisplay('basePrice', {
label: 'Base Walk Price',
computedValue: () => {
const duration = walkSection.radioButton('walkDuration')?.value() || '30';
const numDogs = dogSection.integer('numberOfDogs')?.value() || 1;
const size = dogSection.dropdown('dogSize')?.value() || 'medium';
const basePrice = walkPrices[duration] || 22;
const sizeAdj = sizeAdjustments[size] || 0;
const additionalDogs = Math.max(0, numDogs - 1) * (basePrice * 0.5);
return basePrice + sizeAdj + additionalDogs;
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('walkExtras', {
label: 'Walk Type & Time',
computedValue: () => {
const walkType = walkSection.dropdown('walkType')?.value() || 'neighborhood';
const timeSlot = walkSection.dropdown('timeSlot')?.value() || 'midday';
let extra = 0;
if (walkType === 'park') extra += 5;
if (walkType === 'trail') extra += 10;
if (walkType === 'beach') extra += 15;
if (timeSlot === 'evening') extra += 3;
return extra;
},
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('specialNeedsPrice', {
label: 'Special Care',
computedValue: () => {
const specialNeeds = dogSection.dropdown('specialNeeds')?.value() || 'none';
if (specialNeeds === 'leash-reactive') return 5;
if (specialNeeds === 'medical') return 5;
if (specialNeeds === 'puppy') return 8;
return 0;
},
variant: 'default',
prefix: '+'
}, '1fr');
row.addPriceDisplay('addonsTotal', {
label: 'Add-ons',
computedValue: () => {
let total = 0;
if (addonsSection.checkbox('photoUpdates')?.value()) total += 2;
if (addonsSection.checkbox('gpsTracking')?.value()) total += 3;
if (addonsSection.checkbox('feeding')?.value()) total += 5;
if (addonsSection.checkbox('playtime')?.value()) total += 8;
if (addonsSection.checkbox('freshWater')?.value()) total += 2;
if (addonsSection.checkbox('medicationAdmin')?.value()) total += 5;
return total;
},
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('frequencyDiscount', {
label: 'Frequency Discount',
computedValue: () => {
const frequency = frequencySection.radioButton('frequency')?.value() || 'once';
const discountPercent = frequencyDiscounts[frequency] || 0;
if (discountPercent === 0) return 0;
// Calculate subtotal
const duration = walkSection.radioButton('walkDuration')?.value() || '30';
const numDogs = dogSection.integer('numberOfDogs')?.value() || 1;
const size = dogSection.dropdown('dogSize')?.value() || 'medium';
const walkType = walkSection.dropdown('walkType')?.value() || 'neighborhood';
const timeSlot = walkSection.dropdown('timeSlot')?.value() || 'midday';
const specialNeeds = dogSection.dropdown('specialNeeds')?.value() || 'none';
let subtotal = walkPrices[duration] || 22;
subtotal += sizeAdjustments[size] || 0;
subtotal += Math.max(0, numDogs - 1) * ((walkPrices[duration] || 22) * 0.5);
if (walkType === 'park') subtotal += 5;
if (walkType === 'trail') subtotal += 10;
if (walkType === 'beach') subtotal += 15;
if (timeSlot === 'evening') subtotal += 3;
if (specialNeeds === 'leash-reactive') subtotal += 5;
if (specialNeeds === 'medical') subtotal += 5;
if (specialNeeds === 'puppy') subtotal += 8;
return -(subtotal * discountPercent / 100);
},
variant: 'success',
prefix: ''
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: () => getTravelFeePerWalk(),
variant: 'default',
prefix: '+',
suffix: '/walk'
});
});
const finalSection = form.addSubform('final', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('perWalkPrice', {
label: 'Price Per Walk',
computedValue: () => {
const duration = walkSection.radioButton('walkDuration')?.value() || '30';
const numDogs = dogSection.integer('numberOfDogs')?.value() || 1;
const size = dogSection.dropdown('dogSize')?.value() || 'medium';
const walkType = walkSection.dropdown('walkType')?.value() || 'neighborhood';
const timeSlot = walkSection.dropdown('timeSlot')?.value() || 'midday';
const specialNeeds = dogSection.dropdown('specialNeeds')?.value() || 'none';
const frequency = frequencySection.radioButton('frequency')?.value() || 'once';
// Base calculation
let total = walkPrices[duration] || 22;
total += sizeAdjustments[size] || 0;
total += Math.max(0, numDogs - 1) * ((walkPrices[duration] || 22) * 0.5);
// Walk extras
if (walkType === 'park') total += 5;
if (walkType === 'trail') total += 10;
if (walkType === 'beach') total += 15;
if (timeSlot === 'evening') total += 3;
// Special needs
if (specialNeeds === 'leash-reactive') total += 5;
if (specialNeeds === 'medical') total += 5;
if (specialNeeds === 'puppy') total += 8;
// Add-ons
if (addonsSection.checkbox('photoUpdates')?.value()) total += 2;
if (addonsSection.checkbox('gpsTracking')?.value()) total += 3;
if (addonsSection.checkbox('feeding')?.value()) total += 5;
if (addonsSection.checkbox('playtime')?.value()) total += 8;
if (addonsSection.checkbox('freshWater')?.value()) total += 2;
if (addonsSection.checkbox('medicationAdmin')?.value()) total += 5;
// Frequency discount
const discountPercent = frequencyDiscounts[frequency] || 0;
total = total * (1 - discountPercent / 100);
// Travel fee
total += getTravelFeePerWalk();
return Math.round(total * 100) / 100;
},
variant: 'large',
suffix: '/walk'
}, '1fr');
row.addPriceDisplay('monthlyEstimate', {
label: 'Monthly Estimate',
computedValue: () => {
const frequency = frequencySection.radioButton('frequency')?.value() || 'once';
if (frequency === 'once') return 0;
const weeksPerMonth = frequencySection.integer('weeksPerMonth')?.value() || 4;
let walksPerWeek = 1;
if (frequency === '2-3') walksPerWeek = 2.5;
if (frequency === '4-5') walksPerWeek = 4.5;
if (frequency === 'daily') walksPerWeek = 7;
// Calculate per-walk price
const duration = walkSection.radioButton('walkDuration')?.value() || '30';
const numDogs = dogSection.integer('numberOfDogs')?.value() || 1;
const size = dogSection.dropdown('dogSize')?.value() || 'medium';
const walkType = walkSection.dropdown('walkType')?.value() || 'neighborhood';
const timeSlot = walkSection.dropdown('timeSlot')?.value() || 'midday';
const specialNeeds = dogSection.dropdown('specialNeeds')?.value() || 'none';
let perWalk = walkPrices[duration] || 22;
perWalk += sizeAdjustments[size] || 0;
perWalk += Math.max(0, numDogs - 1) * ((walkPrices[duration] || 22) * 0.5);
if (walkType === 'park') perWalk += 5;
if (walkType === 'trail') perWalk += 10;
if (walkType === 'beach') perWalk += 15;
if (timeSlot === 'evening') perWalk += 3;
if (specialNeeds === 'leash-reactive') perWalk += 5;
if (specialNeeds === 'medical') perWalk += 5;
if (specialNeeds === 'puppy') perWalk += 8;
if (addonsSection.checkbox('photoUpdates')?.value()) perWalk += 2;
if (addonsSection.checkbox('gpsTracking')?.value()) perWalk += 3;
if (addonsSection.checkbox('feeding')?.value()) perWalk += 5;
if (addonsSection.checkbox('playtime')?.value()) perWalk += 8;
if (addonsSection.checkbox('freshWater')?.value()) perWalk += 2;
if (addonsSection.checkbox('medicationAdmin')?.value()) perWalk += 5;
const discountPercent = frequencyDiscounts[frequency] || 0;
perWalk = perWalk * (1 - discountPercent / 100);
// Add travel fee
perWalk += getTravelFeePerWalk();
return Math.round(perWalk * walksPerWeek * weeksPerMonth);
},
variant: 'default',
suffix: '/month',
isVisible: () => frequencySection.radioButton('frequency')?.value() !== 'once'
}, '1fr');
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'First walk includes a free meet & greet.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Book a Walk'
});
}