if yes is selected, this options show up
This commit is contained in:
@@ -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">
|
||||||
<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>
|
||||||
<div className="mt-8 grid w-full grid-cols-1 gap-4 md:grid-cols-2">
|
<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>
|
||||||
<Button
|
<div className="mt-8 grid w-full grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
variant={selectedOption === true ? 'default' : 'outline'}
|
<Button
|
||||||
className="w-full justify-start p-6 text-lg"
|
variant={selections.hasCart === true ? 'default' : 'outline'}
|
||||||
onClick={() => handleSelect(true)}
|
className="w-full justify-start p-6 text-lg"
|
||||||
>
|
onClick={() => handleSelect('hasCart', true)}
|
||||||
Yes
|
>
|
||||||
</Button>
|
Yes
|
||||||
<Button
|
</Button>
|
||||||
variant={selectedOption === false ? 'default' : 'outline'}
|
<Button
|
||||||
className="w-full justify-start p-6 text-lg"
|
variant={selections.hasCart === false ? 'default' : 'outline'}
|
||||||
onClick={() => handleSelect(false)}
|
className="w-full justify-start p-6 text-lg"
|
||||||
>
|
onClick={() => handleSelect('hasCart', false)}
|
||||||
No
|
>
|
||||||
</Button>
|
No
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-16 flex w-full items-center justify-between">
|
|
||||||
|
<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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user