Skip to content

Commit

Permalink
fix: provide default selectedSub upon checkout in mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
trevor-anderson committed May 13, 2023
1 parent d8e58eb commit b1173d0
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 103 deletions.
70 changes: 42 additions & 28 deletions src/pages/ProductsPage/ProductSelection/MappedRowOfProductBoxes.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
import { useNavigate } from "react-router-dom";
import { checkoutValuesStore, type StoredCheckoutValues } from "@cache/checkoutValuesStore";
import { ProductInfoBox } from "./ProductInfoBox";
import { PRICE_INFO } from "./productPricingInfo";
import type { UserSubscriptionPriceLabel } from "@types";

/**
* Product selection for desktop layout
* - Maps each product to a `ProductInfoBox`
*/
export const MappedRowOfProductBoxes = ({ selectedSubscription }: StoredCheckoutValues) => {
const nav = useNavigate();

const handleClickContainer = (priceLabel: UserSubscriptionPriceLabel) => {
checkoutValuesStore.mergeUpdate({
selectedSubscription: priceLabel,
});
};

return (
<>
{PRICE_INFO_ENTRIES.map(([priceLabel, { PRICE_NAME, PRICE_AMOUNT, PRICE_DESCRIPTION }]) => {
const isSelected = selectedSubscription === priceLabel;

return (
<ProductInfoBox
key={`ProductInfoDisplay:${priceLabel}`}
priceName={PRICE_NAME}
priceAmount={PRICE_AMOUNT}
priceDescription={PRICE_DESCRIPTION}
showMostPopularBadge={priceLabel === "ANNUAL"}
buttonLabel={!isSelected ? "Select" : "Subscribe"}
onClickButton={() => {
if (!isSelected) handleClickContainer(priceLabel);
else nav("/checkout");
}}
onClickContainer={() => handleClickContainer(priceLabel)}
sx={({ palette }) => ({
...(isSelected && { borderColor: palette.secondary.main }),
})}
/>
);
})}
</>
);
};

const PRICE_INFO_ENTRIES = Object.entries(PRICE_INFO) as Array<
[
UserSubscriptionPriceLabel,
Expand All @@ -13,31 +55,3 @@ const PRICE_INFO_ENTRIES = Object.entries(PRICE_INFO) as Array<
}
]
>;

/**
* Product selection for desktop layout
* - Maps each product to a `ProductInfoBox`
*/
export const MappedRowOfProductBoxes = ({
selectedSubscription,
promoCode,
}: StoredCheckoutValues) => (
<>
{PRICE_INFO_ENTRIES.map(([priceLabel, { PRICE_NAME, PRICE_AMOUNT, PRICE_DESCRIPTION }]) => (
<ProductInfoBox
key={`ProductInfoDisplay:${priceLabel}`}
priceName={PRICE_NAME}
priceAmount={PRICE_AMOUNT}
priceDescription={PRICE_DESCRIPTION}
showMostPopularBadge={priceLabel === "ANNUAL"}
isSelected={selectedSubscription === priceLabel}
handleClickProduct={() =>
checkoutValuesStore.set({
selectedSubscription: priceLabel,
promoCode: promoCode ?? null,
})
}
/>
))}
</>
);
110 changes: 49 additions & 61 deletions src/pages/ProductsPage/ProductSelection/ProductInfoBox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useNavigate } from "react-router-dom";
import { styled } from "@mui/material/styles";
import Button, { buttonClasses } from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Paper, { type PaperProps } from "@mui/material/Paper";
import Text, { typographyClasses } from "@mui/material/Typography";
import { ShimmerBox, shimmerBoxClassNames } from "@components/Containers/ShimmerBox";
import { ProductFeatures } from "./ProductFeatures";
Expand All @@ -11,69 +10,57 @@ export const ProductInfoBox = ({
priceAmount,
priceDescription,
showMostPopularBadge = false,
isSelected = false,
handleClickProduct,
buttonLabel,
}: ProductInfoBoxProps) => {
const nav = useNavigate();

const handleClickCheckout = () => nav("/checkout");

// If ProductInfoBox can be selected from a group (desktop) and isn't already selected, it's "selectable"
const isSelectable = !!handleClickProduct && !isSelected;

return (
<StyledPaper
onClick={handleClickProduct}
isSelected={isSelected}
className={productInfoBoxClassNames.container}
>
<Paper elevation={2} className={productInfoBoxClassNames.headerContainer}>
<Text>{priceName}</Text>

{showMostPopularBadge && (
<>
<span />
<ShimmerBox>
<Text>Most Popular</Text>
</ShimmerBox>
</>
)}
</Paper>
<div>
<div className={productInfoBoxClassNames.amountAndDescriptionContainer}>
<Text variant="h3">{priceAmount}</Text>
<span>
<Text
style={{
// if priceDescription is "per month/year", align left + margin, else just align center
...(/^per/i.test(priceDescription)
? { textAlign: "left", marginLeft: "0.35rem" }
: { textAlign: "center" }),
}}
>
{priceDescription}
</Text>
</span>
</div>
<ProductFeatures />
<Button onClick={isSelectable ? handleClickProduct : handleClickCheckout}>
{buttonLabel ?? (isSelectable ? "Select" : "Subscribe")}
</Button>
onClickButton,
onClickContainer,
...paperProps
}: ProductInfoBoxProps) => (
<StyledPaper
onClick={onClickContainer}
className={productInfoBoxClassNames.container}
{...paperProps}
>
<Paper elevation={2} className={productInfoBoxClassNames.headerContainer}>
<Text>{priceName}</Text>

{showMostPopularBadge && (
<>
<span />
<ShimmerBox>
<Text>Most Popular</Text>
</ShimmerBox>
</>
)}
</Paper>
<div>
<div className={productInfoBoxClassNames.amountAndDescriptionContainer}>
<Text variant="h3">{priceAmount}</Text>
<span>
<Text
style={{
// if priceDescription is "per month/year", align left + margin, else just align center
...(/^per/i.test(priceDescription)
? { textAlign: "left", marginLeft: "0.35rem" }
: { textAlign: "center", lineHeight: "1.2rem" }),
}}
>
{priceDescription}
</Text>
</span>
</div>
</StyledPaper>
);
};
<ProductFeatures />
<Button onClick={onClickButton}>{buttonLabel}</Button>
</div>
</StyledPaper>
);

export const productInfoBoxClassNames = {
container: "product-info-box-container",
headerContainer: "product-info-box-header-container",
amountAndDescriptionContainer: "product-info-box-amount-and-description-container",
};

const StyledPaper = styled(Paper, {
shouldForwardProp: (propName) => propName !== "isSelected",
})<{ isSelected: boolean }>(({ onClick, theme: { palette, breakpoints }, isSelected }) => ({
const StyledPaper = styled(Paper)(({ onClick, theme: { palette, breakpoints } }) => ({
position: "relative",
height: "clamp(24rem, 43vh, 25rem)",
width: "clamp(15rem, 100%, 25rem)",
Expand All @@ -84,7 +71,8 @@ const StyledPaper = styled(Paper, {
borderWidth: "1px",
borderStyle: "solid",
borderRadius: "1rem",
borderColor: isSelected ? palette.secondary.main : palette.divider,
borderColor: palette.divider,

"&:hover": {
cursor: onClick ? "pointer" : "auto",
},
Expand Down Expand Up @@ -175,7 +163,7 @@ export type ProductInfoBoxProps = {
priceAmount: string;
priceDescription: string;
showMostPopularBadge?: boolean;
isSelected?: boolean;
handleClickProduct?: React.MouseEventHandler<HTMLDivElement | HTMLButtonElement>;
buttonLabel?: string;
};
buttonLabel: string;
onClickButton: React.MouseEventHandler<HTMLButtonElement>;
onClickContainer?: React.MouseEventHandler<HTMLDivElement | HTMLButtonElement>;
} & Omit<PaperProps, "children">;
25 changes: 12 additions & 13 deletions src/pages/ProductsPage/ProductSelection/SingleProductBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Box from "@mui/material/Box";
import Tooltip from "@mui/material/Tooltip";
import { checkoutValuesStore, StoredCheckoutValues } from "@cache/checkoutValuesStore";
Expand All @@ -15,16 +15,10 @@ import type { UserSubscriptionPriceLabel } from "@types";
* selectable (TRIAL will display MONTHLY's PRICE_INFO text, but the
* checkout button label is set to "Start Trial" to avoid ambiguity).
* - If `selectedSubscription` has not been set in `checkoutValuesStore`,
* it is initialized to TRIAL.
* it is initialized to TRIAL before nav'ing to /checkout.
*/
export const SingleProductBox = ({ selectedSubscription, promoCode }: StoredCheckoutValues) => {
// EFFECT: If `selectedSubscription` has no cached value, initialize it to TRIAL
useEffect(() => {
const cachedCheckoutValues = checkoutValuesStore.get();
if (!cachedCheckoutValues.selectedSubscription) {
checkoutValuesStore.mergeUpdate({ selectedSubscription: "TRIAL" });
}
}, []);
const nav = useNavigate();

const priceInfoToDisplay = selectedSubscription === "ANNUAL" ? "ANNUAL" : "MONTHLY";

Expand All @@ -45,13 +39,17 @@ export const SingleProductBox = ({ selectedSubscription, promoCode }: StoredChec

const handleSwitchProducts = (event: React.ChangeEvent<HTMLInputElement>) => {
event.stopPropagation();

checkoutValuesStore.set({
checkoutValuesStore.mergeUpdate({
selectedSubscription: switchOtherProduct.priceLabel,
promoCode: promoCode ?? null,
});
};

const handleClickButton = () => {
const { selectedSubscription: cachedSelectedSub } = checkoutValuesStore.get();
if (!cachedSelectedSub) checkoutValuesStore.mergeUpdate({ selectedSubscription: "TRIAL" });
nav("/checkout");
};

return (
<>
<Tooltip title={switchOtherProduct.tooltipTitle}>
Expand All @@ -75,7 +73,8 @@ export const SingleProductBox = ({ selectedSubscription, promoCode }: StoredChec
priceAmount={PRICE_AMOUNT}
priceDescription={PRICE_DESCRIPTION}
showMostPopularBadge={selectedSubscription === "ANNUAL"}
buttonLabel={selectedSubscription === "ANNUAL" ? "Subscribe" : "Start Trial"}
buttonLabel={selectedSubscription === "TRIAL" ? "Start Trial" : "Subscribe"}
onClickButton={handleClickButton}
/>
</>
);
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ProductsPage/ProductsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const ProductsPage = () => {
toast.info("Please select a subscription", { toastId: "please-select-sub" });
}
}
}, [locationState]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<ProductsPageContainer>
Expand Down

0 comments on commit b1173d0

Please sign in to comment.