if yes is selected, this options show up

This commit is contained in:
Leon Serfaty G
2025-07-17 10:52:57 +00:00
parent 5f1c344406
commit f5294a148d
3 changed files with 107 additions and 34 deletions
@@ -25,6 +25,11 @@ export type IllustrationSelection = {
export type BrandingSelection = 'full-cycle' | 'brush-up' | 'logo-only' | 'none' | null; export type BrandingSelection = 'full-cycle' | 'brush-up' | 'logo-only' | 'none' | null;
export type ShoppingCartSelection = {
hasCart: boolean | null;
multiplePurchases: boolean | null;
}
export type FormData = { export type FormData = {
projectType: 'website' | 'mobile-app' | 'platform' | null; projectType: 'website' | 'mobile-app' | 'platform' | null;
serviceType: 'entire-project' | 'development' | 'ui-ux-design' | 'identity-branding' | null; serviceType: 'entire-project' | 'development' | 'ui-ux-design' | 'identity-branding' | null;
@@ -35,7 +40,7 @@ export type FormData = {
illustrations: IllustrationSelection; illustrations: IllustrationSelection;
branding: BrandingSelection; branding: BrandingSelection;
additionalFeatures: string[]; additionalFeatures: string[];
shoppingCart: boolean | null; shoppingCart: ShoppingCartSelection;
}; };
export function CostEstimatorForm() { export function CostEstimatorForm() {
@@ -55,7 +60,10 @@ export function CostEstimatorForm() {
}, },
branding: null, branding: null,
additionalFeatures: [], additionalFeatures: [],
shoppingCart: null, shoppingCart: {
hasCart: null,
multiplePurchases: null,
},
}); });
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
@@ -91,7 +99,10 @@ export function CostEstimatorForm() {
}, },
branding: null, branding: null,
additionalFeatures: [], additionalFeatures: [],
shoppingCart: null, shoppingCart: {
hasCart: null,
multiplePurchases: null
},
}); });
setCurrentStep(1); setCurrentStep(1);
} }
@@ -1,10 +1,11 @@
"use client"; "use client";
import type { FormData } from './cost-estimator-form'; import type { FormData, ShoppingCartSelection } from './cost-estimator-form';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Gauge } from 'lucide-react'; import { Gauge } from 'lucide-react';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
type Step10Props = { type Step10Props = {
onNext: () => void; onNext: () => void;
@@ -30,11 +31,14 @@ const featuresHoursMap: Record<string, number> = {
'location-based': 40, 'location-based': 40,
'live-streaming': 55, 'live-streaming': 55,
}; };
const shoppingCartHours = 45; const shoppingCartHoursMap = {
simple: 45,
multiple: 20
};
const calculateHours = ( const calculateHours = (
formData: FormData, formData: FormData,
shoppingCart: boolean | null shoppingCart: ShoppingCartSelection | null
): number => { ): number => {
const pageVal = formData.pageCount === 10 ? 50 : (formData.pageCount + 1) * 5 - 1; const pageVal = formData.pageCount === 10 ? 50 : (formData.pageCount + 1) * 5 - 1;
const pageHours = pageVal * 6; const pageHours = pageVal * 6;
@@ -58,43 +62,91 @@ const calculateHours = (
return total + (featuresHoursMap[feature] || 0); return total + (featuresHoursMap[feature] || 0);
}, 0); }, 0);
const cartHours = shoppingCart ? shoppingCartHours : 0; let cartHours = 0;
if (shoppingCart?.hasCart) {
cartHours += shoppingCartHoursMap.simple;
if (shoppingCart.multiplePurchases) {
cartHours += shoppingCartHoursMap.multiple;
}
}
return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours + cartHours; return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours + cartHours;
}; };
export function Step10ShoppingCart({ onNext, onBack, onUpdateData, formData }: Step10Props) { export function Step10ShoppingCart({ onNext, onBack, onUpdateData, formData }: Step10Props) {
const [selectedOption, setSelectedOption] = useState<boolean | null>(formData.shoppingCart); const [selections, setSelections] = useState<ShoppingCartSelection>(formData.shoppingCart);
const handleSelect = (option: boolean) => { const handleSelect = (field: keyof ShoppingCartSelection, value: boolean) => {
setSelectedOption(option); const newSelections = { ...selections, [field]: value };
onUpdateData({ shoppingCart: option }); if (field === 'hasCart' && value === false) {
newSelections.multiplePurchases = null;
}
setSelections(newSelections);
onUpdateData({ shoppingCart: newSelections });
}; };
const estimatedHours = useMemo(() => { const estimatedHours = useMemo(() => {
return calculateHours(formData, selectedOption); return calculateHours(formData, selections);
}, [selectedOption, formData]); }, [selections, formData]);
const isNextDisabled = selections.hasCart === null || (selections.hasCart === true && selections.multiplePurchases === null);
return ( return (
<div className="flex flex-col items-start"> <div className="flex flex-col items-start space-y-8">
<div>
<h2 className="font-headline text-3xl font-bold tracking-tight text-center w-full">Do you need a shopping cart for buying multiple products with one check-out?</h2> <h2 className="font-headline text-3xl font-bold tracking-tight text-center w-full">Do you need a shopping cart for buying multiple products with one check-out?</h2>
<div className="mt-8 grid w-full grid-cols-1 gap-4 md:grid-cols-2"> <div className="mt-8 grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<Button <Button
variant={selectedOption === true ? 'default' : 'outline'} variant={selections.hasCart === true ? 'default' : 'outline'}
className="w-full justify-start p-6 text-lg" className="w-full justify-start p-6 text-lg"
onClick={() => handleSelect(true)} onClick={() => handleSelect('hasCart', true)}
> >
Yes Yes
</Button> </Button>
<Button <Button
variant={selectedOption === false ? 'default' : 'outline'} variant={selections.hasCart === false ? 'default' : 'outline'}
className="w-full justify-start p-6 text-lg" className="w-full justify-start p-6 text-lg"
onClick={() => handleSelect(false)} onClick={() => handleSelect('hasCart', false)}
> >
No No
</Button> </Button>
</div> </div>
<div className="mt-16 flex w-full items-center justify-between"> </div>
<AnimatePresence>
{selections.hasCart && (
<motion.div
key="multiple-purchases"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="w-full overflow-hidden"
>
<div>
<h2 className="font-headline text-3xl font-bold tracking-tight text-center w-full">How about a shopping cart to make multiple purchases at once?</h2>
<div className="mt-8 grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<Button
variant={selections.multiplePurchases === true ? 'default' : 'outline'}
className="w-full justify-start p-6 text-lg"
onClick={() => handleSelect('multiplePurchases', true)}
>
Yes
</Button>
<Button
variant={selections.multiplePurchases === false ? 'default' : 'outline'}
className="w-full justify-start p-6 text-lg"
onClick={() => handleSelect('multiplePurchases', false)}
>
No
</Button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
<div className="mt-8 flex w-full items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Gauge className="h-6 w-6 text-primary" /> <Gauge className="h-6 w-6 text-primary" />
<span className="font-headline text-lg font-bold">{estimatedHours}+ hours</span> <span className="font-headline text-lg font-bold">{estimatedHours}+ hours</span>
@@ -113,7 +165,7 @@ export function Step10ShoppingCart({ onNext, onBack, onUpdateData, formData }: S
<div className="w-10 h-2 bg-primary rounded-full" /> <div className="w-10 h-2 bg-primary rounded-full" />
<div className="w-10 h-2 bg-primary rounded-full" /> <div className="w-10 h-2 bg-primary rounded-full" />
</div> </div>
<Button onClick={onNext} disabled={selectedOption === null}> <Button onClick={onNext} disabled={isNextDisabled}>
Submit Submit
</Button> </Button>
</div> </div>
@@ -1,3 +1,4 @@
"use client"; "use client";
import type { FormData } from './cost-estimator-form'; import type { FormData } from './cost-estimator-form';
@@ -29,7 +30,10 @@ const featuresHoursMap: Record<string, number> = {
'location-based': 40, 'location-based': 40,
'live-streaming': 55, 'live-streaming': 55,
}; };
const shoppingCartHours = 45; const shoppingCartHoursMap = {
simple: 45,
multiple: 20
};
export const calculateTotalHours = (formData: FormData): number => { export const calculateTotalHours = (formData: FormData): number => {
@@ -55,7 +59,13 @@ export const calculateTotalHours = (formData: FormData): number => {
return total + (featuresHoursMap[feature] || 0); return total + (featuresHoursMap[feature] || 0);
}, 0); }, 0);
const cartHours = formData.shoppingCart ? shoppingCartHours : 0; let cartHours = 0;
if (formData.shoppingCart?.hasCart) {
cartHours += shoppingCartHoursMap.simple;
if (formData.shoppingCart.multiplePurchases) {
cartHours += shoppingCartHoursMap.multiple;
}
}
return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours + cartHours; return pageHours + stageHours + designHours + animationHours + illustrationTotalHours + brandingH + additionalFeaturesHours + cartHours;
}; };