diff --git a/src/components/cost-estimator/cost-estimator-form.tsx b/src/components/cost-estimator/cost-estimator-form.tsx index 1287038..b5bd3a6 100644 --- a/src/components/cost-estimator/cost-estimator-form.tsx +++ b/src/components/cost-estimator/cost-estimator-form.tsx @@ -9,7 +9,8 @@ import { Step5PageCount } from './step-5-page-count'; import { Step6Animations } from './step-6-animations'; import { Step7Illustrations } from './step-7-illustrations'; import { Step8Branding } from './step-8-branding'; -import { Step9Results, calculateTotalHours } from './step-9-results'; +import { Step9AdditionalFeatures } from './step-9-additional-features'; +import { Step10Results, calculateTotalHours } from './step-10-results'; import { Card, CardContent } from '@/components/ui/card'; import { AnimatePresence, motion } from 'framer-motion'; @@ -31,6 +32,7 @@ export type FormData = { animatedElements: boolean | null; illustrations: IllustrationSelection; branding: BrandingSelection; + additionalFeatures: string[]; }; export function CostEstimatorForm() { @@ -49,6 +51,7 @@ export function CostEstimatorForm() { is3dAnimated: null, }, branding: null, + additionalFeatures: [], }); const [isPending, startTransition] = useTransition(); @@ -83,6 +86,7 @@ export function CostEstimatorForm() { is3dAnimated: null, }, branding: null, + additionalFeatures: [], }); setCurrentStep(1); } @@ -164,7 +168,16 @@ export function CostEstimatorForm() { ); case 9: return ( - + ); + case 10: + return ( + void; estimatedHours: number; @@ -15,6 +15,21 @@ type Step9Props = { const designHoursMap = { custom: 60, mockups: 30, existing: 0 }; const illustrationHoursMap = { '2d_static': 10, '2d_animated': 25, '3d_static': 20, '3d_animated': 40 }; const brandingHoursMap = { 'full-cycle': 70, 'brush-up': 30, 'logo-only': 20, 'none': 0 }; +const featuresHoursMap: Record = { + 'registration': 40, + 'member-profiles': 30, + 'admin-panel': 80, + 'crm-integration': 60, + 'dashboard': 50, + 'blog': 25, + 'event-scheduling': 40, + 'reservations': 40, + 'chat-live-chat': 60, + 'image-video-galleries': 30, + 'location-based': 35, + 'live-streaming': 70, +}; + export const calculateTotalHours = (formData: FormData): number => { const pageVal = formData.pageCount === 10 ? 50 : (formData.pageCount + 1) * 5 - 1; @@ -35,12 +50,16 @@ export const calculateTotalHours = (formData: FormData): number => { const brandingH = formData.branding ? (brandingHoursMap[formData.branding] ?? 0) : 0; - return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH; + const additionalFeaturesHours = formData.additionalFeatures.reduce((total, feature) => { + return total + (featuresHoursMap[feature] || 0); + }, 0); + + return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours; }; const HOURLY_RATE = 75; // Example hourly rate in USD -export function Step9Results({ formData, onReset, estimatedHours }: Step9Props) { +export function Step10Results({ formData, onReset, estimatedHours }: Step10Props) { const estimatedCost = estimatedHours * HOURLY_RATE; diff --git a/src/components/cost-estimator/step-2-service-type.tsx b/src/components/cost-estimator/step-2-service-type.tsx index 0be0d3b..fec9cc9 100644 --- a/src/components/cost-estimator/step-2-service-type.tsx +++ b/src/components/cost-estimator/step-2-service-type.tsx @@ -56,14 +56,15 @@ export function Step2ServiceType({ onNext, onBack, onUpdateData, formData }: Ste
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- +
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- +
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- +
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- +
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- +
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/components/cost-estimator/step-9-additional-features.tsx b/src/components/cost-estimator/step-9-additional-features.tsx new file mode 100644 index 0000000..1fb0aea --- /dev/null +++ b/src/components/cost-estimator/step-9-additional-features.tsx @@ -0,0 +1,136 @@ +"use client"; + +import type { FormData } from './cost-estimator-form'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import { Gauge, Info } from 'lucide-react'; +import React, { useState, useMemo } from 'react'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; + +type Step9Props = { + onNext: () => void; + onBack: () => void; + onUpdateData: (data: Partial) => void; + formData: FormData; +}; + +const features = [ + { id: 'registration', label: 'Registration & authorization', hours: 40, description: 'User accounts, login, registration, and password recovery.' }, + { id: 'member-profiles', label: 'Member profiles', hours: 30, description: 'User profiles with customizable information and settings.' }, + { id: 'admin-panel', label: 'Admin panel', hours: 80, description: 'A comprehensive backend interface to manage the platform.' }, + { id: 'crm-integration', label: 'CRM Integration', hours: 60, description: 'Integration with a Customer Relationship Management system.' }, + { id: 'dashboard', label: 'Dashboard', hours: 50, description: 'A dashboard for users to view key information and metrics.' }, + { id: 'blog', label: 'Blog', hours: 25, description: 'A blog or news section for content publication.' }, + { id: 'event-scheduling', label: 'Event scheduling', hours: 40, description: 'Functionality for scheduling and managing events.' }, + { id: 'reservations', label: 'Reservations', hours: 40, description: 'A system for making and managing reservations.' }, + { id: 'chat-live-chat', label: 'Chat/Live chat', hours: 60, description: 'Real-time chat functionality for users or support.' }, + { id: 'image-video-galleries', label: 'Image/Video galleries', hours: 30, description: 'Galleries to display images and videos.' }, + { id: 'location-based', label: 'Location-based or navigational elements', hours: 35, description: 'Features that use location data, like maps or navigation.' }, + { id: 'live-streaming', label: 'Live-streaming', hours: 70, description: 'Functionality for live video streaming.' }, +]; + +const designHoursMap = { custom: 60, mockups: 30, existing: 0 }; +const illustrationHoursMap = { '2d_static': 10, '2d_animated': 25, '3d_static': 20, '3d_animated': 40 }; +const brandingHoursMap = { 'full-cycle': 70, 'brush-up': 30, 'logo-only': 20, 'none': 0 }; + +const calculateHours = (formData: FormData, selectedFeatures: string[]): number => { + const pageVal = formData.pageCount === 10 ? 50 : (formData.pageCount + 1) * 5 - 1; + const pageHours = pageVal * 6; + const stageHours = Math.round((formData.projectStage / 100) * 50); + const designHours = formData.designProcess ? designHoursMap[formData.designProcess] : 0; + const animationHours = formData.animatedElements ? 30 : 0; + + let illustrationTotalHours = 0; + if (formData.illustrations.has2d) { + if (formData.illustrations.is2dAnimated === 'static') illustrationTotalHours += illustrationHoursMap['2d_static']; + if (formData.illustrations.is2dAnimated === 'animated') illustrationTotalHours += illustrationHoursMap['2d_animated']; + } + if (formData.illustrations.has3d) { + if (formData.illustrations.is3dAnimated === 'static') illustrationTotalHours += illustrationHoursMap['3d_static']; + if (formData.illustrations.is3dAnimated === 'animated') illustrationTotalHours += illustrationHoursMap['3d_animated']; + } + + const brandingH = formData.branding ? (brandingHoursMap[formData.branding] ?? 0) : 0; + + const additionalFeaturesHours = selectedFeatures.reduce((total, featureId) => { + const feature = features.find(f => f.id === featureId); + return total + (feature ? feature.hours : 0); + }, 0); + + return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours; +}; + + +export function Step9AdditionalFeatures({ onNext, onBack, onUpdateData, formData }: Step9Props) { + const [selectedFeatures, setSelectedFeatures] = useState(formData.additionalFeatures); + + const handleSelect = (featureId: string) => { + const newSelection = selectedFeatures.includes(featureId) + ? selectedFeatures.filter((id) => id !== featureId) + : [...selectedFeatures, featureId]; + setSelectedFeatures(newSelection); + onUpdateData({ additionalFeatures: newSelection }); + }; + + const estimatedHours = useMemo(() => { + return calculateHours(formData, selectedFeatures); + }, [selectedFeatures, formData]); + + return ( +
+

Would you like us to develop any additional features?

+
+ {features.map((feature) => ( +
handleSelect(feature.id)} + className={`flex items-center space-x-3 rounded-lg border p-4 cursor-pointer transition-all ${selectedFeatures.includes(feature.id) ? 'border-primary bg-primary/5' : 'border-border'}`} + > + handleSelect(feature.id)} + /> + + + + + + + +

{feature.description}

+
+
+
+
+ ))} +
+
+
+ + {estimatedHours}+ hours +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ ); +}