diff --git a/public/amazon-pay-mark.png b/public/amazon-pay-mark.png new file mode 100644 index 0000000..602efb7 Binary files /dev/null and b/public/amazon-pay-mark.png differ diff --git a/public/google-pay-mark.png b/public/google-pay-mark.png new file mode 100644 index 0000000..07cd76a Binary files /dev/null and b/public/google-pay-mark.png differ diff --git a/public/link-pay-mark.png b/public/link-pay-mark.png new file mode 100644 index 0000000..4184628 Binary files /dev/null and b/public/link-pay-mark.png differ 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: '',