Merge branch 'preview/email-pay-email' into 'develop'
Revert "fix: remove double image key and update filter of subscription plans" See merge request witapp/aura-webapp!60
This commit is contained in:
commit
d22157cde1
@ -23,7 +23,8 @@ import {
|
|||||||
GoogleAuth,
|
GoogleAuth,
|
||||||
SubscriptionPlans,
|
SubscriptionPlans,
|
||||||
AppleAuth,
|
AppleAuth,
|
||||||
AIRequestsV2
|
AIRequestsV2,
|
||||||
|
SinglePayment
|
||||||
} from './resources'
|
} from './resources'
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
@ -55,6 +56,9 @@ const api = {
|
|||||||
getZodiacs: createMethod<Zodiacs.Payload, Zodiacs.Response>(Zodiacs.createRequest),
|
getZodiacs: createMethod<Zodiacs.Payload, Zodiacs.Response>(Zodiacs.createRequest),
|
||||||
AIRequestsV2: createMethod<AIRequestsV2.Payload, AIRequestsV2.Response>(AIRequestsV2.createRequest),
|
AIRequestsV2: createMethod<AIRequestsV2.Payload, AIRequestsV2.Response>(AIRequestsV2.createRequest),
|
||||||
getAIRequestsV2: createMethod<AIRequestsV2.PayloadGet, AIRequestsV2.IAiResponseGet>(AIRequestsV2.createRequestGet),
|
getAIRequestsV2: createMethod<AIRequestsV2.PayloadGet, AIRequestsV2.IAiResponseGet>(AIRequestsV2.createRequestGet),
|
||||||
|
|
||||||
|
getSinglePaymentProducts: createMethod<SinglePayment.PayloadGet, SinglePayment.ResponseGet[]>(SinglePayment.createRequestGet),
|
||||||
|
createSinglePayment: createMethod<SinglePayment.PayloadPost, SinglePayment.ResponsePost | SinglePayment.ResponsePostExistPaymentData>(SinglePayment.createRequestPost),
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiContextValue = typeof api
|
export type ApiContextValue = typeof api
|
||||||
|
|||||||
82
src/api/resources/SinglePayment.ts
Normal file
82
src/api/resources/SinglePayment.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import routes from "@/routes";
|
||||||
|
import { getAuthHeaders } from "../utils";
|
||||||
|
|
||||||
|
interface Payload {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayloadGet extends Payload {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayloadPost extends Payload {
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
sign: string;
|
||||||
|
age: number;
|
||||||
|
};
|
||||||
|
partner: {
|
||||||
|
sign: string | null;
|
||||||
|
age: number | null;
|
||||||
|
};
|
||||||
|
paymentInfo: {
|
||||||
|
productId: string;
|
||||||
|
};
|
||||||
|
return_url: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseGet {
|
||||||
|
key: string;
|
||||||
|
productId: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponsePost {
|
||||||
|
paymentIntent: {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
client_secret: string;
|
||||||
|
paymentIntentId: string;
|
||||||
|
return_url?: string;
|
||||||
|
public_key: string;
|
||||||
|
product: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: null | string;
|
||||||
|
price: {
|
||||||
|
id: string;
|
||||||
|
unit_amount: number;
|
||||||
|
currency: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponsePostExistPaymentData {
|
||||||
|
payment: {
|
||||||
|
status: string;
|
||||||
|
invoiceId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRequestPost = ({ data, token }: PayloadPost): Request => {
|
||||||
|
const url = new URL(routes.server.dApiPaymentCheckout());
|
||||||
|
const body = JSON.stringify(data);
|
||||||
|
return new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: getAuthHeaders(token),
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRequestGet = ({ id, token }: PayloadGet): Request => {
|
||||||
|
id;
|
||||||
|
const url = new URL(routes.server.dApiTestPaymentProducts());
|
||||||
|
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
|
||||||
|
};
|
||||||
@ -22,3 +22,4 @@ export * as GoogleAuth from "./GoogleAuth";
|
|||||||
export * as SubscriptionPlans from "./SubscriptionPlans";
|
export * as SubscriptionPlans from "./SubscriptionPlans";
|
||||||
export * as AppleAuth from "./AppleAuth";
|
export * as AppleAuth from "./AppleAuth";
|
||||||
export * as AIRequestsV2 from "./AIRequestsV2";
|
export * as AIRequestsV2 from "./AIRequestsV2";
|
||||||
|
export * as SinglePayment from "./SinglePayment";
|
||||||
|
|||||||
@ -104,6 +104,9 @@ import AddReportPage from "../pages/AdditionalPurchases/pages/AddReport";
|
|||||||
import UnlimitedReadingsPage from "../pages/AdditionalPurchases/pages/UnlimitedReadings";
|
import UnlimitedReadingsPage from "../pages/AdditionalPurchases/pages/UnlimitedReadings";
|
||||||
import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultation";
|
import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultation";
|
||||||
import StepsManager from "@/components/palmistry/steps-manager/steps-manager";
|
import StepsManager from "@/components/palmistry/steps-manager/steps-manager";
|
||||||
|
import PaymentWithEmailPage from "../pages/PaymentWithEmailPage";
|
||||||
|
import SuccessPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage";
|
||||||
|
import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage";
|
||||||
|
|
||||||
const isProduction = import.meta.env.MODE === "production";
|
const isProduction = import.meta.env.MODE === "production";
|
||||||
|
|
||||||
@ -216,6 +219,13 @@ function App(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
|
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
|
||||||
|
{/* Email - Pay - Email */}
|
||||||
|
<Route path={routes.client.epeBirthdate()} element={<BirthdayPage />} />
|
||||||
|
<Route path={routes.client.epePayment()} element={<PaymentWithEmailPage />} />
|
||||||
|
<Route path={routes.client.epeSuccessPayment()} element={<SuccessPaymentPage />} />
|
||||||
|
<Route path={routes.client.epeFailPayment()} element={<FailPaymentPage />} />
|
||||||
|
{/* Email - Pay - Email */}
|
||||||
|
|
||||||
{/* Test Routes Start */}
|
{/* Test Routes Start */}
|
||||||
<Route path={routes.client.notFound()} element={<NotFoundPage />} />
|
<Route path={routes.client.notFound()} element={<NotFoundPage />} />
|
||||||
<Route path={routes.client.gender()} element={<GenderPage />}>
|
<Route path={routes.client.gender()} element={<GenderPage />}>
|
||||||
@ -331,8 +341,14 @@ function App(): JSX.Element {
|
|||||||
{/* Additional Purchases */}
|
{/* Additional Purchases */}
|
||||||
<Route element={<AdditionalPurchases />}>
|
<Route element={<AdditionalPurchases />}>
|
||||||
<Route path={routes.client.addReport()} element={<AddReportPage />} />
|
<Route path={routes.client.addReport()} element={<AddReportPage />} />
|
||||||
<Route path={routes.client.unlimitedReadings()} element={<UnlimitedReadingsPage />} />
|
<Route
|
||||||
<Route path={routes.client.addConsultation()} element={<AddConsultationPage />} />
|
path={routes.client.unlimitedReadings()}
|
||||||
|
element={<UnlimitedReadingsPage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.addConsultation()}
|
||||||
|
element={<AddConsultationPage />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
{/* Additional Purchases End */}
|
{/* Additional Purchases End */}
|
||||||
|
|
||||||
@ -472,15 +488,9 @@ function App(): JSX.Element {
|
|||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route
|
<Route path="/palmistry" element={<StepsManager />} />
|
||||||
path="/palmistry"
|
|
||||||
element={<StepsManager/>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
<Route path="/palmistry/:step" element={<StepsManager />} />
|
||||||
path="/palmistry/:step"
|
|
||||||
element={<StepsManager/>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@ -1,54 +1,83 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from "react";
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { actions, selectors } from '@/store'
|
import { actions, selectors } from "@/store";
|
||||||
import { DatePicker } from '../DateTimePicker'
|
import { DatePicker } from "../DateTimePicker";
|
||||||
import MainButton from '../MainButton'
|
import MainButton from "../MainButton";
|
||||||
import Policy from '../Policy'
|
import Policy from "../Policy";
|
||||||
import Purposes from '../Purposes'
|
import Purposes from "../Purposes";
|
||||||
import Title from '../Title'
|
import Title from "../Title";
|
||||||
import routes from '@/routes'
|
import routes from "@/routes";
|
||||||
import './styles.css'
|
import "./styles.css";
|
||||||
|
|
||||||
function BirthdayPage(): JSX.Element {
|
function BirthdayPage(): JSX.Element {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const birthdate = useSelector(selectors.selectBirthdate)
|
const birthdate = useSelector(selectors.selectBirthdate);
|
||||||
const [isDisabled, setIsDisabled] = useState(true)
|
const [isDisabled, setIsDisabled] = useState(true);
|
||||||
const handleNext = () => navigate(routes.client.didYouKnow())
|
const nextRoute = window.location.href.includes("/epe/")
|
||||||
|
? routes.client.epePayment()
|
||||||
|
: routes.client.didYouKnow();
|
||||||
|
const handleNext = () => navigate(nextRoute);
|
||||||
const handleValid = (birthdate: string) => {
|
const handleValid = (birthdate: string) => {
|
||||||
dispatch(actions.form.addDate(birthdate))
|
dispatch(actions.form.addDate(birthdate));
|
||||||
setIsDisabled(birthdate === '')
|
setIsDisabled(birthdate === "");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='page'>
|
<section className="page">
|
||||||
<Title variant='h3' className='mt-24'>{t('lets_start')}</Title>
|
<Title variant="h3" className="mt-24">
|
||||||
<Title variant='h2'>{t('date_of_birth')}</Title>
|
{t("lets_start")}
|
||||||
|
</Title>
|
||||||
|
<Title variant="h2">{t("date_of_birth")}</Title>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
name='birthdate'
|
name="birthdate"
|
||||||
value={birthdate}
|
value={birthdate}
|
||||||
onValid={handleValid}
|
onValid={handleValid}
|
||||||
onInvalid={() => setIsDisabled(true)}
|
onInvalid={() => setIsDisabled(true)}
|
||||||
inputClassName='date-picker-input'
|
inputClassName="date-picker-input"
|
||||||
/>
|
/>
|
||||||
<MainButton onClick={handleNext} disabled={isDisabled}>
|
<MainButton onClick={handleNext} disabled={isDisabled}>
|
||||||
{t('next')}
|
{t("next")}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
<footer className='footer'>
|
<footer className="footer">
|
||||||
<Policy>
|
<Policy>
|
||||||
{t('privacy_text', {
|
{t("privacy_text", {
|
||||||
eulaLink: <a href='https://aura.wit.life/terms' target='_blank' rel='noopener noreferrer'>{t('eula')}</a>,
|
eulaLink: (
|
||||||
privacyLink: <a href='https://aura.wit.life/privacy' target='_blank' rel='noopener noreferrer'>{t('privacy_notice')}</a>,
|
<a
|
||||||
clickHere: <a href='https://aura.wit.life/' target='_blank' rel='noopener noreferrer'>{t('here')}</a>,
|
href="https://aura.wit.life/terms"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("eula")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
privacyLink: (
|
||||||
|
<a
|
||||||
|
href="https://aura.wit.life/privacy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("privacy_notice")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
clickHere: (
|
||||||
|
<a
|
||||||
|
href="https://aura.wit.life/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t("here")}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
})}
|
})}
|
||||||
</Policy>
|
</Policy>
|
||||||
<Purposes />
|
<Purposes />
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BirthdayPage
|
export default BirthdayPage;
|
||||||
|
|||||||
@ -15,11 +15,13 @@ import styles from "./styles.module.css";
|
|||||||
interface ICheckoutFormProps {
|
interface ICheckoutFormProps {
|
||||||
children?: JSX.Element | null;
|
children?: JSX.Element | null;
|
||||||
subscriptionReceiptId?: string;
|
subscriptionReceiptId?: string;
|
||||||
|
returnUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckoutForm({
|
export default function CheckoutForm({
|
||||||
children,
|
children,
|
||||||
subscriptionReceiptId,
|
subscriptionReceiptId,
|
||||||
|
returnUrl,
|
||||||
}: ICheckoutFormProps) {
|
}: ICheckoutFormProps) {
|
||||||
const stripe = useStripe();
|
const stripe = useStripe();
|
||||||
const elements = useElements();
|
const elements = useElements();
|
||||||
@ -43,7 +45,9 @@ export default function CheckoutForm({
|
|||||||
const { error } = await stripe.confirmPayment({
|
const { error } = await stripe.confirmPayment({
|
||||||
elements,
|
elements,
|
||||||
confirmParams: {
|
confirmParams: {
|
||||||
return_url: `https://${window.location.host}/payment/result/${subscriptionReceiptId}/`,
|
return_url: returnUrl
|
||||||
|
? returnUrl
|
||||||
|
: `https://${window.location.host}/payment/result/${subscriptionReceiptId}/`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -75,9 +79,7 @@ export default function CheckoutForm({
|
|||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
<img src="/lock.svg" alt="Secure" />
|
<img src="/lock.svg" alt="Secure" />
|
||||||
<span id="button-text">
|
<span id="button-text">{isProcessing ? "Processing..." : "Start"}</span>
|
||||||
{isProcessing ? "Processing..." : "Start"}
|
|
||||||
</span>
|
|
||||||
</MainButton>
|
</MainButton>
|
||||||
{!!message.length && (
|
{!!message.length && (
|
||||||
<Title variant="h5" style={{ color: "red" }}>
|
<Title variant="h5" style={{ color: "red" }}>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ function PaymentResultPage(): JSX.Element {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const status = searchParams.get("redirect_status");
|
const status = searchParams.get("redirect_status");
|
||||||
|
const type = searchParams.get("type");
|
||||||
// const { id } = useParams();
|
// const { id } = useParams();
|
||||||
// const requestTimeOutRef = useRef<NodeJS.Timeout>();
|
// const requestTimeOutRef = useRef<NodeJS.Timeout>();
|
||||||
const [isLoading] = useState(true);
|
const [isLoading] = useState(true);
|
||||||
@ -89,9 +90,17 @@ function PaymentResultPage(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === "succeeded") {
|
if (status === "succeeded") {
|
||||||
dispatch(actions.status.update("subscribed"));
|
dispatch(actions.status.update("subscribed"));
|
||||||
return navigate(routes.client.paymentSuccess());
|
let successPaymentRoute = routes.client.paymentSuccess();
|
||||||
|
if (type === "epe") {
|
||||||
|
successPaymentRoute = routes.client.epeSuccessPayment();
|
||||||
|
}
|
||||||
|
return navigate(successPaymentRoute);
|
||||||
}
|
}
|
||||||
return navigate(routes.client.paymentFail());
|
let failPaymentRoute = routes.client.paymentFail();
|
||||||
|
if (type === "epe") {
|
||||||
|
failPaymentRoute = routes.client.epeFailPayment();
|
||||||
|
}
|
||||||
|
return navigate(failPaymentRoute);
|
||||||
}, [navigate, status, dispatch]);
|
}, [navigate, status, dispatch]);
|
||||||
|
|
||||||
return <div className={styles.page}>{isLoading && <Loader />}</div>;
|
return <div className={styles.page}>{isLoading && <Loader />}</div>;
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { Elements } from "@stripe/react-stripe-js";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import CheckoutForm from "@/components/PaymentPage/methods/Stripe/CheckoutForm";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Stripe, loadStripe } from "@stripe/stripe-js";
|
||||||
|
import SecurityPayments from "../../TrialPayment/components/SecurityPayments";
|
||||||
|
|
||||||
|
interface IPaymentFormProps {
|
||||||
|
stripePublicKey: string;
|
||||||
|
clientSecret: string;
|
||||||
|
returnUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaymentForm({ stripePublicKey, clientSecret, returnUrl }: IPaymentFormProps) {
|
||||||
|
const [stripePromise, setStripePromise] =
|
||||||
|
useState<Promise<Stripe | null> | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStripePromise(loadStripe(stripePublicKey));
|
||||||
|
}, [stripePublicKey]);
|
||||||
|
return (
|
||||||
|
<div className={styles["payment-modal"]}>
|
||||||
|
<div className={styles["payment-method-container"]}>
|
||||||
|
{stripePromise && clientSecret && (
|
||||||
|
<Elements stripe={stripePromise} options={{ clientSecret }}>
|
||||||
|
<CheckoutForm returnUrl={returnUrl} />
|
||||||
|
</Elements>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<SecurityPayments />
|
||||||
|
<p className={styles.address}>500 N RAINBOW BLVD LAS VEGAS, NV 89107</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentForm;
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
.payment-modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 250px;
|
||||||
|
gap: 25px;
|
||||||
|
color: #2f2e37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-plan-description {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
color: gray;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
|
||||||
|
function FailPaymentPage(): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleNext = () => navigate(routes.client.epePayment());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<img
|
||||||
|
src="/ExclamationIcon.png"
|
||||||
|
alt="Exclamation Icon"
|
||||||
|
style={{ minHeight: "180px" }}
|
||||||
|
/>
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Title variant="h1">{t("auweb.pay_bad.title")}</Title>
|
||||||
|
<p className={styles.list}>{t("auweb.pay_bad.text1")}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottom}>
|
||||||
|
<p className={styles.description}>{t("auweb.pay_bad.text2")}</p>
|
||||||
|
<MainButton className={styles.button} onClick={handleNext}>
|
||||||
|
{t("auweb.pay_bad.button")}
|
||||||
|
</MainButton>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FailPaymentPage;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
/* max-height: -webkit-fill-available; */
|
||||||
|
overflow-y: scroll;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #fe2b57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
|
||||||
|
function SuccessPaymentPage(): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<img
|
||||||
|
src="/SuccessIcon.png"
|
||||||
|
alt="Success Icon"
|
||||||
|
style={{ minHeight: "98px" }}
|
||||||
|
/>
|
||||||
|
<div className={styles.text}>
|
||||||
|
<Title variant="h1">The information has been sent to your e-mail</Title>
|
||||||
|
<p>{t("auweb.pay_good.text1")}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SuccessPaymentPage;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
flex: auto;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
max-height: -webkit-fill-available;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text > p {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #fe2b57;
|
||||||
|
}
|
||||||
213
src/components/pages/PaymentWithEmailPage/index.tsx
Normal file
213
src/components/pages/PaymentWithEmailPage/index.tsx
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import EmailInput from "@/components/EmailEnterPage/EmailInput";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { actions, selectors } from "@/store";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
import Loader, { LoaderColor } from "@/components/Loader";
|
||||||
|
import { useAuth } from "@/auth";
|
||||||
|
import { ApiError, extractErrorMessage, useApi } from "@/api";
|
||||||
|
import { getClientTimezone } from "@/locales";
|
||||||
|
import ErrorText from "@/components/ErrorText";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import NameInput from "@/components/EmailEnterPage/NameInput";
|
||||||
|
import { getZodiacSignByDate } from "@/services/zodiac-sign";
|
||||||
|
import {
|
||||||
|
ResponsePost,
|
||||||
|
ResponsePostExistPaymentData,
|
||||||
|
} from "@/api/resources/SinglePayment";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import PaymentForm from "./PaymentForm";
|
||||||
|
|
||||||
|
function PaymentWithEmailPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const { signUp } = useAuth();
|
||||||
|
const api = useApi();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const timezone = getClientTimezone();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const birthday = useSelector(selectors.selectBirthday);
|
||||||
|
const locale = i18n.language;
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [isValidEmail, setIsValidEmail] = useState(false);
|
||||||
|
const [isValidName, setIsValidName] = useState(true);
|
||||||
|
const [isDisabled, setIsDisabled] = useState(true);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isAuth, setIsAuth] = useState(false);
|
||||||
|
const [apiError, setApiError] = useState<ApiError | null>(null);
|
||||||
|
const [error, setError] = useState<boolean>(false);
|
||||||
|
const [paymentIntent, setPaymentIntent] = useState<
|
||||||
|
ResponsePost | ResponsePostExistPaymentData | null
|
||||||
|
>(null);
|
||||||
|
const returnUrl = `${window.location.protocol}//${window.location.host}/payment/result/?type=epe`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isValidName && isValidEmail) {
|
||||||
|
setIsDisabled(false);
|
||||||
|
} else {
|
||||||
|
setIsDisabled(true);
|
||||||
|
}
|
||||||
|
}, [isValidEmail, email, isValidName, name]);
|
||||||
|
|
||||||
|
const handleValidEmail = (email: string) => {
|
||||||
|
dispatch(actions.form.addEmail(email));
|
||||||
|
setEmail(email);
|
||||||
|
setIsValidEmail(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleValidName = (name: string) => {
|
||||||
|
setName(name);
|
||||||
|
setIsValidName(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorization = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const auth = await api.auth({ email, timezone, locale });
|
||||||
|
const {
|
||||||
|
auth: { token, user },
|
||||||
|
} = auth;
|
||||||
|
signUp(token, user);
|
||||||
|
const payload = {
|
||||||
|
user: {
|
||||||
|
profile_attributes: {
|
||||||
|
birthday,
|
||||||
|
full_name: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
};
|
||||||
|
const updatedUser = await api.updateUser(payload).catch((error) => {
|
||||||
|
console.log("Error: ", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updatedUser?.user) {
|
||||||
|
dispatch(actions.user.update(updatedUser.user));
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
dispatch(
|
||||||
|
actions.user.update({
|
||||||
|
username: name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(actions.status.update("registred"));
|
||||||
|
setIsAuth(true);
|
||||||
|
const userUpdated = await api.getUser({ token });
|
||||||
|
return { user: userUpdated?.user, token };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
setApiError(error as ApiError);
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
const authData = await authorization();
|
||||||
|
if (!authData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { user, token } = authData;
|
||||||
|
console.log(token);
|
||||||
|
|
||||||
|
const productsSinglePayment = await api.getSinglePaymentProducts({
|
||||||
|
id: "1",
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
const { productId } = productsSinglePayment[0];
|
||||||
|
const createSinglePayment = await api.createSinglePayment({
|
||||||
|
token,
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
id: `${user?.id}`,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
sign: user?.profile?.sign?.sign || getZodiacSignByDate(birthday),
|
||||||
|
age: user?.profile?.age?.years || 1,
|
||||||
|
},
|
||||||
|
partner: {
|
||||||
|
sign: "partner_cancer",
|
||||||
|
age: 26,
|
||||||
|
},
|
||||||
|
paymentInfo: {
|
||||||
|
productId,
|
||||||
|
},
|
||||||
|
return_url: returnUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setPaymentIntent(createSinglePayment);
|
||||||
|
setIsLoading(false);
|
||||||
|
if ("payment" in createSinglePayment) {
|
||||||
|
if (createSinglePayment.payment.status === "paid")
|
||||||
|
return navigate(routes.client.epeSuccessPayment());
|
||||||
|
return navigate(routes.client.epeFailPayment());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.page} page`}>
|
||||||
|
{paymentIntent && "paymentIntent" in paymentIntent && (
|
||||||
|
<PaymentForm
|
||||||
|
stripePublicKey={paymentIntent.paymentIntent.data.public_key}
|
||||||
|
clientSecret={paymentIntent.paymentIntent.data.client_secret}
|
||||||
|
returnUrl={returnUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!paymentIntent && (
|
||||||
|
<>
|
||||||
|
<NameInput
|
||||||
|
value={name}
|
||||||
|
placeholder="Your name"
|
||||||
|
onValid={handleValidName}
|
||||||
|
onInvalid={() => setIsValidName(true)}
|
||||||
|
/>
|
||||||
|
<EmailInput
|
||||||
|
name="email"
|
||||||
|
value={email}
|
||||||
|
placeholder={t("your_email")}
|
||||||
|
onValid={handleValidEmail}
|
||||||
|
onInvalid={() => setIsValidEmail(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MainButton
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
{isLoading && <Loader color={LoaderColor.White} />}
|
||||||
|
{!isLoading &&
|
||||||
|
!(!apiError && !error && !isLoading && isAuth) &&
|
||||||
|
t("_continue")}
|
||||||
|
{!apiError && !error && !isLoading && isAuth && (
|
||||||
|
<img
|
||||||
|
className={styles["success-icon"]}
|
||||||
|
src="/SuccessIcon.png"
|
||||||
|
alt="Success Icon"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MainButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(error || apiError) && (
|
||||||
|
<Title variant="h3" style={{ color: "red", margin: 0 }}>
|
||||||
|
Something went wrong
|
||||||
|
</Title>
|
||||||
|
)}
|
||||||
|
{apiError && (
|
||||||
|
<ErrorText
|
||||||
|
size="medium"
|
||||||
|
isShown={Boolean(apiError)}
|
||||||
|
message={apiError ? extractErrorMessage(apiError) : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentWithEmailPage;
|
||||||
61
src/components/pages/PaymentWithEmailPage/styles.module.css
Normal file
61
src/components/pages/PaymentWithEmailPage/styles.module.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
.page {
|
||||||
|
/* position: relative; */
|
||||||
|
position: static;
|
||||||
|
height: fit-content;
|
||||||
|
min-height: calc(100dvh - 103px);
|
||||||
|
/* max-height: -webkit-fill-available; */
|
||||||
|
display: flex;
|
||||||
|
justify-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
/* gap: 16px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-top: 0;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.25) 0px 4px 4px 0px;
|
||||||
|
height: 50px;
|
||||||
|
min-height: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
165.54deg,
|
||||||
|
rgb(20, 19, 51) -33.39%,
|
||||||
|
rgb(32, 34, 97) 15.89%,
|
||||||
|
rgb(84, 60, 151) 55.84%,
|
||||||
|
rgb(105, 57, 162) 74.96%
|
||||||
|
);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-loader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cross {
|
||||||
|
position: absolute;
|
||||||
|
top: -36px;
|
||||||
|
right: 28px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 27px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@ -22,6 +22,9 @@ import PayPalButton from "./components/PayPalButton";
|
|||||||
|
|
||||||
interface IPaymentModalProps {
|
interface IPaymentModalProps {
|
||||||
activeSubscriptionPlan?: ISubscriptionPlan;
|
activeSubscriptionPlan?: ISubscriptionPlan;
|
||||||
|
stripePublicKey?: string;
|
||||||
|
singlePayClientSecret?: string;
|
||||||
|
defaultPaymentMethod?: EPaymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaymentModal({ activeSubscriptionPlan }: IPaymentModalProps) {
|
function PaymentModal({ activeSubscriptionPlan }: IPaymentModalProps) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const host = "";
|
|||||||
export const apiHost = "https://api-web.aura.wit.life";
|
export const apiHost = "https://api-web.aura.wit.life";
|
||||||
const siteHost = "https://aura.wit.life";
|
const siteHost = "https://aura.wit.life";
|
||||||
const prefix = "api/v1";
|
const prefix = "api/v1";
|
||||||
|
const dApiHost = "https://dev.api.witapps.us";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
client: {
|
client: {
|
||||||
@ -14,17 +15,22 @@ const routes = {
|
|||||||
palmistryBirthdate: () => [host, "palmistry", "birthdate"].join("/"),
|
palmistryBirthdate: () => [host, "palmistry", "birthdate"].join("/"),
|
||||||
palmistryPalmsHold: () => [host, "palmistry", "palms-hold"].join("/"),
|
palmistryPalmsHold: () => [host, "palmistry", "palms-hold"].join("/"),
|
||||||
palmistryWish: () => [host, "palmistry", "wish"].join("/"),
|
palmistryWish: () => [host, "palmistry", "wish"].join("/"),
|
||||||
palmistryRelationship: () => [host, "palmistry", "relationship-status"].join("/"),
|
palmistryRelationship: () =>
|
||||||
palmistryResonatedElement: () => [host, "palmistry", "resonated-element"].join("/"),
|
[host, "palmistry", "relationship-status"].join("/"),
|
||||||
palmistryColorYouLike: () => [host, "palmistry", "color-you-like"].join("/"),
|
palmistryResonatedElement: () =>
|
||||||
|
[host, "palmistry", "resonated-element"].join("/"),
|
||||||
|
palmistryColorYouLike: () =>
|
||||||
|
[host, "palmistry", "color-you-like"].join("/"),
|
||||||
palmistryDecisions: () => [host, "palmistry", "decisions"].join("/"),
|
palmistryDecisions: () => [host, "palmistry", "decisions"].join("/"),
|
||||||
palmistryGuidancePlan: () => [host, "palmistry", "guidance-plan"].join("/"),
|
palmistryGuidancePlan: () => [host, "palmistry", "guidance-plan"].join("/"),
|
||||||
palmistryPersonalStatement: () => [host, "palmistry", "personal-statement"].join("/"),
|
palmistryPersonalStatement: () =>
|
||||||
|
[host, "palmistry", "personal-statement"].join("/"),
|
||||||
palmistryScanInfo: () => [host, "palmistry", "scan-info"].join("/"),
|
palmistryScanInfo: () => [host, "palmistry", "scan-info"].join("/"),
|
||||||
palmistryUpload: () => [host, "palmistry", "upload"].join("/"),
|
palmistryUpload: () => [host, "palmistry", "upload"].join("/"),
|
||||||
palmistryScanPhoto: () => [host, "palmistry", "scan-photo"].join("/"),
|
palmistryScanPhoto: () => [host, "palmistry", "scan-photo"].join("/"),
|
||||||
palmistryEmail: () => [host, "palmistry", "email"].join("/"),
|
palmistryEmail: () => [host, "palmistry", "email"].join("/"),
|
||||||
palmistrySubscriptionPlan: () => [host, "palmistry", "subscription-plan"].join("/"),
|
palmistrySubscriptionPlan: () =>
|
||||||
|
[host, "palmistry", "subscription-plan"].join("/"),
|
||||||
palmistryPaywall: () => [host, "palmistry", "paywall"].join("/"),
|
palmistryPaywall: () => [host, "palmistry", "paywall"].join("/"),
|
||||||
palmistryPayment: () => [host, "palmistry", "payment"].join("/"),
|
palmistryPayment: () => [host, "palmistry", "payment"].join("/"),
|
||||||
palmistryDiscount: () => [host, "palmistry", "discount"].join("/"),
|
palmistryDiscount: () => [host, "palmistry", "discount"].join("/"),
|
||||||
@ -108,6 +114,12 @@ const routes = {
|
|||||||
unlimitedReadings: () => [host, "unlimited-readings"].join("/"),
|
unlimitedReadings: () => [host, "unlimited-readings"].join("/"),
|
||||||
addConsultation: () => [host, "add-consultation"].join("/"),
|
addConsultation: () => [host, "add-consultation"].join("/"),
|
||||||
|
|
||||||
|
// Email - Pay - Email
|
||||||
|
epeBirthdate: () => [host, "epe", "birthdate"].join("/"),
|
||||||
|
epePayment: () => [host, "epe", "payment"].join("/"),
|
||||||
|
epeSuccessPayment: () => [host, "epe", "success-payment"].join("/"),
|
||||||
|
epeFailPayment: () => [host, "epe", "fail-payment"].join("/"),
|
||||||
|
|
||||||
notFound: () => [host, "404"].join("/"),
|
notFound: () => [host, "404"].join("/"),
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@ -164,6 +176,10 @@ const routes = {
|
|||||||
),
|
),
|
||||||
getAiRequestsV2: (id: string) =>
|
getAiRequestsV2: (id: string) =>
|
||||||
[apiHost, "api/v2", "ai", "requests", `${id}.json`].join("/"),
|
[apiHost, "api/v2", "ai", "requests", `${id}.json`].join("/"),
|
||||||
|
|
||||||
|
dApiTestPaymentProducts: () =>
|
||||||
|
[dApiHost, "payment", "test", "products"].join("/"),
|
||||||
|
dApiPaymentCheckout: () => [dApiHost, "payment", "checkout"].join("/"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -266,6 +282,7 @@ export const withoutFooterRoutes = [
|
|||||||
routes.client.addReport(),
|
routes.client.addReport(),
|
||||||
routes.client.unlimitedReadings(),
|
routes.client.unlimitedReadings(),
|
||||||
routes.client.addConsultation(),
|
routes.client.addConsultation(),
|
||||||
|
routes.client.epeSuccessPayment(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const withoutFooterPartOfRoutes = [routes.client.questionnaire()];
|
export const withoutFooterPartOfRoutes = [routes.client.questionnaire()];
|
||||||
@ -334,14 +351,15 @@ export const withoutHeaderRoutes = [
|
|||||||
routes.client.email("marketing-landing"),
|
routes.client.email("marketing-landing"),
|
||||||
routes.client.email("marketing-trial-payment"),
|
routes.client.email("marketing-trial-payment"),
|
||||||
routes.client.tryApp(),
|
routes.client.tryApp(),
|
||||||
|
routes.client.epeSuccessPayment(),
|
||||||
];
|
];
|
||||||
export const hasNoHeader = (path: string) => {
|
export const hasNoHeader = (path: string) => {
|
||||||
let result = true;
|
let result = true;
|
||||||
|
|
||||||
withoutHeaderRoutes.forEach((route) => {
|
withoutHeaderRoutes.forEach((route) => {
|
||||||
if (
|
if (
|
||||||
!path.includes("palmistry") && path.includes(route) ||
|
(!path.includes("palmistry") && path.includes(route)) ||
|
||||||
path.includes("palmistry") && path === route
|
(path.includes("palmistry") && path === route)
|
||||||
) {
|
) {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user