diff --git a/src/components/PaymentModalNew/PaymentCardModal/CreditCardIcon/index.tsx b/src/components/PaymentModalNew/PaymentCardModal/CreditCardIcon/index.tsx new file mode 100644 index 0000000..89cb11d --- /dev/null +++ b/src/components/PaymentModalNew/PaymentCardModal/CreditCardIcon/index.tsx @@ -0,0 +1,9 @@ +export default function CreditCardIcon() { + return ( + + + + + + ) +} \ No newline at end of file diff --git a/src/components/PaymentModalNew/PaymentCardModal/index.tsx b/src/components/PaymentModalNew/PaymentCardModal/index.tsx new file mode 100644 index 0000000..3bc937d --- /dev/null +++ b/src/components/PaymentModalNew/PaymentCardModal/index.tsx @@ -0,0 +1,39 @@ +import { Elements } from '@stripe/react-stripe-js'; +import CheckoutForm, { TConfirmType } from '@/components/PaymentPage/methods/CheckoutForm'; +import Modal from '@/components/Modal'; +import { Stripe } from '@stripe/stripe-js'; +import { Dispatch, SetStateAction } from 'react'; + +import './style.scss'; + +interface IPaymentCardModalProps { + clientSecret?: string; + stripePromise: Promise | null; + paymentType?: TConfirmType; + paymentIntentId?: string; + returnUrl?: string; + isOpen: boolean; + setIsOpen: Dispatch>; +} + +export default function PaymentCardModal({ + clientSecret, + stripePromise, + paymentType, + paymentIntentId, + returnUrl, + isOpen, + setIsOpen, +}: IPaymentCardModalProps) { + return ( + setIsOpen(false)}> + + + + + ) +} \ No newline at end of file diff --git a/src/components/PaymentModalNew/PaymentCardModal/style.scss b/src/components/PaymentModalNew/PaymentCardModal/style.scss new file mode 100644 index 0000000..06a8a21 --- /dev/null +++ b/src/components/PaymentModalNew/PaymentCardModal/style.scss @@ -0,0 +1,7 @@ +:global(.paymentCardModalContainer) { + background: none; + + .p-PaymentMethodSelector { + display: none !important; + } +} \ No newline at end of file diff --git a/src/components/PaymentModalNew/index.tsx b/src/components/PaymentModalNew/index.tsx new file mode 100644 index 0000000..5473e7b --- /dev/null +++ b/src/components/PaymentModalNew/index.tsx @@ -0,0 +1,155 @@ +import Loader from '@/components/Loader'; + +import styles from './styles.module.scss'; + +import cn from 'classnames'; +import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall'; +import { useNavigate } from 'react-router-dom'; +import { Dispatch, LegacyRef, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'; +import { loadStripe, Stripe } from '@stripe/stripe-js'; +import { usePaywall } from '@/hooks/paywall/usePaywall'; +import { useMakePayment } from '@/hooks/payment/useMakePayment'; +import { getFormattedPrice } from '@/utils/price.utils'; +import routes from '@/routes'; +import Title from '@/components/Title'; +import { Elements } from '@stripe/react-stripe-js'; +import ExpressCheckoutStripe from '@/components/PaymentPage/methods/ExpressCheckoutStripe'; +import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments'; +import PaymentCardModal from '@/components/PaymentModalNew/PaymentCardModal'; +import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon'; + +interface IPaymentModalNewProps { + returnUrl: string; + placementKey: EPlacementKeys; + activeProduct: IPaywallProduct; + setHeight?: Dispatch>; +} + +export default function PaymentModalNew({ + returnUrl, + activeProduct, + placementKey, + setHeight, +}: IPaymentModalNewProps) { + const navigate = useNavigate(); + const ref = useRef(); + const [stripePromise, setStripePromise] = + useState | null>(null); + + const {products, placementId, paywallId} = usePaywall({ + placementKey, + }); + + const [isOpenCardModal, setIsOpenCardModal] = useState(false); + + const { + paymentIntentId, + clientSecret, + returnUrl: checkoutUrl, + paymentType, + publicKey, + isLoading: isLoadingPayment, + error, + } = useMakePayment({ + productId: activeProduct?._id || '', + placementId, + paywallId, + returnPaidUrl: returnUrl, + }); + + const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] = + useState(true); + + const isLoading = useMemo(() => { + return isLoadingPayment || isLoadingExpressCheckout; + }, [isLoadingPayment, isLoadingExpressCheckout]); + + if (checkoutUrl?.length) { + window.location.href = checkoutUrl; + } + + useEffect(() => { + (async () => { + if (!products?.length || !publicKey) return; + setStripePromise(loadStripe(publicKey)); + const isActiveProduct = products.find( + (product) => product._id === activeProduct?._id, + ); + if (!activeProduct || !isActiveProduct) { + navigate(routes.client.trialChoice()); + } + })(); + }, [activeProduct, navigate, products, publicKey]); + + const resizeHandler = () => { + setHeight?.(ref?.current?.clientHeight || 32); + }; + + if (error?.length) { + setTimeout(resizeHandler, 300); + return ( +
} className={styles['payment-modal']}> + + Something went wrong + +
+ ); + } + + return ( +
} + className={cn(styles.paymentModalContainer, isLoading && styles.paymentModalContainerLoading)}> + {isLoading &&
+ +
+ } + +
Total due today: + ${getFormattedPrice(activeProduct.trialPrice)}
+ + {!isLoadingPayment && + <> + {!isLoading && +
setIsOpenCardModal(true)}> + +
Credit / Debit Card
+
+ } + + { + setIsLoadingExpressCheckout(isLoading); + setTimeout(resizeHandler, 300); + } + } + /> + + + + {!isLoading && + <> +
+ +

1123 Rimer Dr Moraga, California 94556

+
+ + } + + } + + +
+ ); +} + diff --git a/src/components/PaymentModalNew/styles.module.scss b/src/components/PaymentModalNew/styles.module.scss new file mode 100644 index 0000000..10618d2 --- /dev/null +++ b/src/components/PaymentModalNew/styles.module.scss @@ -0,0 +1,60 @@ +.paymentModalContainer { + display: flex; + flex-direction: column; + position: relative; + margin: -12px -20px; + padding: 12px 20px; + gap: 6px; + + transition: height 1s ease-out; + + .address { + color: gray; + font-size: 10px; + margin-bottom: 16px; + text-transform: uppercase; + } + + .infoContainer > * { + padding-top: 16px; + } + + .paymentCreditCard { + background: #066fde; + color: #fff !important; + gap: 6px; + display: flex; + font-size: 14px; + line-height: 18px; + align-items: center; + font-weight: 400; + min-height: 48px; + border-radius: 5px; + justify-content: center; + } + + &Loading { + background: rgba(215, 213, 213, .5); + } + + .paymentModalLoader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 25px; + color: #2f2e37; + position: absolute; + width: 100%; + margin-left: -20px; + } + + .paymentModalPrice { + color: #066fde; + font-size: 16px; + font-weight: 700; + line-height: 25px; + text-align: center; + margin-bottom: 12px; + } +} \ No newline at end of file diff --git a/src/components/PaymentPage/methods/CheckoutForm/index.tsx b/src/components/PaymentPage/methods/CheckoutForm/index.tsx index fa0242a..f85d722 100644 --- a/src/components/PaymentPage/methods/CheckoutForm/index.tsx +++ b/src/components/PaymentPage/methods/CheckoutForm/index.tsx @@ -12,11 +12,13 @@ import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import styles from "./styles.module.css"; +export type TConfirmType = "payment" | "setup"; + interface ICheckoutFormProps { children?: JSX.Element | null; subscriptionReceiptId?: string; returnUrl?: string; - confirmType?: "payment" | "setup"; + confirmType?: TConfirmType; isHide?: boolean; } diff --git a/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx index cbb6972..414a044 100644 --- a/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx +++ b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx @@ -21,6 +21,7 @@ interface IExpressCheckoutStripeProps { availableMethods: AvailablePaymentMethods | undefined ) => void; onChangeLoading?: (isLoading: boolean) => void; + paymentMethodOrderList?: string[]; } function ExpressCheckoutStripe({ @@ -29,6 +30,7 @@ function ExpressCheckoutStripe({ isHide = false, onAvailable, onChangeLoading, + paymentMethodOrderList }: IExpressCheckoutStripeProps) { const stripe = useStripe(); const elements = useElements(); @@ -104,7 +106,7 @@ function ExpressCheckoutStripe({ maxColumns: 1, overflow: "never", }, - paymentMethodOrder: ["apple_pay", "google_pay", "amazon_pay", "link"], + paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"], wallets: { googlePay: "always", applePay: "always", diff --git a/src/components/palmistry/payment-screen/payment-screen.css b/src/components/palmistry/payment-screen/payment-screen.css index a88396e..6765f55 100644 --- a/src/components/palmistry/payment-screen/payment-screen.css +++ b/src/components/palmistry/payment-screen/payment-screen.css @@ -1,228 +1,247 @@ .payment-screen { - margin: 0 auto; - position: relative; - max-width: 428px; - height: 100%; - display: flex; - flex-direction: column; + margin: 0 auto; + position: relative; + max-width: 428px; + height: 100%; + display: flex; + flex-direction: column; } .payment-screen__header { - display: flex; - width: 100%; - padding: 24px 0 11px; - justify-content: center; + display: flex; + width: 100%; + padding: 24px 0 11px; + justify-content: center; } .payment-screen__content { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - height: 100%; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: 100%; } .payment-screen__content * { - font-family: OpenSans Regular; + font-family: OpenSans Regular; } .payment-screen__about-us { - display: flex; - text-align: center; - align-items: center; - flex-direction: column; - margin-bottom: 16px; + display: flex; + text-align: center; + align-items: center; + flex-direction: column; + margin-bottom: 16px; } .payment-screen__about-us > span { - padding: 0 60px; - margin-bottom: 6px; + padding: 0 60px; + margin-bottom: 6px; } .payment-screen__about-us span { - font-size: 14px; - line-height: 18px; - font-family: Alata Regular !important; + font-size: 14px; + line-height: 18px; + font-family: Alata Regular !important; } .payment-screen__about-us-accent { - color: #066fde; + color: #066fde; } .payment-screen__timer { - width: 100%; - display: flex; - padding: 8px 12px; - border-radius: 4px; - background: #eff2fd; - margin-bottom: 16px; - align-items: center; - justify-content: space-between; - position: sticky; - top: 0; + width: 100%; + display: flex; + padding: 8px 12px; + border-radius: 4px; + background: #eff2fd; + margin-bottom: 16px; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; } .payment-screen__timer-title { - font-size: 14px; - line-height: 20px; + font-size: 14px; + line-height: 20px; } .payment-screen__timer-time { - gap: 1px; - display: flex; - font-size: 16px; - line-height: 22px; - align-items: center; + gap: 1px; + display: flex; + font-size: 16px; + line-height: 22px; + align-items: center; } .payment-screen__timer-time span { - width: 18px; - height: 28px; - display: flex; - background: #fff; - font-weight: 700; - border-radius: 4px; - align-items: center; - justify-content: center; - border: 1px solid #dee5f9; + width: 18px; + height: 28px; + display: flex; + background: #fff; + font-weight: 700; + border-radius: 4px; + align-items: center; + justify-content: center; + border: 1px solid #dee5f9; } .payment-screen__title { - width: 100%; - margin-bottom: 16px; - font-size: 24px; - line-height: 32px; - text-align: left; - color: #121620; - font-weight: 400; - font-family: Alata Regular !important; + width: 100%; + margin-bottom: 16px; + font-size: 24px; + line-height: 32px; + text-align: left; + color: #121620; + font-weight: 400; + font-family: Alata Regular !important; } .payment-screen__total-today { - width: 100%; - display: flex; - padding: 12px 0; - margin-bottom: 6px; - align-items: center; - border-top: 1px solid #dee5f9; - border-bottom: 1px solid #dee5f9; - justify-content: space-between; + width: 100%; + display: flex; + padding: 12px 0; + margin-bottom: 6px; + align-items: center; + border-top: 1px solid #dee5f9; + border-bottom: 1px solid #dee5f9; + justify-content: space-between; } .payment-screen__total-today span { - font-size: 16px; - line-height: 18px; - font-weight: 600; + font-size: 16px; + line-height: 18px; + font-weight: 600; } .payment-screen__total-today .payment-screen__trial-price { - color: #066fde; - font-weight: 700; + color: #066fde; + font-weight: 700; } .payment-screen__promocode { - gap: 8px; - width: 100%; - display: flex; - align-items: center; - padding: 12px 0 16px; + gap: 8px; + width: 100%; + display: flex; + align-items: center; + padding: 12px 0 16px; } .payment-screen__promocode span { - color: #04a777; - font-size: 12px; - font-weight: 600; - line-height: 18px; + color: #04a777; + font-size: 12px; + font-weight: 600; + line-height: 18px; } .payment-screen__prices { - width: 100%; - display: flex; - margin-bottom: 6px; - flex-direction: column; + width: 100%; + display: flex; + margin-bottom: 6px; + flex-direction: column; } .payment-screen__prices span { - color: #4b536a; - font-size: 12px; - line-height: 20px; + color: #4b536a; + font-size: 12px; + line-height: 20px; } .payment-screen__prices s { - color: #858da5; + color: #858da5; } .payment-screen__guarantees { - width: 100%; - display: flex; - justify-content: space-between; - gap: 30px; - margin-bottom: 30px; + width: 100%; + display: flex; + justify-content: space-between; + gap: 30px; + margin-bottom: 30px; } .payment-screen__guarantee { - width: 100%; - display: flex; - justify-content: space-between; - gap: 8px; - max-width: 155px; - align-items: center; + width: 100%; + display: flex; + justify-content: space-between; + gap: 8px; + max-width: 155px; + align-items: center; } .payment-screen__guarantee svg { - min-width: 24px; - min-height: 24px; - max-width: 24px; - max-height: 24px; + min-width: 24px; + min-height: 24px; + max-width: 24px; + max-height: 24px; } .payment-screen__guarantee span { - color: #4b536a; - font-weight: 600; - font-size: 12px; - line-height: 18px; + color: #4b536a; + font-weight: 600; + font-size: 12px; + line-height: 18px; } .payment-screen__widget { - background: #fff; - bottom: 0; - box-shadow: 0 -2px 16px rgba(18, 22, 32, .1); - max-width: 428px; - width: 100%; - padding: 40px; - position: relative; + position: fixed; + background: #fff; + bottom: 0; + box-shadow: 0 -2px 16px rgba(18, 22, 32, .1); + width: 100%; + padding: 12px 20px; + text-align: center; + text-align: -webkit-center; + transition: .5s height; + max-width: 560px; + margin-left: -66px; } .payment-screen__widget_success { - height: 400px; + height: 400px; } .payment-screen__success { - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: 0; - background: #fff; - z-index: 99; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 30px; - padding: 40px; + width: 100%; + height: 100%; + background: #fff; + z-index: 99; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 30px; + padding: 40px; } .payment-screen__success-icon { - width: 100px; - height: 100px; - max-width: 50%; - flex-shrink: 0; + width: 100px; + height: 100px; + max-width: 50%; + flex-shrink: 0; } .payment-screen__success-text { - font-size: 24px; - line-height: 32px; - text-align: center; - color: #121620; + font-size: 24px; + line-height: 32px; + text-align: center; + color: #121620; } + +.payment-screen__widget_modal_container { + height: 100%; +} + + +@media screen and (max-width: 560px) { + .payment-screen__widget { + width: 100%; + padding: 12px 20px; + text-align: center; + text-align: -webkit-center; + transition: height 1s linear; + max-width: 560px; + margin-left: 0; + left: 0; + } +} \ No newline at end of file diff --git a/src/components/palmistry/payment-screen/payment-screen.tsx b/src/components/palmistry/payment-screen/payment-screen.tsx index 10ad872..853eeb7 100644 --- a/src/components/palmistry/payment-screen/payment-screen.tsx +++ b/src/components/palmistry/payment-screen/payment-screen.tsx @@ -1,20 +1,17 @@ -import React from "react"; +import React, { useState } from 'react'; -import { useSelector } from "react-redux"; +import { useSelector } from 'react-redux'; -import "./payment-screen.css"; +import './payment-screen.css'; -import useSteps, { Step } from "@/hooks/palmistry/use-steps"; -import useTimer from "@/hooks/palmistry/use-timer"; -import HeaderLogo from "@/components/palmistry/header-logo/header-logo"; -import PaymentModal from "@/components/PaymentModal"; -import { selectors } from "@/store"; -import { EPlacementKeys } from "@/api/resources/Paywall"; -import { useSearchParams } from "react-router-dom"; - -const getFormattedPrice = (price: number) => { - return (price / 100).toFixed(2); -}; +import useSteps, { Step } from '@/hooks/palmistry/use-steps'; +import useTimer from '@/hooks/palmistry/use-timer'; +import HeaderLogo from '@/components/palmistry/header-logo/header-logo'; +import { selectors } from '@/store'; +import { EPlacementKeys } from '@/api/resources/Paywall'; +import { useSearchParams } from 'react-router-dom'; +import PaymentModalNew from '@/components/PaymentModalNew'; +import { getFormattedPrice } from '@/utils/price.utils'; export default function PaymentScreen() { const time = useTimer(); @@ -24,7 +21,7 @@ export default function PaymentScreen() { searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead"; - // const subscriptionStatus = useSelector(selectors.selectStatus); + const [height, setHeight] = useState(subscriptionStatus === "subscribed" ? 246 : 146); const steps = useSteps(); @@ -52,7 +49,7 @@ export default function PaymentScreen() { return (
- +
@@ -246,40 +243,45 @@ export default function PaymentScreen() { {activeProductFromStore && ( -
- {subscriptionStatus !== "subscribed" && ( - - )} +
+
+ {subscriptionStatus !== "subscribed" && ( + + )} - {subscriptionStatus === "subscribed" && ( -
- - - + {subscriptionStatus === "subscribed" && ( +
+ + + -
- Payment success +
+ Payment success +
-
- )} + )} +
)}
diff --git a/src/utils/price.utils.tsx b/src/utils/price.utils.tsx new file mode 100644 index 0000000..9bde61d --- /dev/null +++ b/src/utils/price.utils.tsx @@ -0,0 +1,3 @@ +export const getFormattedPrice = (price: number) => { + return (price / 100).toFixed(2); +}; \ No newline at end of file