From bafd8cda00ab704920836486d789c9ad9b36328e Mon Sep 17 00:00:00 2001 From: Daniil Chemerkin Date: Thu, 13 Jun 2024 00:59:26 +0000 Subject: [PATCH] Develop --- public/amazon-pay-mark.png | Bin 0 -> 14520 bytes public/google-pay-mark.png | Bin 0 -> 16605 bytes public/link-pay-mark.png | Bin 0 -> 1092 bytes src/api/resources/User.ts | 2 + src/components/EmailEnterPage/index.tsx | 12 +- src/components/HomePage/index.tsx | 2 +- .../methods/ApplePayButton/index.tsx | 121 ------------- .../methods/ExpressCheckoutStripe/index.tsx | 112 ++++++++++++ .../ExpressCheckoutStripe/styles.module.css | 16 ++ .../CheckAvailableStripeButton/index.tsx | 33 ++++ .../methods/StripeButton/index.tsx | 27 +++ .../styles.module.css | 0 .../SubPlanInformation/TotalToday/index.tsx | 17 -- .../TotalToday/styles.module.css | 17 -- src/components/SubPlanInformation/index.tsx | 37 ---- .../SubPlanInformation/styles.module.css | 31 ---- .../ABDesign/v1/components/Checkbox/index.tsx | 34 ++++ .../v1/components/Checkbox/styles.module.css | 92 ++++++++++ .../v1/components/PrivacyPolicy/index.tsx | 45 +++++ .../PrivacyPolicy/styles.module.css | 16 ++ .../v1/components/Toast/ErrorIcon/index.tsx | 24 +++ .../ABDesign/v1/components/Toast/index.tsx | 28 +++ .../v1/components/Toast/styles.module.css | 26 +++ .../v1/pages/EmailEnterPage/index.tsx | 14 +- .../pages/ABDesign/v1/pages/Gender/index.tsx | 40 ++++- .../v1/pages/Gender/styles.module.css | 29 ++- .../components/PaymentModal/index.tsx | 155 +++++++++------- .../components/PaymentModal/styles.module.css | 7 + .../components/PaymentAddress/index.tsx | 2 +- .../PaymentAddress/styles.module.css | 1 + .../SinglePaymentPage/PaymentForm/index.tsx | 2 +- .../PaymentForm/styles.module.css | 1 + .../components/PaymentModal/index.tsx | 167 +++++++++++------- .../components/PaymentModal/styles.module.css | 7 + .../PaymentButtons/index.tsx | 32 +++- .../PaymentButtons/styles.module.css | 2 +- src/data/paymentMethods.tsx | 63 +++++-- .../authentication/use-authentication.ts | 7 +- src/hooks/payment/useCanUseStripeButton.ts | 98 ++++++++++ src/locales/dev.ts | 2 +- src/store/index.ts | 6 + src/store/payment.ts | 19 ++ src/store/privacyPolicy.ts | 30 ++++ src/store/user.ts | 2 +- 44 files changed, 983 insertions(+), 395 deletions(-) create mode 100644 public/amazon-pay-mark.png create mode 100644 public/google-pay-mark.png create mode 100644 public/link-pay-mark.png delete mode 100644 src/components/PaymentPage/methods/ApplePayButton/index.tsx create mode 100644 src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx create mode 100644 src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css create mode 100644 src/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton/index.tsx create mode 100644 src/components/PaymentPage/methods/StripeButton/index.tsx rename src/components/PaymentPage/methods/{ApplePayButton => StripeButton}/styles.module.css (100%) delete mode 100644 src/components/SubPlanInformation/TotalToday/index.tsx delete mode 100644 src/components/SubPlanInformation/TotalToday/styles.module.css delete mode 100644 src/components/SubPlanInformation/index.tsx delete mode 100644 src/components/SubPlanInformation/styles.module.css create mode 100644 src/components/pages/ABDesign/v1/components/Checkbox/index.tsx create mode 100644 src/components/pages/ABDesign/v1/components/Checkbox/styles.module.css create mode 100644 src/components/pages/ABDesign/v1/components/PrivacyPolicy/index.tsx create mode 100644 src/components/pages/ABDesign/v1/components/PrivacyPolicy/styles.module.css create mode 100644 src/components/pages/ABDesign/v1/components/Toast/ErrorIcon/index.tsx create mode 100644 src/components/pages/ABDesign/v1/components/Toast/index.tsx create mode 100644 src/components/pages/ABDesign/v1/components/Toast/styles.module.css create mode 100644 src/hooks/payment/useCanUseStripeButton.ts create mode 100644 src/store/privacyPolicy.ts diff --git a/public/amazon-pay-mark.png b/public/amazon-pay-mark.png new file mode 100644 index 0000000000000000000000000000000000000000..602efb79465d18f99d928b4237336466638e9a97 GIT binary patch literal 14520 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 literal 0 HcmV?d00001 diff --git a/public/google-pay-mark.png b/public/google-pay-mark.png new file mode 100644 index 0000000000000000000000000000000000000000..07cd76a672e32757d65f34dccf73281970b92140 GIT binary patch literal 16605 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 KMNUMnLSTaDl>h1g literal 0 HcmV?d00001 diff --git a/src/api/resources/User.ts b/src/api/resources/User.ts index d814463..f0a1209 100644 --- a/src/api/resources/User.ts +++ b/src/api/resources/User.ts @@ -171,6 +171,8 @@ export interface ICreateAuthorizePayload { source: ESourceAuthorization; profile?: Partial; partner?: Partial>; + sign?: boolean; + signDate?: string; } export interface ICreateAuthorizeResponse { diff --git a/src/components/EmailEnterPage/index.tsx b/src/components/EmailEnterPage/index.tsx index adb7488..b3f57bb 100755 --- a/src/components/EmailEnterPage/index.tsx +++ b/src/components/EmailEnterPage/index.tsx @@ -118,12 +118,6 @@ function EmailEnterPage({ Enter your email

{t("we_dont_share")}

- setIsValidName(!isRequiredName)} - /> setIsValidEmail(false)} /> + setIsValidName(!isRequiredName)} + /> {t("_continue_agree", { eulaLink: ( diff --git a/src/components/HomePage/index.tsx b/src/components/HomePage/index.tsx index f288b91..f7ccd8b 100644 --- a/src/components/HomePage/index.tsx +++ b/src/components/HomePage/index.tsx @@ -343,7 +343,7 @@ function HomePage(): JSX.Element { {predictionMoonsPeriods.map((item, index) => ( { handleNameHoroscope(item); diff --git a/src/components/PaymentPage/methods/ApplePayButton/index.tsx b/src/components/PaymentPage/methods/ApplePayButton/index.tsx deleted file mode 100644 index 6e88090..0000000 --- a/src/components/PaymentPage/methods/ApplePayButton/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useEffect, useState } from "react"; -import { - PaymentRequestButtonElement, - useStripe, - useElements, -} from "@stripe/react-stripe-js"; -import { PaymentRequest } from "@stripe/stripe-js"; -import styles from "./styles.module.css"; -import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import routes from "@/routes"; -import { IPaywallProduct } from "@/api/resources/Paywall"; - -interface ApplePayButtonProps { - activeProduct: IPaywallProduct | null; - client_secret: string; - subscriptionReceiptId?: string; - returnUrl?: string; - setCanMakePayment?: (isCanMakePayment: boolean) => void; -} - -function ApplePayButton({ - activeProduct, - client_secret, - subscriptionReceiptId, - returnUrl, - setCanMakePayment, -}: ApplePayButtonProps) { - const stripe = useStripe(); - const elements = useElements(); - const dispatch = useDispatch(); - const navigate = useNavigate(); - const [paymentRequest, setPaymentRequest] = useState( - null - ); - - const getAmountFromProduct = (subPlan: IPaywallProduct) => { - if (subPlan.isTrial) { - return subPlan.trialPrice; - } - return subPlan.price; - }; - - useEffect(() => { - if (!stripe || !elements || !activeProduct) { - return; - } - - const pr = stripe.paymentRequest({ - country: "US", - currency: "usd", - total: { - label: activeProduct.name || "Subscription", - amount: getAmountFromProduct(activeProduct), - }, - requestPayerName: true, - requestPayerEmail: true, - }); - - pr.canMakePayment().then((result) => { - if (result) { - setPaymentRequest(pr); - setCanMakePayment?.(true); - } - }); - - pr.on("paymentmethod", async (e) => { - const { error: stripeError, paymentIntent } = - await stripe.confirmCardPayment( - client_secret, - { - payment_method: e.paymentMethod.id, - }, - { handleActions: false } - ); - paymentIntent; - - if (stripeError) { - // Show error to your customer (e.g., insufficient funds) - navigate( - `${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=failed` - ); - return e.complete("fail"); - } - navigate( - returnUrl || - `${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=succeeded` - ); - e.complete("success"); - // Show a success message to your customer - // There's a risk of the customer closing the window before callback - // execution. Set up a webhook or plugin to listen for the - // payment_intent.succeeded event that handles any business critical - // post-payment actions. - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - client_secret, - dispatch, - elements, - navigate, - stripe, - subscriptionReceiptId, - ]); - - return ( - <> - {paymentRequest && ( - - )} - - ); -} - -export default ApplePayButton; diff --git a/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx new file mode 100644 index 0000000..f54297a --- /dev/null +++ b/src/components/PaymentPage/methods/ExpressCheckoutStripe/index.tsx @@ -0,0 +1,112 @@ +import { useMemo, useState } from "react"; +import styles from "./styles.module.css"; + +import { + useStripe, + useElements, + ExpressCheckoutElement, +} from "@stripe/react-stripe-js"; +import { + AvailablePaymentMethods, + StripeExpressCheckoutElementReadyEvent, +} from "@stripe/stripe-js"; +import { checkExpressCheckoutStripeFormAvailable } from "@/data/paymentMethods"; + +interface IExpressCheckoutStripeProps { + clientSecret: string; + returnUrl?: string; + isHide?: boolean; + onAvailable?: ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => void; + onChangeLoading?: (isLoading: boolean) => void; +} + +function ExpressCheckoutStripe({ + clientSecret, + returnUrl = "https://${window.location.host}/payment/result/", + isHide = false, + onAvailable, + onChangeLoading, +}: IExpressCheckoutStripeProps) { + const stripe = useStripe(); + const elements = useElements(); + const [errorMessage, setErrorMessage] = useState(); + const [isAvailable, setIsAvailable] = useState(false); + const isHideForm = useMemo( + () => isHide || !isAvailable, + [isAvailable, isHide] + ); + + const onConfirm = async () => + // event: StripeExpressCheckoutElementConfirmEvent + { + if (!stripe || !elements) { + // Stripe.js hasn't loaded yet. + // Make sure to disable form submission until Stripe.js has loaded. + return; + } + + const { error: submitError } = await elements.submit(); + if (submitError) { + setErrorMessage(submitError.message); + return; + } + + // // Create the PaymentIntent and obtain clientSecret + // const res = await fetch("/create-intent", { + // method: "POST", + // }); + // const { client_secret: clientSecret } = await res.json(); + + // Confirm the PaymentIntent using the details collected by the Express Checkout Element + const { error } = await stripe.confirmPayment({ + // `elements` instance used to create the Express Checkout Element + elements, + // `clientSecret` from the created PaymentIntent + clientSecret, + confirmParams: { + return_url: returnUrl, + }, + }); + + if (error) { + // This point is only reached if there's an immediate error when + // confirming the payment. Show the error to your customer (for example, payment details incomplete) + setErrorMessage(error.message); + } else { + // The payment UI automatically closes with a success animation. + // Your customer is redirected to your `return_url`. + } + }; + + const onReady = (event: StripeExpressCheckoutElementReadyEvent) => { + const _isAvailable = checkExpressCheckoutStripeFormAvailable( + event.availablePaymentMethods + ); + setIsAvailable(_isAvailable); + onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods); + onChangeLoading && onChangeLoading(false); + }; + + return ( +
+ onChangeLoading && onChangeLoading(false)} + onConfirm={onConfirm} + options={{ + layout: { + maxColumns: 1, + overflow: "never", + }, + paymentMethodOrder: ["apple_pay", "google_pay", "amazon_pay", "link"], + }} + /> + {errorMessage &&

{errorMessage}

} +
+ ); +} + +export default ExpressCheckoutStripe; diff --git a/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css b/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css new file mode 100644 index 0000000..68a9185 --- /dev/null +++ b/src/components/PaymentPage/methods/ExpressCheckoutStripe/styles.module.css @@ -0,0 +1,16 @@ +.container { + width: 100%; +} + +.hide { + height: 0; + visibility: hidden; +} + +.error { + width: 100%; + color: #FF5758; + text-align: center; + font-size: 14px; + margin-top: 8px; +} \ No newline at end of file diff --git a/src/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton/index.tsx b/src/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton/index.tsx new file mode 100644 index 0000000..3b686d5 --- /dev/null +++ b/src/components/PaymentPage/methods/StripeButton/CheckAvailableStripeButton/index.tsx @@ -0,0 +1,33 @@ +import { IPaywallProduct } from "@/api/resources/Paywall"; +import { useCanUseStripeButton } from "@/hooks/payment/useCanUseStripeButton"; +import { actions } from "@/store"; +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; + +interface ICheckAvailableStripeButtonProps { + activeProduct: IPaywallProduct | null; + clientSecret: string; +} + +function CheckAvailableStripeButton({ + activeProduct, + clientSecret, +}: ICheckAvailableStripeButtonProps) { + const dispatch = useDispatch(); + const { paymentRequest, availableMethods } = useCanUseStripeButton({ + activeProduct, + client_secret: clientSecret, + }); + + useEffect(() => { + if (paymentRequest && availableMethods) { + dispatch( + actions.payment.updateStripeButton({ paymentRequest, availableMethods }) + ); + } + }, [availableMethods, dispatch, paymentRequest]); + + return <>; +} + +export default CheckAvailableStripeButton; diff --git a/src/components/PaymentPage/methods/StripeButton/index.tsx b/src/components/PaymentPage/methods/StripeButton/index.tsx new file mode 100644 index 0000000..9b47aa1 --- /dev/null +++ b/src/components/PaymentPage/methods/StripeButton/index.tsx @@ -0,0 +1,27 @@ +import { PaymentRequestButtonElement } from "@stripe/react-stripe-js"; +import { CanMakePaymentResult, PaymentRequest } from "@stripe/stripe-js"; +import styles from "./styles.module.css"; + +export type TCanMakePaymentResult = CanMakePaymentResult | null; + +interface ApplePayButtonProps { + paymentRequest: PaymentRequest; +} + +function StripeButton({ paymentRequest }: ApplePayButtonProps) { + return ( + <> + {paymentRequest && ( + + )} + + ); +} + +export default StripeButton; diff --git a/src/components/PaymentPage/methods/ApplePayButton/styles.module.css b/src/components/PaymentPage/methods/StripeButton/styles.module.css similarity index 100% rename from src/components/PaymentPage/methods/ApplePayButton/styles.module.css rename to src/components/PaymentPage/methods/StripeButton/styles.module.css diff --git a/src/components/SubPlanInformation/TotalToday/index.tsx b/src/components/SubPlanInformation/TotalToday/index.tsx deleted file mode 100644 index d36b3c4..0000000 --- a/src/components/SubPlanInformation/TotalToday/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styles from "./styles.module.css"; -import Title from "@/components/Title"; - -interface ITotalTodayProps { - total: string; -} - -function TotalToday({ total }: ITotalTodayProps): JSX.Element { - return ( -
- {"Total today:"} - {total} -
- ); -} - -export default TotalToday; diff --git a/src/components/SubPlanInformation/TotalToday/styles.module.css b/src/components/SubPlanInformation/TotalToday/styles.module.css deleted file mode 100644 index fe619d9..0000000 --- a/src/components/SubPlanInformation/TotalToday/styles.module.css +++ /dev/null @@ -1,17 +0,0 @@ -.container { - width: 100%; - padding: 16px; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - background-color: #e7f5ee; - border-radius: 7px; -} - -.text { - font-size: 16px; - font-weight: 700; - color: #000; - margin: 0; -} diff --git a/src/components/SubPlanInformation/index.tsx b/src/components/SubPlanInformation/index.tsx deleted file mode 100644 index c872f5c..0000000 --- a/src/components/SubPlanInformation/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useTranslation } from "react-i18next"; -import styles from "./styles.module.css"; -import TotalToday from "./TotalToday"; -import ApplePayButton from "../PaymentPage/methods/ApplePayButton"; -import { IPaywallProduct } from "@/api/resources/Paywall"; - -interface ISubPlanInformationProps { - product: IPaywallProduct; - client_secret?: string; -} - -const getPrice = (product: IPaywallProduct): string => { - return `$${ - (product.trialPrice === 100 ? 99 : product.trialPrice || 0) / 100 - }`; -}; - -function SubPlanInformation({ - product, - client_secret, -}: ISubPlanInformationProps): JSX.Element { - const { t } = useTranslation(); - - return ( -
- - {client_secret && ( - - )} -

- {t("auweb.pay.information").replaceAll("%@", getPrice(product))}. -

-
- ); -} - -export default SubPlanInformation; diff --git a/src/components/SubPlanInformation/styles.module.css b/src/components/SubPlanInformation/styles.module.css deleted file mode 100644 index 7c6cd7d..0000000 --- a/src/components/SubPlanInformation/styles.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.container { - width: 100%; - max-width: 300px; - display: flex; - flex-direction: column; - gap: 20px; -} - -.description { - font-size: 13px; - color: #666666; - text-align: left; - font-weight: 400; - line-height: 16px; - padding-bottom: 16px; -} - -.pay-pal-button { - width: 100%; - height: 60px; - display: flex; - align-items: center; - justify-content: center; - background-color: #ffc43a; - border-radius: 7px; -} - -.errors { - color: red; - text-align: center; -} diff --git a/src/components/pages/ABDesign/v1/components/Checkbox/index.tsx b/src/components/pages/ABDesign/v1/components/Checkbox/index.tsx new file mode 100644 index 0000000..4988a4d --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/Checkbox/index.tsx @@ -0,0 +1,34 @@ +import styles from "./styles.module.css"; + +interface ICheckboxProps { + checked: boolean; + onChange: () => void; +} + +function Checkbox({ checked, onChange }: ICheckboxProps) { + return ( +
+ + + + + + + +
+ ); +} + +export default Checkbox; diff --git a/src/components/pages/ABDesign/v1/components/Checkbox/styles.module.css b/src/components/pages/ABDesign/v1/components/Checkbox/styles.module.css new file mode 100644 index 0000000..d760c66 --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/Checkbox/styles.module.css @@ -0,0 +1,92 @@ +.checkbox-wrapper-4 * { + box-sizing: border-box; +} +.checkbox-wrapper-4 .cbx { + -webkit-user-select: none; + user-select: none; + cursor: pointer; + padding: 2px; + border-radius: 6px; + overflow: hidden; + transition: all 0.2s ease; + display: inline-block; +} +.checkbox-wrapper-4 .cbx span { + float: left; + vertical-align: middle; + transform: translate3d(0, 0, 0); +} +.checkbox-wrapper-4 .cbx span:first-child { + position: relative; + width: 20px; + height: 20px; + border-radius: 4px; + transform: scale(1); + border: 1px solid #484848; + transition: all 0.2s ease; + box-shadow: 0 1px 1px rgba(0,16,75,0.05); +} +.checkbox-wrapper-4 .cbx span:first-child svg { + position: absolute; + top: 50%; + left: 50%; + fill: none; + stroke: #fff; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: 16px; + stroke-dashoffset: 16px; + transition: all 0.3s ease; + transition-delay: 0.1s; + transform: translate3d(-50%, -50%, 0); +} +.checkbox-wrapper-4 .cbx span:last-child { + padding-left: 8px; + line-height: 18px; +} +.checkbox-wrapper-4 .inp-cbx { + position: absolute; + visibility: hidden; +} +.checkbox-wrapper-4 .inp-cbx:checked + .cbx span:first-child { + background: #07f; + border-color: #07f; + animation: wave-4 0.4s ease; +} +.checkbox-wrapper-4 .inp-cbx:checked + .cbx span:first-child svg { + stroke-dashoffset: 0; +} +.checkbox-wrapper-4 .inline-svg { + position: absolute; + width: 0; + height: 0; + pointer-events: none; + user-select: none; +} +@media screen and (max-width: 640px) { + .checkbox-wrapper-4 .cbx { + width: 100%; + display: inline-block; + } +} +@-moz-keyframes wave-4 { + 50% { + transform: scale(0.9); + } +} +@-webkit-keyframes wave-4 { + 50% { + transform: scale(0.9); + } +} +@-o-keyframes wave-4 { + 50% { + transform: scale(0.9); + } +} +@keyframes wave-4 { + 50% { + transform: scale(0.9); + } +} \ No newline at end of file diff --git a/src/components/pages/ABDesign/v1/components/PrivacyPolicy/index.tsx b/src/components/pages/ABDesign/v1/components/PrivacyPolicy/index.tsx new file mode 100644 index 0000000..e69cf58 --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/PrivacyPolicy/index.tsx @@ -0,0 +1,45 @@ +import Checkbox from "../Checkbox"; +import styles from "./styles.module.css"; +import { useDispatch, useSelector } from "react-redux"; +import { actions, selectors } from "@/store"; + +interface IPrivacyPolicyProps { + containerClassName?: string; +} + +function PrivacyPolicy({ containerClassName = "" }: IPrivacyPolicyProps) { + const dispatch = useDispatch(); + const { checked } = useSelector(selectors.selectPrivacyPolicy); + + const handleChange = () => { + dispatch(actions.privacyPolicy.updateChecked(!checked)); + }; + + return ( +
+ +

+ I agree to the{" "} + + Privacy Policy + + ,{" "} + + Terms of use + {" "} + and to the use of cookies and tracking technologies, that require your + consent +

+
+ ); +} + +export default PrivacyPolicy; diff --git a/src/components/pages/ABDesign/v1/components/PrivacyPolicy/styles.module.css b/src/components/pages/ABDesign/v1/components/PrivacyPolicy/styles.module.css new file mode 100644 index 0000000..b5e2072 --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/PrivacyPolicy/styles.module.css @@ -0,0 +1,16 @@ +.container { + width: 100%; + display: flex; + flex-direction: row; + gap: 8px; +} + +.text { + font-size: 14px; + line-height: 125%; + color: #515151; +} + +.text > a { + text-decoration: underline; +} \ No newline at end of file diff --git a/src/components/pages/ABDesign/v1/components/Toast/ErrorIcon/index.tsx b/src/components/pages/ABDesign/v1/components/Toast/ErrorIcon/index.tsx new file mode 100644 index 0000000..2a532ab --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/Toast/ErrorIcon/index.tsx @@ -0,0 +1,24 @@ +function ErrorIcon() { + return ( + + + + + + ); +} + +export default ErrorIcon; diff --git a/src/components/pages/ABDesign/v1/components/Toast/index.tsx b/src/components/pages/ABDesign/v1/components/Toast/index.tsx new file mode 100644 index 0000000..6f94359 --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/Toast/index.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import ErrorIcon from "./ErrorIcon"; +import styles from "./styles.module.css"; + +interface IToastProps { + variant: "error"; + children: React.ReactNode; + classNameContainer?: string; + classNameToast?: string; +} + +function Toast({ + variant, + children, + classNameContainer = "", + classNameToast = "", +}: IToastProps) { + return ( +
+
+ {variant === "error" && } + {children} +
+
+ ); +} + +export default Toast; diff --git a/src/components/pages/ABDesign/v1/components/Toast/styles.module.css b/src/components/pages/ABDesign/v1/components/Toast/styles.module.css new file mode 100644 index 0000000..202a15a --- /dev/null +++ b/src/components/pages/ABDesign/v1/components/Toast/styles.module.css @@ -0,0 +1,26 @@ +.toast { + width: 100%; + display: grid; + grid-template-columns: 24px 1fr; + gap: 6px; + align-items: center; + padding: 16px; + border-radius: 12px; + font-size: 14px; + color: #000; + animation: appearance .8s linear(0 0%, 0 1.8%, 0.01 3.6%, 0.08 10.03%, 0.15 14.25%, 0.2 14.34%, 0.31 14.14%, 0.41 17.21%, 0.49 19.04%, 0.58 20.56%, 0.66 22.07%, 0.76 23.87%, 0.84 26.07%, 0.93 28.04%, 1.03 31.14%, 1.09 37.31%, 1.09 44.28%, 1.02 49.41%, 0.96 55%, 0.98 64%, 0.99 74.4%, 1 86.4%, 1 100%); + animation-fill-mode: forwards; +} + +.toast.error { + background-color: #ffdcdc; +} + +@keyframes appearance { + 0% { + transform: translateY(100%); + } + 100% { + transform: translateY(0); + } +} \ No newline at end of file diff --git a/src/components/pages/ABDesign/v1/pages/EmailEnterPage/index.tsx b/src/components/pages/ABDesign/v1/pages/EmailEnterPage/index.tsx index 6f4a44c..106c13b 100644 --- a/src/components/pages/ABDesign/v1/pages/EmailEnterPage/index.tsx +++ b/src/components/pages/ABDesign/v1/pages/EmailEnterPage/index.tsx @@ -133,19 +133,19 @@ function EmailEnterPage({ Enter your email

{t("we_dont_share")}

+ setIsValidEmail(false)} + /> setIsValidName(!isRequiredName)} /> - setIsValidEmail(false)} - /> (null); + const { checked: privacyPolicyChecked } = useSelector( + selectors.selectPrivacyPolicy + ); useEffect(() => { const isShowTryApp = targetId === "i"; dispatch(actions.userConfig.addIsShowTryApp(isShowTryApp)); }, [dispatch, targetId]); - const selectGender = async (gender: Gender) => { - setSelectedGender(gender); + useEffect(() => { + if (privacyPolicyChecked && selectedGender) { + handleNext(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [privacyPolicyChecked, selectedGender]); + + const handleNext = async () => { + if (!selectedGender) return; await new Promise((resolve) => setTimeout(resolve, 1000)); - dispatch(actions.questionnaire.update({ gender: gender.id })); + dispatch(actions.questionnaire.update({ gender: selectedGender.id })); if (productKey === EProductKeys["moons.pdf.aura"]) { return navigate(routes.client.epeBirthdate()); } @@ -41,6 +53,18 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element { navigate(`/v1/questionnaire/profile/flowChoice`); }; + const selectGender = async (gender: Gender) => { + if (selectedGender?.id === gender.id) { + setSelectedGender(null); + } else { + setSelectedGender(gender); + } + if (!privacyPolicyChecked) { + return; + } + handleNext(); + }; + const getButtonBGColor = (gender: Gender): string => { const { colorAssociation } = gender; if (Array.isArray(colorAssociation)) { @@ -114,6 +138,12 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element { ))} + + {selectedGender && !privacyPolicyChecked && ( + + To continue, please accept our terms and policies + + )} ); } diff --git a/src/components/pages/ABDesign/v1/pages/Gender/styles.module.css b/src/components/pages/ABDesign/v1/pages/Gender/styles.module.css index 09251cc..6f12d89 100644 --- a/src/components/pages/ABDesign/v1/pages/Gender/styles.module.css +++ b/src/components/pages/ABDesign/v1/pages/Gender/styles.module.css @@ -106,18 +106,43 @@ .gender--selected { transform: scale(1.1); - /* animation: gender-click 1s linear; */ + /* animation: gender-click 1.4s linear; */ } .gender--selected .gender__slide-element { left: calc(100% - 27px - 10px); + /* animation: gender-slide 1.4s linear; */ +} + +.privacy-policy { + max-width: 316px; + margin-top: 26px; +} + +.toast-container { + margin-top: 16px; } @keyframes gender-click { 0% { transform: scale(1); } - 100% { + 50% { transform: scale(1.1); } + 100% { + transform: scale(1); + } +} + +@keyframes gender-slide { + 0% { + left: 10px; + } + 50% { + left: calc(100% - 27px - 10px); + } + 100% { + left: 10px; + } } \ No newline at end of file diff --git a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx index 5f14274..bcddde0 100644 --- a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx +++ b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/index.tsx @@ -4,9 +4,8 @@ import PaymentMethodsChoice from "../PaymentMethodsChoice"; import { useEffect, useMemo, useState } from "react"; import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods"; import { Elements } from "@stripe/react-stripe-js"; -import ApplePayButton from "@/components/PaymentPage/methods/ApplePayButton"; import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm"; -import { Stripe, loadStripe } from "@stripe/stripe-js"; +import { AvailablePaymentMethods, Stripe, loadStripe } from "@stripe/stripe-js"; import { useSelector } from "react-redux"; import { selectors } from "@/store"; import { useNavigate } from "react-router-dom"; @@ -16,6 +15,7 @@ import SecurityPayments from "../SecurityPayments"; import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall"; import { usePaywall } from "@/hooks/paywall/usePaywall"; import { useMakePayment } from "@/hooks/payment/useMakePayment"; +import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe"; interface IPaymentModalProps { activeProduct?: IPaywallProduct; @@ -41,14 +41,14 @@ function PaymentModal({ const activeProductFromStore = useSelector(selectors.selectActiveProduct); const _activeProduct = activeProduct ? activeProduct : activeProductFromStore; const { products, paywallId, placementId } = usePaywall({ placementKey }); - + const { paymentIntentId, clientSecret, returnUrl: checkoutUrl, paymentType, publicKey, - isLoading, + isLoading: isLoadingPayment, error, } = useMakePayment({ productId: _activeProduct?._id || "", @@ -57,16 +57,27 @@ function PaymentModal({ returnPaidUrl: returnUrl, }); + const [availableMethods, setAvailableMethods] = useState< + AvailablePaymentMethods | undefined + >(); + + const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] = + useState(true); + + const isLoading = useMemo(() => { + return isLoadingPayment || isLoadingExpressCheckout; + }, [isLoadingPayment, isLoadingExpressCheckout]); + if (checkoutUrl?.length) { window.location.href = checkoutUrl; } const paymentMethodsButtons = useMemo(() => { - return paymentMethods; - }, []); + return paymentMethods(availableMethods || null); + }, [availableMethods]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( - EPaymentMethod.PAYMENT_BUTTONS + paymentMethodsButtons[0].id ); const onSelectPaymentMethod = (method: EPaymentMethod) => { @@ -86,15 +97,16 @@ function PaymentModal({ })(); }, [_activeProduct, navigate, products, publicKey]); - if (isLoading) { - return ( -
-
- -
-
- ); - } + const onAvailableExpressCheckout = ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => { + if (isAvailable && availableMethods) { + setAvailableMethods(availableMethods); + return setSelectedPaymentMethod(EPaymentMethod.PAYMENT_BUTTONS); + } + return setAvailableMethods(undefined); + }; if (error?.length) { return ( @@ -107,60 +119,75 @@ function PaymentModal({ } return ( -
- - Choose payment method - - - {_activeProduct && ( -
- {!noTrial && ( - <> -

- You will be charged only{" "} - ${getPrice(_activeProduct)} for your 3-day trial. -

-

- We`ll email you a reminder before your trial period ends. -

- - )} - -

- Cancel anytime. The charge will appear on your bill as witapps. -

+ <> + {isLoading && ( +
+
+ +
)} -
- {stripePromise && clientSecret && ( - - {selectedPaymentMethod === EPaymentMethod.PAYMENT_BUTTONS && ( -
- -
+
+ + Choose payment method + + + {_activeProduct && ( +
+ {!noTrial && ( + <> +

+ You will be charged only{" "} + ${getPrice(_activeProduct)} for your 3-day trial. +

+

+ We`ll email you a reminder before your trial period + ends. +

+ )} - {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( - - )} - +

+ Cancel anytime. The charge will appear on your bill as witapps. +

+
)} +
+ {stripePromise && clientSecret && ( + + + onAvailableExpressCheckout(_isAvailable, _availableMethods) + } + onChangeLoading={(isLoading) => + setIsLoadingExpressCheckout(isLoading) + } + /> + {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( + + )} + + )} +
+ +

1123 Rimer Dr Moraga, California 94556

- -

500 N RAINBOW BLVD LAS VEGAS, NV 89107

-
+ ); } diff --git a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css index db76c7b..5bc386a 100644 --- a/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css +++ b/src/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentModal/styles.module.css @@ -8,6 +8,12 @@ color: #2f2e37; } +.payment-modal.hide { + min-height: 0; + height: 0; + opacity: 0; +} + .title { font-weight: 700; font-size: 20px; @@ -31,6 +37,7 @@ .address { margin-bottom: 24px; + text-transform: uppercase; } .payment-method { diff --git a/src/components/pages/AdditionalPurchases/components/PaymentAddress/index.tsx b/src/components/pages/AdditionalPurchases/components/PaymentAddress/index.tsx index 35a69b9..3a45bf6 100644 --- a/src/components/pages/AdditionalPurchases/components/PaymentAddress/index.tsx +++ b/src/components/pages/AdditionalPurchases/components/PaymentAddress/index.tsx @@ -2,7 +2,7 @@ import styles from "./styles.module.css" function PaymentAddress() { return ( -

500 N RAINBOW BLVD LAS VEGAS, NV 89107

+

1123 Rimer Dr Moraga, California 94556

) } diff --git a/src/components/pages/AdditionalPurchases/components/PaymentAddress/styles.module.css b/src/components/pages/AdditionalPurchases/components/PaymentAddress/styles.module.css index 4e8b4f9..c08a20b 100644 --- a/src/components/pages/AdditionalPurchases/components/PaymentAddress/styles.module.css +++ b/src/components/pages/AdditionalPurchases/components/PaymentAddress/styles.module.css @@ -4,4 +4,5 @@ text-align: center; margin-top: 9px; color: rgb(130, 130, 130); + text-transform: uppercase; } diff --git a/src/components/pages/SinglePaymentPage/PaymentForm/index.tsx b/src/components/pages/SinglePaymentPage/PaymentForm/index.tsx index ecaf0d1..903e693 100644 --- a/src/components/pages/SinglePaymentPage/PaymentForm/index.tsx +++ b/src/components/pages/SinglePaymentPage/PaymentForm/index.tsx @@ -34,7 +34,7 @@ function PaymentForm({ )}
-

500 N RAINBOW BLVD LAS VEGAS, NV 89107

+

1123 Rimer Dr Moraga, California 94556

); } diff --git a/src/components/pages/SinglePaymentPage/PaymentForm/styles.module.css b/src/components/pages/SinglePaymentPage/PaymentForm/styles.module.css index ac07530..0e2cfb1 100644 --- a/src/components/pages/SinglePaymentPage/PaymentForm/styles.module.css +++ b/src/components/pages/SinglePaymentPage/PaymentForm/styles.module.css @@ -41,4 +41,5 @@ .address { color: gray; font-size: 10px; + text-transform: uppercase; } diff --git a/src/components/pages/TrialPayment/components/PaymentModal/index.tsx b/src/components/pages/TrialPayment/components/PaymentModal/index.tsx index a6f9908..eb64b55 100644 --- a/src/components/pages/TrialPayment/components/PaymentModal/index.tsx +++ b/src/components/pages/TrialPayment/components/PaymentModal/index.tsx @@ -4,9 +4,8 @@ import PaymentMethodsChoice from "../PaymentMethodsChoice"; import { useEffect, useMemo, useState } from "react"; import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods"; import { Elements } from "@stripe/react-stripe-js"; -import ApplePayButton from "@/components/PaymentPage/methods/ApplePayButton"; import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm"; -import { Stripe, loadStripe } from "@stripe/stripe-js"; +import { AvailablePaymentMethods, Stripe, loadStripe } from "@stripe/stripe-js"; import { useSelector } from "react-redux"; import { selectors } from "@/store"; import Loader from "@/components/Loader"; @@ -14,6 +13,9 @@ import SecurityPayments from "../SecurityPayments"; import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall"; import { usePaywall } from "@/hooks/paywall/usePaywall"; import { useMakePayment } from "@/hooks/payment/useMakePayment"; +import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe"; +import routes from "@/routes"; +import { useNavigate } from "react-router-dom"; interface IPaymentModalProps { activeProduct?: IPaywallProduct; @@ -35,6 +37,7 @@ function PaymentModal({ returnUrl, placementKey = EPlacementKeys["aura.placement.main"], }: IPaymentModalProps) { + const navigate = useNavigate(); const [stripePromise, setStripePromise] = useState | null>(null); @@ -50,7 +53,7 @@ function PaymentModal({ returnUrl: checkoutUrl, paymentType, publicKey, - isLoading, + isLoading: isLoadingPayment, error, } = useMakePayment({ productId: _activeProduct?._id || "", @@ -59,19 +62,27 @@ function PaymentModal({ returnPaidUrl: returnUrl, }); + const [availableMethods, setAvailableMethods] = useState< + AvailablePaymentMethods | undefined + >(); + + const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] = + useState(true); + + const isLoading = useMemo(() => { + return isLoadingPayment || isLoadingExpressCheckout; + }, [isLoadingPayment, isLoadingExpressCheckout]); + if (checkoutUrl?.length) { window.location.href = checkoutUrl; } const paymentMethodsButtons = useMemo(() => { - // return paymentMethods.filter( - // (method) => method.id !== EPaymentMethod.PAYMENT_BUTTONS - // ); - return paymentMethods; - }, []); + return paymentMethods(availableMethods || null); + }, [availableMethods]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( - EPaymentMethod.PAYMENT_BUTTONS + paymentMethodsButtons[0].id ); const onSelectPaymentMethod = (method: EPaymentMethod) => { @@ -82,18 +93,25 @@ function PaymentModal({ (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()); + } })(); - }, [products, publicKey]); + }, [_activeProduct, navigate, products, publicKey]); - if (isLoading) { - return ( -
-
- -
-
- ); - } + const onAvailableExpressCheckout = ( + isAvailable: boolean, + availableMethods: AvailablePaymentMethods | undefined + ) => { + if (isAvailable && availableMethods) { + setAvailableMethods(availableMethods); + return setSelectedPaymentMethod(EPaymentMethod.PAYMENT_BUTTONS); + } + return setAvailableMethods(undefined); + }; if (error?.length) { return ( @@ -106,60 +124,75 @@ function PaymentModal({ } return ( -
- - Choose payment method - - - {_activeProduct && ( -
- {!noTrial && ( - <> -

- You will be charged only{" "} - ${getPrice(_activeProduct)} for your 3-day trial. -

-

- We`ll email you a reminder before your trial period ends. -

- - )} - -

- Cancel anytime. The charge will appear on your bill as witapps. -

+ <> + {isLoading && ( +
+
+ +
)} -
- {stripePromise && clientSecret && ( - - {selectedPaymentMethod === EPaymentMethod.PAYMENT_BUTTONS && ( -
- -
+
+ + Choose payment method + + + {_activeProduct && ( +
+ {!noTrial && ( + <> +

+ You will be charged only{" "} + ${getPrice(_activeProduct)} for your 3-day trial. +

+

+ We`ll email you a reminder before your trial period + ends. +

+ )} - {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( - - )} - +

+ Cancel anytime. The charge will appear on your bill as witapps. +

+
)} +
+ {stripePromise && clientSecret && ( + + + onAvailableExpressCheckout(_isAvailable, _availableMethods) + } + onChangeLoading={(isLoading) => + setIsLoadingExpressCheckout(isLoading) + } + /> + {selectedPaymentMethod === EPaymentMethod.CREDIT_CARD && ( + + )} + + )} +
+ +

1123 Rimer Dr Moraga, California 94556

- -

500 N RAINBOW BLVD LAS VEGAS, NV 89107

-
+ ); } diff --git a/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css b/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css index db76c7b..5bc386a 100644 --- a/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css +++ b/src/components/pages/TrialPayment/components/PaymentModal/styles.module.css @@ -8,6 +8,12 @@ color: #2f2e37; } +.payment-modal.hide { + min-height: 0; + height: 0; + opacity: 0; +} + .title { font-weight: 700; font-size: 20px; @@ -31,6 +37,7 @@ .address { margin-bottom: 24px; + text-transform: uppercase; } .payment-method { diff --git a/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx b/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx index dd1721a..b78586b 100644 --- a/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx +++ b/src/components/ui/PaymentMethodsButtons/PaymentButtons/index.tsx @@ -1,9 +1,33 @@ import styles from "./styles.module.css"; +import { TCanMakePaymentResult } from "@/components/PaymentPage/methods/StripeButton"; -function PaymentButtons() { - return
- ApplePay -
; +interface IPaymentButtonsProps { + availableMethods: TCanMakePaymentResult; +} +const Image = ({ availableMethods }: IPaymentButtonsProps) => { + if (!availableMethods) return <>; + if (availableMethods["applePay"]) { + return ApplePay; + } + if (availableMethods["googlePay"]) { + return google; + } + if (availableMethods["amazon"]) { + return AmazonPay; + } + if (availableMethods["link"]) { + return LinkPay; + } + return <>; +}; + +function PaymentButtons({ availableMethods }: IPaymentButtonsProps) { + if (!availableMethods) return <>; + return ( +
+ +
+ ); } export default PaymentButtons; diff --git a/src/components/ui/PaymentMethodsButtons/PaymentButtons/styles.module.css b/src/components/ui/PaymentMethodsButtons/PaymentButtons/styles.module.css index 7d1360f..0bad093 100644 --- a/src/components/ui/PaymentMethodsButtons/PaymentButtons/styles.module.css +++ b/src/components/ui/PaymentMethodsButtons/PaymentButtons/styles.module.css @@ -8,5 +8,5 @@ } .container > img { - height: 16px; + height: 22px; } \ No newline at end of file diff --git a/src/data/paymentMethods.tsx b/src/data/paymentMethods.tsx index 5428dc2..3f79077 100644 --- a/src/data/paymentMethods.tsx +++ b/src/data/paymentMethods.tsx @@ -1,5 +1,7 @@ +import { TCanMakePaymentResult } from "@/components/PaymentPage/methods/StripeButton"; import CreditCard from "@/components/ui/PaymentMethodsButtons/CreditCard"; import PaymentButtons from "@/components/ui/PaymentMethodsButtons/PaymentButtons"; +import { AvailablePaymentMethods } from "@stripe/stripe-js"; export enum EPaymentMethod { CREDIT_CARD = "card", @@ -11,13 +13,54 @@ export interface IPaymentMethod { component: JSX.Element; } -export const paymentMethods: IPaymentMethod[] = [ - { - id: EPaymentMethod.PAYMENT_BUTTONS, - component: , - }, - { - id: EPaymentMethod.CREDIT_CARD, - component: , - }, -]; +// export const paymentMethods: IPaymentMethod[] = [ +// { +// id: EPaymentMethod.PAYMENT_BUTTONS, +// component: , +// }, +// { +// id: EPaymentMethod.CREDIT_CARD, +// component: , +// }, +// ]; + +export const checkExpressCheckoutStripeFormAvailable = ( + availablePaymentMethods: undefined | AvailablePaymentMethods +) => { + if (!availablePaymentMethods) return false; + let result = false; + for (const key in availablePaymentMethods) { + if (availablePaymentMethods[key as keyof AvailablePaymentMethods]) { + result = true; + break; + } + } + return result; +}; + +export function paymentMethods( + availableMethods: TCanMakePaymentResult +): IPaymentMethod[] { + let methods = [ + { + id: EPaymentMethod.PAYMENT_BUTTONS, + component: , + }, + { + id: EPaymentMethod.CREDIT_CARD, + component: , + }, + ]; + + if ( + !availableMethods || + !checkExpressCheckoutStripeFormAvailable( + availableMethods as AvailablePaymentMethods + ) + ) { + methods = methods.filter( + (method) => method.id !== EPaymentMethod.PAYMENT_BUTTONS + ); + } + return methods; +} diff --git a/src/hooks/authentication/use-authentication.ts b/src/hooks/authentication/use-authentication.ts index da7602f..a1ec078 100644 --- a/src/hooks/authentication/use-authentication.ts +++ b/src/hooks/authentication/use-authentication.ts @@ -33,6 +33,7 @@ export const useAuthentication = () => { partnerBirthPlace, partnerBirthtime, } = useSelector(selectors.selectQuestionnaire) + const { checked, dateOfCheck } = useSelector(selectors.selectPrivacyPolicy) const birthdateFromForm = useSelector(selectors.selectBirthdate); const birthtimeFromForm = useSelector(selectors.selectBirthtime); @@ -94,7 +95,9 @@ export const useAuthentication = () => { birthplace: { address: partnerBirthPlace, }, - } + }, + sign: checked, + signDate: dateOfCheck }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ @@ -108,6 +111,8 @@ export const useAuthentication = () => { partnerName, username, birthtime, + checked, + dateOfCheck ]); const authorization = useCallback(async (email: string, source: ESourceAuthorization) => { diff --git a/src/hooks/payment/useCanUseStripeButton.ts b/src/hooks/payment/useCanUseStripeButton.ts new file mode 100644 index 0000000..f387d22 --- /dev/null +++ b/src/hooks/payment/useCanUseStripeButton.ts @@ -0,0 +1,98 @@ +import { IPaywallProduct } from "@/api/resources/Paywall"; +import routes from "@/routes"; +import { useElements, useStripe } from "@stripe/react-stripe-js"; +import { CanMakePaymentResult, PaymentRequest } from "@stripe/stripe-js"; +import { useEffect, useMemo, useState } from "react" +import { useNavigate } from "react-router-dom"; + +export type TCanMakePaymentResult = CanMakePaymentResult | null; + + +const getAmountFromProduct = (subPlan: IPaywallProduct) => { + if (subPlan.isTrial) { + return subPlan.trialPrice; + } + return subPlan.price; +}; + +interface IUseCanUseStripeButton { + activeProduct: IPaywallProduct | null; + client_secret: string; + subscriptionReceiptId?: string; + returnUrl?: string; + setCanMakePayment?: (canMakePayment: TCanMakePaymentResult) => void; + setCanMakePaymentLoading?: (loading: boolean) => void; +} + +export const useCanUseStripeButton = ({ + activeProduct, + client_secret, + subscriptionReceiptId, + returnUrl, +}: IUseCanUseStripeButton) => { + const stripe = useStripe(); + const elements = useElements(); + const [paymentRequest, setPaymentRequest] = useState( + null + ); + const [availableMethods, setAvailableMethods] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + if (!stripe || !elements || !activeProduct) { + return; + } + + const pr = stripe.paymentRequest({ + country: "US", + currency: "usd", + total: { + label: activeProduct.name || "Subscription", + amount: getAmountFromProduct(activeProduct), + }, + requestPayerName: true, + requestPayerEmail: true, + }); + + pr.canMakePayment() + .then((result) => { + if (result) { + setPaymentRequest(pr) + setAvailableMethods(result) + } + }) + + pr.on("paymentmethod", async (e) => { + const { error: stripeError, paymentIntent } = + await stripe.confirmCardPayment( + client_secret, + { + payment_method: e.paymentMethod.id, + }, + { handleActions: false } + ); + paymentIntent; + + if (stripeError) { + // Show error to your customer (e.g., insufficient funds) + navigate( + `${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=failed` + ); + return e.complete("fail"); + } + navigate( + returnUrl || + `${routes.client.paymentResult()}/${subscriptionReceiptId}/?redirect_status=succeeded` + ); + e.complete("success"); + }); + }, [activeProduct, client_secret, elements, navigate, returnUrl, stripe, subscriptionReceiptId]); + + return useMemo(() => ({ + paymentRequest, + availableMethods + }), [ + paymentRequest, + availableMethods + ]) +} \ No newline at end of file diff --git a/src/locales/dev.ts b/src/locales/dev.ts index 48ca5ee..9ee8f92 100755 --- a/src/locales/dev.ts +++ b/src/locales/dev.ts @@ -4,7 +4,7 @@ export default { next: "Next", date_of_birth: "What's your date of birth?", privacy_text: "By continuing, you agree to our and . Have a question? Reach our support team ", - eula: "Hint's EULA", + eula: "EULA", here: 'here', privacy_notice: 'Privacy Notice', born_time_question: "What time were you born?", diff --git a/src/store/index.ts b/src/store/index.ts index 868cc4d..33550ea 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -32,6 +32,7 @@ import payment, { actions as paymentActions, selectActiveProduct, selectIsDiscount, + selectStripeButton, selectSubscriptionReceipt, } from "./payment"; import subscriptionPlans, { @@ -69,6 +70,7 @@ import palmistry, { selectPalmistryLines, } from "./palmistry"; import { selectPaywallsIsMustUpdate, selectPaywalls } from "./paywalls"; +import privacyPolicy, { actions as privacyPolicyActions, selectPrivacyPolicy } from "./privacyPolicy"; const preloadedState = loadStore(); export const actions = { @@ -88,6 +90,7 @@ export const actions = { questionnaire: questionnaireActions, userConfig: userConfigActions, palmistry: palmistryActions, + privacyPolicy: privacyPolicyActions, reset: createAction("reset"), }; export const selectors = { @@ -120,6 +123,8 @@ export const selectors = { selectPalmistryLines, selectPaywalls, selectPaywallsIsMustUpdate, + selectPrivacyPolicy, + selectStripeButton, ...formSelectors, }; @@ -140,6 +145,7 @@ export const reducer = combineReducers({ userConfig, palmistry, paywalls, + privacyPolicy }); export type RootState = ReturnType; diff --git a/src/store/payment.ts b/src/store/payment.ts index 23dfc9d..50d4d06 100644 --- a/src/store/payment.ts +++ b/src/store/payment.ts @@ -1,13 +1,21 @@ import { IPaywallProduct } from "@/api/resources/Paywall"; import { SubscriptionReceipt } from "@/api/resources/UserSubscriptionReceipts"; +import { TCanMakePaymentResult } from "@/hooks/payment/useCanUseStripeButton"; import { createSlice, createSelector } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit"; +import { PaymentRequest } from "@stripe/stripe-js"; + +interface IStripeButton { + paymentRequest: PaymentRequest | null; + availableMethods: TCanMakePaymentResult; +} interface IPayment { selectedPrice: number | null; isDiscount: boolean; subscriptionReceipt: SubscriptionReceipt | null; activeProduct: IPaywallProduct | null; + stripeButton: IStripeButton; } const initialState: IPayment = { @@ -15,6 +23,10 @@ const initialState: IPayment = { isDiscount: false, subscriptionReceipt: null, activeProduct: null, + stripeButton: { + paymentRequest: null, + availableMethods: null, + } }; const paymentSlice = createSlice({ @@ -24,6 +36,9 @@ const paymentSlice = createSlice({ update(state, action: PayloadAction>) { return { ...state, ...action.payload }; }, + updateStripeButton(state, action: PayloadAction) { + return { ...state, stripeButton: action.payload }; + }, }, extraReducers: (builder) => builder.addCase("reset", () => initialState), }); @@ -45,4 +60,8 @@ export const selectSubscriptionReceipt = createSelector( (state: { payment: IPayment }) => state.payment.subscriptionReceipt, (payment) => payment ); +export const selectStripeButton = createSelector( + (state: { payment: IPayment }) => state.payment.stripeButton, + (payment) => payment +); export default paymentSlice.reducer; diff --git a/src/store/privacyPolicy.ts b/src/store/privacyPolicy.ts new file mode 100644 index 0000000..a70b5c0 --- /dev/null +++ b/src/store/privacyPolicy.ts @@ -0,0 +1,30 @@ +import { createSlice, createSelector } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +interface IPrivacyPolicy { + checked: boolean + dateOfCheck: string +} + +const initialState: IPrivacyPolicy = { + checked: false, + dateOfCheck: '', +} + +const privacyPolicySlice = createSlice({ + name: 'privacyPolicy', + initialState, + reducers: { + updateChecked(state, action: PayloadAction) { + return { ...state, checked: action.payload, dateOfCheck: new Date().toISOString() } + }, + }, + extraReducers: (builder) => builder.addCase('reset', () => initialState), +}) + +export const { actions } = privacyPolicySlice +export const selectPrivacyPolicy = createSelector( + (state: { privacyPolicy: IPrivacyPolicy }) => state.privacyPolicy, + (privacyPolicy) => privacyPolicy +) +export default privacyPolicySlice.reducer diff --git a/src/store/user.ts b/src/store/user.ts index 983e32b..1b07753 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -5,7 +5,7 @@ import { getClientLocale, getClientTimezone } from '../locales' const initialState: User.User = { id: undefined, - username: "I Am", + username: "", email: '', locale: getClientLocale(), state: '',