Skip to content

Commit

Permalink
feat: calculator fixups and polish (#200)
Browse files Browse the repository at this point in the history
This PR incorporates various fixes and improvements for the calculator
page based on feedback.
- Calculator numbers update live as inputs change
- Homepage calculator widget shows placeholder / blank numbers once
inputs are changed from default values (so the user clicks "calculate"
to see new numbers)
- Calculator validates inputs from url parameters to ensure they are in
range
- Calculator page has its own more minimal demo CTA component, different
from the homepage
  • Loading branch information
skylarmb authored Sep 10, 2024
1 parent b65a916 commit a773cd9
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 86 deletions.
21 changes: 3 additions & 18 deletions website/app/calculator/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Metadata } from "next";

import { ButtonPrimary } from "@/components/Button";
import Calculator from "@/components/Calculator";
import CTA from "@/components/CTA";
import CTASmall from "@/components/CTASmall";
import CTADemo from "@/components/CTADemo";
import SavingsGraph from "@/components/SavingsGraph";
import Spacer from "@/components/Spacer";
import { TextBase } from "@/components/Text";
Expand All @@ -17,7 +16,7 @@ const Page = () => {
return (
<>
<CTA
style={{ maxHeight: 640 }}
style={{ maxHeight: 540, minHeight: 540 }}
imageUrl={null}
buttonText={null}
fullHeight
Expand All @@ -39,21 +38,7 @@ const Page = () => {

<SavingsGraph />

<CTASmall heading={"Want a demo?"} hasBackground>
<TextBase>
Use the link below to book <br data-desktop /> a personalized demo of
Kardinal.
</TextBase>
<ButtonPrimary
analyticsId="button_calculator_cta_get_demo"
href="https://calendly.com/d/cqhd-tgj-vmc/45-minute-meeting"
rel="noopener noreferrer"
target="_blank"
size="lg"
>
Get a Demo
</ButtonPrimary>
</CTASmall>
<CTADemo />
</>
);
};
Expand Down
3 changes: 2 additions & 1 deletion website/components/CTAButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import styled from "styled-components";

import { ButtonPrimary, ButtonTertiary } from "@/components/Button";
import { mobile } from "@/constants/breakpoints";
import { calendlyDemoUrl } from "@/constants/urls";

const CTAButtons = () => {
return (
<S.CTAButtons>
<ButtonPrimary
analyticsId="button_hero_get_demo"
href="https://calendly.com/d/cqhd-tgj-vmc/45-minute-meeting"
href={calendlyDemoUrl}
rel="noopener noreferrer"
target="_blank"
iconLeft={<FiCalendar size={18} />}
Expand Down
76 changes: 76 additions & 0 deletions website/components/CTADemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import { FiArrowRight } from "react-icons/fi";
import styled from "styled-components";

import Button from "@/components/Button";
import Heading from "@/components/Heading";
import Section from "@/components/Section";
import Text from "@/components/Text";
import { mobile } from "@/constants/breakpoints";
import { calendlyDemoUrl } from "@/constants/urls";

const CTADemo = () => {
return (
<Section>
<S.CTADemo>
<S.Content>
<S.TextWrapper>
<Heading.H2>Fancy a demo?</Heading.H2>
<Text.Base>
Schedule some time for a personalized demo of Kardinal.
</Text.Base>
</S.TextWrapper>
<Button.Primary
analyticsId="button_demo_cta_schedule_demo"
iconRight={<FiArrowRight />}
href={calendlyDemoUrl}
rel="noopener noreferrer"
target="_blank"
>
Schedule a demo
</Button.Primary>
</S.Content>
</S.CTADemo>
</Section>
);
};

const S = {
CTADemo: styled.div`
display: flex;
width: 100%;
padding: 64px 0;
align-items: center;
justify-content: center;
`,

Content: styled.div`
max-width: 1038px;
border-radius: 12px;
background-color: rgba(252, 160, 97, 0.08);
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 48px;
gap: 4px;
@media ${mobile} {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
`,

TextWrapper: styled.div`
display: flex;
flex-direction: column;
gap: 4px;
@media ${mobile} {
gap: 8px;
}
`,
};

export default CTADemo;
22 changes: 1 addition & 21 deletions website/components/Calculator/CalculatorInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import {
useCalculatorContext,
} from "@/context/CalcualtorContext";

interface Props {
onCalculate: () => void;
}

const resourceRequirementsOptions: ResourceRequirement[] = [
ResourceRequirement.MICRO,
ResourceRequirement.SMALL,
Expand All @@ -23,7 +19,7 @@ const resourceRequirementsOptions: ResourceRequirement[] = [

const costIntervalOptions: CostInterval[] = ["Year", "Month"];

const CostSavingsCalculator = ({ onCalculate }: Props) => {
const CostSavingsCalculator = () => {
const {
engineers,
setEngineers,
Expand Down Expand Up @@ -98,16 +94,6 @@ const CostSavingsCalculator = ({ onCalculate }: Props) => {
<S.Chevron size={20} role="presentation" />
</S.SelectContainer>
</S.Columns>

<S.ButtonWrapper>
<ButtonPrimary
analyticsId={"button_calculator_page_calculate"}
iconRight={<FiArrowRight />}
onClick={onCalculate}
>
Calculate!
</ButtonPrimary>
</S.ButtonWrapper>
</S.Wrapper>
);
};
Expand Down Expand Up @@ -242,12 +228,6 @@ namespace S {
top: calc(50% + 4px);
color: var(--gray-400);
`;

export const ButtonWrapper = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`;
}

export default CostSavingsCalculator;
41 changes: 10 additions & 31 deletions website/components/Calculator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,24 @@ const Calculator = () => {
const costPerServiceHour =
HOURLY_COST_PER_RESOURCE_REQUIREMENT[resourceRequirement];

const calculateCostBefore = () =>
microservices * engineers * costPerServiceHour;
const calculateCostAfter = () =>
(microservices + engineers) * costPerServiceHour;
const calculateSavings = () => calculateCostBefore() - calculateCostAfter();

// Use copies of state values so numbers only change when calculate button is clicked
const [costBefore, setCostBefore] = useState<number>(calculateCostBefore());
const [costAfter, setCostAfter] = useState<number>(calculateCostAfter());
const [savings, setSavings] = useState<number>(calculateSavings());
const [interval, setInterval] = useState<CostInterval>(costInterval);

// only update values when user clicks calculate
const handleCalculate = () => {
setCostBefore(calculateCostBefore());
setCostAfter(calculateCostAfter());
setSavings(calculateSavings());
setInterval(costInterval);

analytics.track("CALCULATE", {
numEngineers: engineers,
numServices: microservices,
});
};
const costBefore = microservices * engineers * costPerServiceHour;
const costAfter = (microservices + engineers) * costPerServiceHour;
const savings = costBefore - costAfter;

return (
<Section>
<S.Title>
{"put in your organization numbers to see cost savings 👇🏻"}
</S.Title>
<CalculatorInputs onCalculate={handleCalculate} />
<CalculatorInputs />
<CardGroup>
<Card
title="Your costs before"
values={[
{
label: `Services cost before (per ${interval.toLowerCase()})`,
label: `Services cost before (per ${costInterval.toLowerCase()})`,
value: currencyFormatter.format(
costBefore * WORKING_HOURS_PER_COST_INTERVAL[interval],
costBefore * WORKING_HOURS_PER_COST_INTERVAL[costInterval],
),
},
{
Expand All @@ -96,9 +75,9 @@ const Calculator = () => {
title="Your costs after"
values={[
{
label: `Services cost after (per ${interval.toLowerCase()})`,
label: `Services cost after (per ${costInterval.toLowerCase()})`,
value: currencyFormatter.format(
costAfter * WORKING_HOURS_PER_COST_INTERVAL[interval],
costAfter * WORKING_HOURS_PER_COST_INTERVAL[costInterval],
),
},
{
Expand All @@ -116,9 +95,9 @@ const Calculator = () => {
value: 100 - Math.round((costAfter / costBefore) * 100) + "%",
},
{
label: `Cost savings per ${interval.toLowerCase()}*`,
label: `Cost savings per ${costInterval.toLowerCase()}*`,
value: currencyFormatter.format(
savings * WORKING_HOURS_PER_COST_INTERVAL[interval],
savings * WORKING_HOURS_PER_COST_INTERVAL[costInterval],
),
},
]}
Expand Down
13 changes: 8 additions & 5 deletions website/components/SavingsGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import savingsGraphImg from "@/public/illustrations/savings-graph.svg";

const SavingsGraph = () => {
return (
<Section padTop padBottom>
<Section>
<S.SavingsGraph>
<Image
src={savingsGraphImg}
Expand All @@ -39,10 +39,11 @@ const SavingsGraph = () => {
</ButtonPrimary>
</div>
</S.Content>
<small>
* Graph values are approximate. Based on use case with 20
microservices.
</small>
</S.SavingsGraph>
<small>
* Graph values are approximate. Based on use case with 20 microservices.
</small>
</Section>
);
};
Expand All @@ -52,9 +53,11 @@ namespace S {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 64px;
margin-bottom: 24px;
padding: 100px 0;
@media ${tablet} {
grid-template-columns: 1fr;
padding: 64px 0;
}
`;

Expand Down
24 changes: 17 additions & 7 deletions website/components/SavingsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ import Text from "@/components/Text";
import { tablet } from "@/constants/breakpoints";
import analytics from "@/lib/analytics";

const INITIAL_ENGINEERS = 20;
const INITIAL_MICROSERVICES = 60;

const SavingsSection = () => {
const [engineers, setEngineers] = useState(20);
const [microservices, setMicroservices] = useState(60);
const [engineers, setEngineers] = useState(INITIAL_ENGINEERS);
const [microservices, setMicroservices] = useState(INITIAL_MICROSERVICES);

const inputsAreInitialValues =
engineers === INITIAL_ENGINEERS && microservices === INITIAL_MICROSERVICES;

return (
<Section>
Expand Down Expand Up @@ -66,11 +72,15 @@ const SavingsSection = () => {
</S.Columns>
<S.PlaceholderFooter>
Potential savings:
<S.SavingsAmount>{"$26,726.40"}</S.SavingsAmount>
<S.SavingsPercentage>
<FiArrowDown size={16} />
~93%
</S.SavingsPercentage>
<S.SavingsAmount>
{inputsAreInitialValues ? "$24,944.64" : "$--,---.--"}
</S.SavingsAmount>
{inputsAreInitialValues && (
<S.SavingsPercentage>
<FiArrowDown size={16} />
~93%
</S.SavingsPercentage>
)}
</S.PlaceholderFooter>
</S.CalculatorPlaceholder>
</S.SavingsSection>
Expand Down
2 changes: 2 additions & 0 deletions website/constants/urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const calendlyDemoUrl =
"https://calendly.com/d/cqhd-tgj-vmc/45-minute-meeting";
6 changes: 3 additions & 3 deletions website/context/CalcualtorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export const CalculatorProvider = ({ children }: PropsWithChildren) => {
: 60;

const [engineers, setEngineers] = useState<number>(
Math.min(initialEngineers, 100),
); // max value 100
Math.min(Math.max(initialEngineers, 2), 100),
); // value from 2 to 100
const [microservices, setMicroservices] = useState<number>(
Math.min(initialMicroservices, 100),
Math.min(Math.max(initialMicroservices, 2), 100),
); // max value 100
const [resourceRequirement, setResourceRequirement] =
useState<ResourceRequirement>(ResourceRequirement.MICRO);
Expand Down

0 comments on commit a773cd9

Please sign in to comment.