export function photographyPackageCalculator(form: FormTs) {
// ==================== HELPER FUNCTIONS ====================
const isWedding = () => sessionSection.dropdown('eventType')?.value() === 'wedding';
const isPortrait = () => ['portrait', 'headshot'].includes(sessionSection.dropdown('eventType')?.value() || '');
const isProduct = () => sessionSection.dropdown('eventType')?.value() === 'product';
const isRealEstate = () => sessionSection.dropdown('eventType')?.value() === 'real-estate';
const isFamily = () => sessionSection.dropdown('eventType')?.value() === 'family';
const isMaternity = () => sessionSection.dropdown('eventType')?.value() === 'maternity';
const isEvent = () => ['event', 'party'].includes(sessionSection.dropdown('eventType')?.value() || '');
const getHourlyRate = () => {
const rates: Record<string, number> = {
'emerging': 150, 'professional': 275, 'expert': 450, 'master': 700
};
return rates[sessionSection.dropdown('experience')?.value() || 'professional'] || 275;
};
// ==================== HEADER ====================
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => '📸 Photography Package Builder',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b', 'text-align': 'center' }
});
});
form.addRow(row => {
row.addTextPanel('subtitle', {
computedValue: () => 'Build your perfect photography package',
customStyles: { 'font-size': '0.95rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.addSpacer({ height: 24 });
// ==================== SESSION TYPE ====================
const sessionSection = form.addSubform('session', { title: '📷 What Are We Shooting?' });
sessionSection.addRow(row => {
row.addDropdown('eventType', {
label: 'Photography Type',
options: [
{ id: 'wedding', name: '💒 Wedding' },
{ id: 'portrait', name: '👤 Portrait / Headshot' },
{ id: 'family', name: '👨👩👧👦 Family Session' },
{ id: 'maternity', name: '🤰 Maternity / Newborn' },
{ id: 'product', name: '📦 Product Photography' },
{ id: 'real-estate', name: '🏠 Real Estate' },
{ id: 'event', name: '🎤 Corporate Event' },
{ id: 'party', name: '🎉 Party / Celebration' }
],
defaultValue: 'wedding',
isRequired: true
}, '1fr');
row.addDropdown('experience', {
label: 'Photographer Experience',
options: [
{ id: 'emerging', name: 'Emerging (1-2 years) - Budget friendly' },
{ id: 'professional', name: 'Professional (3-7 years) - Most popular' },
{ id: 'expert', name: 'Expert (8-15 years) - Premium quality' },
{ id: 'master', name: 'Master / Award-winning - Best of the best' }
],
defaultValue: 'professional'
}, '1fr');
});
// ==================== WEDDING DETAILS ====================
const weddingSection = form.addSubform('wedding', {
title: '💒 Wedding Details',
isVisible: isWedding
});
weddingSection.addRow(row => {
row.addDatepicker('weddingDate', {
label: 'Wedding Date',
isRequired: true,
minDate: () => new Date().toISOString().split('T')[0]
}, '1fr');
row.addTimepicker('ceremonyTime', {
label: 'Ceremony Start Time',
defaultValue: '14:00'
}, '1fr');
});
weddingSection.addRow(row => {
row.addSlider('guestCount', {
label: 'Expected Guest Count',
min: 10,
max: 500,
step: 10,
defaultValue: 100,
unit: 'guests'
});
});
weddingSection.addRow(row => {
row.addRadioButton('weddingPackage', {
label: 'Wedding Package',
options: [
{ id: 'essential', name: 'Essential - Ceremony + Reception (6 hours)' },
{ id: 'complete', name: 'Complete - Getting Ready through Reception (10 hours)' },
{ id: 'luxury', name: 'Luxury - Full Day + Second Shooter (12 hours)' }
],
defaultValue: 'complete',
orientation: 'vertical'
});
});
weddingSection.addRow(row => {
row.addCheckboxList('weddingAddons', {
label: 'Wedding Add-ons',
options: [
{ id: 'engagement', name: 'Engagement Session (+$400)' },
{ id: 'rehearsal', name: 'Rehearsal Dinner Coverage (+$350)' },
{ id: 'bridal', name: 'Bridal Portrait Session (+$300)' },
{ id: 'drone', name: 'Drone Aerial Shots (+$400)' },
{ id: 'sameday', name: 'Same-Day Photo Preview (+$250)' }
],
orientation: 'vertical'
});
});
// ==================== PORTRAIT DETAILS ====================
const portraitSection = form.addSubform('portrait', {
title: '👤 Portrait Details',
isVisible: isPortrait
});
portraitSection.addRow(row => {
row.addRadioButton('portraitType', {
label: 'Session Type',
options: [
{ id: 'individual', name: 'Individual (1 person)' },
{ id: 'couple', name: 'Couple (2 people)' },
{ id: 'group', name: 'Small Group (3-5 people)' },
{ id: 'team', name: 'Team/Corporate (6+ people)' }
],
defaultValue: 'individual',
orientation: 'horizontal'
});
});
portraitSection.addRow(row => {
row.addInteger('headshotCount', {
label: 'Number of People',
min: 1,
max: 50,
defaultValue: 1,
isVisible: () => portraitSection.radioButton('portraitType')?.value() === 'team'
}, '1fr');
row.addDropdown('sessionLength', {
label: 'Session Length',
options: [
{ id: '30', name: '30 minutes - Quick headshots' },
{ id: '60', name: '1 hour - Standard session' },
{ id: '90', name: '1.5 hours - Multiple looks' },
{ id: '120', name: '2 hours - Extended session' }
],
defaultValue: '60'
}, '1fr');
});
portraitSection.addRow(row => {
row.addDropdown('background', {
label: 'Background Preference',
options: [
{ id: 'white', name: 'Clean White Background' },
{ id: 'gray', name: 'Professional Gray' },
{ id: 'environmental', name: 'Environmental (Office/Location)' },
{ id: 'outdoor', name: 'Outdoor Natural Setting' }
],
defaultValue: 'gray'
}, '1fr');
row.addInteger('wardrobeChanges', {
label: 'Wardrobe Changes',
min: 1,
max: 5,
defaultValue: 1,
tooltip: 'Additional outfit changes during session'
}, '1fr');
});
// ==================== FAMILY DETAILS ====================
const familySection = form.addSubform('family', {
title: '👨👩👧👦 Family Session Details',
isVisible: isFamily
});
familySection.addRow(row => {
row.addSlider('familySize', {
label: 'How Many People?',
min: 2,
max: 20,
step: 1,
defaultValue: 4,
unit: 'people'
}, '1fr');
row.addInteger('childrenUnder5', {
label: 'Children Under 5',
min: 0,
max: 10,
defaultValue: 0,
tooltip: 'Young children may need extra time'
}, '1fr');
});
familySection.addRow(row => {
row.addCheckbox('includePets', {
label: 'Include Pets',
defaultValue: false
}, '1fr');
row.addInteger('petCount', {
label: 'Number of Pets',
min: 1,
max: 5,
defaultValue: 1,
isVisible: () => familySection.checkbox('includePets')?.value() === true
}, '1fr');
});
familySection.addRow(row => {
row.addRadioButton('familyLocation', {
label: 'Session Location',
options: [
{ id: 'studio', name: 'Studio - Controlled lighting, classic look' },
{ id: 'outdoor', name: 'Outdoor - Park, beach, or natural setting' },
{ id: 'home', name: 'Your Home - Lifestyle, documentary style' }
],
defaultValue: 'outdoor',
orientation: 'vertical'
});
});
// ==================== MATERNITY DETAILS ====================
const maternitySection = form.addSubform('maternity', {
title: '🤰 Maternity / Newborn Details',
isVisible: isMaternity
});
maternitySection.addRow(row => {
row.addRadioButton('maternityType', {
label: 'Session Type',
options: [
{ id: 'maternity', name: 'Maternity Only' },
{ id: 'newborn', name: 'Newborn Only' },
{ id: 'both', name: 'Maternity + Newborn Bundle (Save 15%)' }
],
defaultValue: 'maternity',
orientation: 'vertical'
});
});
maternitySection.addRow(row => {
row.addDatepicker('dueDate', {
label: 'Due Date',
isVisible: () => ['maternity', 'both'].includes(maternitySection.radioButton('maternityType')?.value() || '')
}, '1fr');
row.addCheckbox('includeSiblings', {
label: 'Include Siblings',
defaultValue: false
}, '1fr');
});
maternitySection.addRow(row => {
row.addCheckboxList('maternityOptions', {
label: 'Session Options',
options: [
{ id: 'partner', name: 'Include Partner' },
{ id: 'props', name: 'Professional Props & Wraps' },
{ id: 'outfits', name: 'Wardrobe Provided (Gowns/Wraps)' },
{ id: 'milk-bath', name: 'Milk Bath Setup (Maternity)' }
],
orientation: 'vertical'
});
});
// ==================== PRODUCT DETAILS ====================
const productSection = form.addSubform('product', {
title: '📦 Product Photography Details',
isVisible: isProduct
});
productSection.addRow(row => {
row.addInteger('productCount', {
label: 'Number of Products',
min: 1,
max: 100,
defaultValue: 5
}, '1fr');
row.addInteger('anglesPerProduct', {
label: 'Angles Per Product',
min: 1,
max: 10,
defaultValue: 3,
tooltip: 'Front, side, detail shots, etc.'
}, '1fr');
});
productSection.addRow(row => {
row.addRadioButton('productStyle', {
label: 'Photography Style',
options: [
{ id: 'white', name: 'White Background (E-commerce)' },
{ id: 'lifestyle', name: 'Lifestyle (In-use context)' },
{ id: 'both', name: 'Both Styles' }
],
defaultValue: 'white',
orientation: 'horizontal'
});
});
productSection.addRow(row => {
row.addCheckboxList('productServices', {
label: 'Additional Services',
options: [
{ id: 'styling', name: 'Professional Styling (+$150)' },
{ id: 'retouching', name: 'Advanced Retouching (+$10/image)' },
{ id: '360', name: '360° Spin Photography (+$50/product)' },
{ id: 'video', name: 'Product Video Clips (+$200)' }
],
orientation: 'vertical'
});
});
// ==================== REAL ESTATE DETAILS ====================
const realEstateSection = form.addSubform('realEstate', {
title: '🏠 Real Estate Details',
isVisible: isRealEstate
});
realEstateSection.addRow(row => {
row.addDropdown('propertyType', {
label: 'Property Type',
options: [
{ id: 'apartment', name: 'Apartment / Condo' },
{ id: 'house', name: 'Single Family Home' },
{ id: 'luxury', name: 'Luxury Property' },
{ id: 'commercial', name: 'Commercial Space' }
],
defaultValue: 'house'
}, '1fr');
row.addSlider('sqft', {
label: 'Square Footage',
min: 500,
max: 10000,
step: 100,
defaultValue: 2000,
unit: 'sq ft'
}, '1fr');
});
realEstateSection.addRow(row => {
row.addCheckboxList('realEstateServices', {
label: 'Services Needed',
options: [
{ id: 'photos', name: 'Interior & Exterior Photos' },
{ id: 'aerial', name: 'Aerial/Drone Photography (+$200)' },
{ id: 'twilight', name: 'Twilight Shots (+$150)' },
{ id: 'virtual', name: 'Virtual Tour / 3D (+$300)' },
{ id: 'video', name: 'Property Video Walkthrough (+$400)' },
{ id: 'floorplan', name: 'Floor Plan (+$100)' }
],
orientation: 'vertical',
defaultValue: ['photos']
});
});
// ==================== EVENT DETAILS ====================
const eventSection = form.addSubform('event', {
title: '🎤 Event Details',
isVisible: isEvent
});
eventSection.addRow(row => {
row.addDatepicker('eventDate', {
label: 'Event Date',
isRequired: true
}, '1fr');
row.addSlider('eventDuration', {
label: 'Event Duration',
min: 1,
max: 12,
step: 1,
defaultValue: 4,
unit: 'hours'
}, '1fr');
});
eventSection.addRow(row => {
row.addSlider('eventAttendees', {
label: 'Expected Attendees',
min: 10,
max: 1000,
step: 10,
defaultValue: 100,
unit: 'people'
});
});
eventSection.addRow(row => {
row.addCheckboxList('eventCoverage', {
label: 'Coverage Needed',
options: [
{ id: 'candid', name: 'Candid Event Coverage' },
{ id: 'speakers', name: 'Speaker/Presenter Shots' },
{ id: 'headshots', name: 'Attendee Headshots Station' },
{ id: 'group', name: 'Group Photos' },
{ id: 'branding', name: 'Branding/Signage Documentation' }
],
orientation: 'vertical',
defaultValue: ['candid']
});
});
// ==================== DELIVERABLES ====================
form.addSpacer({ height: 10, showLine: true, lineStyle: 'dashed' });
const deliverablesSection = form.addSubform('deliverables', { title: '🖼️ What You\'ll Receive' });
deliverablesSection.addRow(row => {
row.addDropdown('editedPhotos', {
label: 'Edited Photos',
options: () => {
if (isRealEstate()) {
return [
{ id: '15', name: '15 Photos - Small property' },
{ id: '25', name: '25 Photos - Standard' },
{ id: '40', name: '40 Photos - Large property' },
{ id: '60', name: '60 Photos - Luxury/Commercial' }
];
}
if (isProduct()) {
return [
{ id: '10', name: '10 Photos' },
{ id: '25', name: '25 Photos' },
{ id: '50', name: '50 Photos' },
{ id: '100', name: '100 Photos' }
];
}
return [
{ id: '50', name: '50 Photos' },
{ id: '100', name: '100 Photos' },
{ id: '200', name: '200 Photos' },
{ id: '300', name: '300 Photos' },
{ id: '500', name: '500 Photos' },
{ id: 'unlimited', name: 'Unlimited (All Keepers)' }
];
},
defaultValue: () => isRealEstate() ? '25' : isProduct() ? '25' : '200'
}, '1fr');
row.addDropdown('turnaround', {
label: 'Delivery Time',
options: [
{ id: 'rush', name: 'Rush - 48 hours (+$300)' },
{ id: 'fast', name: 'Fast - 1 week (+$150)' },
{ id: 'standard', name: 'Standard - 2-3 weeks' },
{ id: 'relaxed', name: 'Relaxed - 4-6 weeks (-$100)' }
],
defaultValue: 'standard'
}, '1fr');
});
deliverablesSection.addRow(row => {
row.addCheckbox('onlineGallery', {
label: 'Private Online Gallery (1 year hosting)',
defaultValue: true
}, '1fr');
row.addCheckbox('printRights', {
label: 'Full Print Rights & Commercial License',
defaultValue: true
}, '1fr');
});
// ==================== PRINTS & ALBUMS (conditional) ====================
const printsSection = form.addSubform('prints', {
title: '📚 Prints & Albums',
isVisible: () => !isProduct() && !isRealEstate()
});
printsSection.addRow(row => {
row.addDropdown('printPackage', {
label: 'Print Package',
options: [
{ id: 'none', name: 'No Prints - Digital only' },
{ id: 'starter', name: 'Starter - 10 prints (+$150)' },
{ id: 'standard', name: 'Standard - 25 prints + canvas (+$400)' },
{ id: 'premium', name: 'Premium - 50 prints + 2 canvases (+$750)' }
],
defaultValue: 'none'
}, '1fr');
row.addDropdown('album', {
label: 'Photo Album',
options: [
{ id: 'none', name: 'No Album' },
{ id: 'mini', name: 'Mini Softcover (20 pages) - +$200' },
{ id: 'standard', name: 'Standard Hardcover (30 pages) - +$450' },
{ id: 'premium', name: 'Premium Leather (50 pages) - +$800' },
{ id: 'luxury', name: 'Luxury Heirloom (60+ pages) - +$1,200' }
],
defaultValue: 'none',
isVisible: () => isWedding() || isFamily() || isMaternity()
}, '1fr');
});
printsSection.addRow(row => {
row.addInteger('parentAlbums', {
label: 'Parent/Grandparent Albums (smaller copies)',
min: 0,
max: 6,
defaultValue: 0,
isVisible: () => printsSection.dropdown('album')?.value() !== 'none' && isWedding(),
tooltip: '$150 each - smaller duplicates of main album'
});
});
// ==================== PRICE SUMMARY ====================
form.addSpacer({ height: 20, showLine: true, lineStyle: 'solid' });
const priceSection = form.addSubform('price', { title: '💰 Your Quote', isCollapsible: false });
const calculateTotal = () => {
let total = 0;
const hourlyRate = getHourlyRate();
// Wedding pricing
if (isWedding()) {
const packagePrices: Record<string, number> = {
'essential': hourlyRate * 6,
'complete': hourlyRate * 10,
'luxury': hourlyRate * 12 * 1.5 // includes second shooter
};
total += packagePrices[weddingSection.radioButton('weddingPackage')?.value() || 'complete'] || 0;
// Wedding add-ons
const addons = weddingSection.checkboxList('weddingAddons')?.value() || [];
if (addons.includes('engagement')) total += 400;
if (addons.includes('rehearsal')) total += 350;
if (addons.includes('bridal')) total += 300;
if (addons.includes('drone')) total += 400;
if (addons.includes('sameday')) total += 250;
// Guest count affects editing time
const guests = weddingSection.slider('guestCount')?.value() || 100;
if (guests > 200) total += 300;
else if (guests > 100) total += 150;
}
// Portrait pricing
if (isPortrait()) {
const sessionMin = parseInt(portraitSection.dropdown('sessionLength')?.value() || '60');
total += hourlyRate * (sessionMin / 60);
// Team/group pricing
if (portraitSection.radioButton('portraitType')?.value() === 'team') {
const people = portraitSection.integer('headshotCount')?.value() || 1;
total += (people - 1) * 75; // per additional person
}
// Wardrobe changes
const changes = portraitSection.integer('wardrobeChanges')?.value() || 1;
if (changes > 1) total += (changes - 1) * 50;
}
// Family pricing
if (isFamily()) {
total += hourlyRate * 1.5; // 1.5 hour base
const size = familySection.slider('familySize')?.value() || 4;
if (size > 6) total += (size - 6) * 25;
const kids = familySection.integer('childrenUnder5')?.value() || 0;
if (kids >= 2) total += 100; // extra time for young children
if (familySection.checkbox('includePets')?.value()) {
total += 75 * (familySection.integer('petCount')?.value() || 1);
}
}
// Maternity pricing
if (isMaternity()) {
const type = maternitySection.radioButton('maternityType')?.value();
if (type === 'maternity') total += hourlyRate * 1.5;
else if (type === 'newborn') total += hourlyRate * 3;
else if (type === 'both') total += hourlyRate * 4 * 0.85; // 15% bundle discount
const options = maternitySection.checkboxList('maternityOptions')?.value() || [];
if (options.includes('props')) total += 100;
if (options.includes('outfits')) total += 75;
if (options.includes('milk-bath')) total += 150;
if (maternitySection.checkbox('includeSiblings')?.value()) total += 100;
}
// Product pricing
if (isProduct()) {
const products = productSection.integer('productCount')?.value() || 5;
const angles = productSection.integer('anglesPerProduct')?.value() || 3;
const style = productSection.radioButton('productStyle')?.value();
let perProduct = 40;
if (style === 'lifestyle') perProduct = 60;
if (style === 'both') perProduct = 85;
total += products * perProduct * (angles > 3 ? 1 + (angles - 3) * 0.2 : 1);
const services = productSection.checkboxList('productServices')?.value() || [];
if (services.includes('styling')) total += 150;
if (services.includes('retouching')) total += products * angles * 10;
if (services.includes('360')) total += products * 50;
if (services.includes('video')) total += 200;
}
// Real estate pricing
if (isRealEstate()) {
const sqft = realEstateSection.slider('sqft')?.value() || 2000;
const type = realEstateSection.dropdown('propertyType')?.value();
let base = 200;
if (sqft > 3000) base = 300;
if (sqft > 5000) base = 400;
if (type === 'luxury') base *= 1.5;
if (type === 'commercial') base *= 1.3;
total += base;
const services = realEstateSection.checkboxList('realEstateServices')?.value() || [];
if (services.includes('aerial')) total += 200;
if (services.includes('twilight')) total += 150;
if (services.includes('virtual')) total += 300;
if (services.includes('video')) total += 400;
if (services.includes('floorplan')) total += 100;
}
// Event pricing
if (isEvent()) {
const hours = eventSection.slider('eventDuration')?.value() || 4;
total += hourlyRate * hours;
const attendees = eventSection.slider('eventAttendees')?.value() || 100;
if (attendees > 200) total += 200;
if (attendees > 500) total += 300;
const coverage = eventSection.checkboxList('eventCoverage')?.value() || [];
if (coverage.includes('headshots')) total += 300;
if (coverage.includes('group')) total += 100;
}
// Deliverables pricing
const photos = deliverablesSection.dropdown('editedPhotos')?.value() || '200';
const photoCount = photos === 'unlimited' ? 500 : parseInt(photos);
total += photoCount * 3; // base editing cost
const turnaround = deliverablesSection.dropdown('turnaround')?.value();
if (turnaround === 'rush') total += 300;
if (turnaround === 'fast') total += 150;
if (turnaround === 'relaxed') total -= 100;
if (deliverablesSection.checkbox('onlineGallery')?.value()) total += 50;
// Prints & Albums
if (!isProduct() && !isRealEstate()) {
const printPkg = printsSection.dropdown('printPackage')?.value();
if (printPkg === 'starter') total += 150;
if (printPkg === 'standard') total += 400;
if (printPkg === 'premium') total += 750;
const album = printsSection.dropdown('album')?.value();
if (album === 'mini') total += 200;
if (album === 'standard') total += 450;
if (album === 'premium') total += 800;
if (album === 'luxury') total += 1200;
const parentAlbums = printsSection.integer('parentAlbums')?.value() || 0;
total += parentAlbums * 150;
}
return Math.round(total);
};
priceSection.addRow(row => {
row.addPriceDisplay('total', {
label: 'Estimated Package Price',
computedValue: calculateTotal,
variant: 'large'
});
});
priceSection.addRow(row => {
row.addTextPanel('deposit', {
computedValue: () => {
const total = calculateTotal();
const deposit = Math.round(total * 0.3);
return `30% deposit to book: $${deposit.toLocaleString()}`;
},
customStyles: { 'font-size': '0.95rem', 'color': '#059669', 'font-weight': '500' }
});
});
// ==================== SUMMARY ====================
const summarySection = form.addSubform('summary', {
title: '📋 Package Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addTextPanel('summaryText', {
computedValue: () => {
const type = sessionSection.dropdown('eventType')?.value();
const labels: Record<string, string> = {
'wedding': '💒 Wedding Photography',
'portrait': '👤 Portrait Session',
'family': '👨👩👧👦 Family Session',
'maternity': '🤰 Maternity/Newborn',
'product': '📦 Product Photography',
'real-estate': '🏠 Real Estate',
'event': '🎤 Event Coverage',
'party': '🎉 Party Coverage'
};
const photos = deliverablesSection.dropdown('editedPhotos')?.value();
const photoLabel = photos === 'unlimited' ? 'Unlimited' : photos;
return `${labels[type || 'wedding']} • ${photoLabel} edited photos`;
},
customStyles: { 'font-size': '1rem', 'font-weight': '500', 'text-align': 'center' }
});
});
summarySection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Final pricing confirmed after consultation. Travel fees may apply.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Request This Package'
});
}