Merge branch 'clone' into 'main'

feat: questionnaire

See merge request witapp/aura-webapp!25
This commit is contained in:
Victor Ershov 2024-01-23 00:15:05 +00:00
commit 212b7cbccb
8 changed files with 149 additions and 55 deletions

View File

@ -66,6 +66,7 @@ export interface SubscriptionReceipt {
autorenewable: boolean; autorenewable: boolean;
error: string; error: string;
links?: IPayPalLink[]; links?: IPayPalLink[];
stripe_status?: string;
}; };
} }

View File

@ -232,7 +232,9 @@ function App(): JSX.Element {
<Route <Route
path={routes.client.paymentResult()} path={routes.client.paymentResult()}
element={<PaymentResultPage />} element={<PaymentResultPage />}
/> >
<Route path=":id" element={<PaymentResultPage />} />
</Route>
<Route <Route
path={routes.client.paymentSuccess()} path={routes.client.paymentSuccess()}
element={<PaymentSuccessPage />} element={<PaymentSuccessPage />}

View File

@ -13,9 +13,13 @@ import { useNavigate } from "react-router-dom";
interface ICheckoutFormProps { interface ICheckoutFormProps {
children?: JSX.Element | null; children?: JSX.Element | null;
subscriptionReceiptId?: string;
} }
export default function CheckoutForm({ children }: ICheckoutFormProps) { export default function CheckoutForm({
children,
subscriptionReceiptId,
}: ICheckoutFormProps) {
const stripe = useStripe(); const stripe = useStripe();
const elements = useElements(); const elements = useElements();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -28,7 +32,6 @@ export default function CheckoutForm({ children }: ICheckoutFormProps) {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if (!stripe || !elements) { if (!stripe || !elements) {
return; return;
} }
@ -39,7 +42,7 @@ export default function CheckoutForm({ children }: ICheckoutFormProps) {
const { error } = await stripe.confirmPayment({ const { error } = await stripe.confirmPayment({
elements, elements,
confirmParams: { confirmParams: {
return_url: `https://${window.location.host}/payment/result`, return_url: `https://${window.location.host}/payment/result/${subscriptionReceiptId}`,
}, },
}); });
if (error) { if (error) {
@ -48,14 +51,12 @@ export default function CheckoutForm({ children }: ICheckoutFormProps) {
dispatch(actions.status.update("subscribed")); dispatch(actions.status.update("subscribed"));
navigate(routes.client.paymentSuccess()); navigate(routes.client.paymentSuccess());
} }
} catch(error) { } catch (error) {
console.log('error -> ', error); console.log("error -> ", error);
setMessage("Oops! Something went wrong."); setMessage("Oops! Something went wrong.");
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };
return ( return (
@ -66,12 +67,18 @@ export default function CheckoutForm({ children }: ICheckoutFormProps) {
> >
{children ? children : null} {children ? children : null}
<PaymentElement onReady={() => setFormReady(true)} /> <PaymentElement onReady={() => setFormReady(true)} />
<MainButton color="blue" disabled={isProcessing || !formReady} id="submit"> <MainButton
color="blue"
disabled={isProcessing || !formReady}
id="submit"
>
<span id="button-text"> <span id="button-text">
{isProcessing ? "Processing..." : "Pay now"} {isProcessing ? "Processing..." : "Pay now"}
</span> </span>
</MainButton> </MainButton>
<Title variant="h5" style={{color: 'red'}}>{message}</Title> <Title variant="h5" style={{ color: "red" }}>
{message}
</Title>
</form> </form>
); );
} }

View File

@ -1,15 +1,91 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import routes from "@/routes"; import routes from "@/routes";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { actions } from "@/store"; import { actions } from "@/store";
// import { SubscriptionReceipts, useApi, useApiCall } from "@/api";
// import { useAuth } from "@/auth";
import styles from "./styles.module.css";
import Loader from "@/components/Loader";
function PaymentResultPage(): JSX.Element { function PaymentResultPage(): JSX.Element {
// const api = useApi();
// const { token } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const status = searchParams.get("redirect_status"); const status = searchParams.get("redirect_status");
status;
// const { id } = useParams();
// const requestTimeOutRef = useRef<NodeJS.Timeout>();
const [isLoading] = useState(true);
// const [subscriptionReceipt, setSubscriptionReceipt] =
// useState<SubscriptionReceipts.SubscriptionReceipt>();
// const loadData = useCallback(async () => {
// if (!id) {
// return null;
// }
// const getSubscriptionReceiptStatus = async () => {
// const { subscription_receipt } = await api.getSubscriptionReceipt({
// token,
// id,
// });
// const { stripe_status } = subscription_receipt.data;
// if (stripe_status === "incomplete") {
// requestTimeOutRef.current = setTimeout(
// getSubscriptionReceiptStatus,
// 3000
// );
// }
// setSubscriptionReceipt(subscription_receipt);
// return { subscription_receipt };
// };
// return getSubscriptionReceiptStatus();
// }, [api, id, token]);
// useApiCall<SubscriptionReceipts.Response | null>(loadData);
// useEffect(() => {
// if (!subscriptionReceipt) {
// if (id?.length) return;
// return () => {
// if (requestTimeOutRef.current) {
// clearTimeout(requestTimeOutRef.current);
// }
// navigate(routes.client.paymentFail());
// };
// }
// const { stripe_status } = subscriptionReceipt.data;
// if (stripe_status === "succeeded") {
// dispatch(actions.status.update("subscribed"));
// setIsLoading(false);
// return () => {
// if (requestTimeOutRef.current) {
// clearTimeout(requestTimeOutRef.current);
// }
// navigate(routes.client.paymentSuccess());
// };
// } else if (stripe_status === "payment_failed") {
// setIsLoading(false);
// return () => {
// if (requestTimeOutRef.current) {
// clearTimeout(requestTimeOutRef.current);
// }
// navigate(routes.client.paymentFail());
// };
// }
// }, [dispatch, id, navigate, subscriptionReceipt]);
// useEffect(() => {
// return () => {
// if (requestTimeOutRef.current) {
// clearTimeout(requestTimeOutRef.current);
// }
// };
// }, []);
useEffect(() => { useEffect(() => {
if (status === "succeeded") { if (status === "succeeded") {
@ -19,7 +95,7 @@ function PaymentResultPage(): JSX.Element {
return navigate(routes.client.paymentFail()); return navigate(routes.client.paymentFail());
}, [navigate, status, dispatch]); }, [navigate, status, dispatch]);
return <></>; return <div className={styles.page}>{isLoading && <Loader />}</div>;
} }
export default PaymentResultPage; export default PaymentResultPage;

View File

@ -0,0 +1,15 @@
.page {
position: relative;
height: fit-content;
min-height: calc(100vh - 103px);
flex: auto;
/* max-height: -webkit-fill-available; */
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}

View File

@ -7,15 +7,25 @@ import {
import { PaymentRequest } from "@stripe/stripe-js"; import { PaymentRequest } from "@stripe/stripe-js";
import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans"; import { ISubscriptionPlan } from "@/api/resources/SubscriptionPlans";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
interface ApplePayButtonProps { interface ApplePayButtonProps {
activeSubPlan: ISubscriptionPlan | null; activeSubPlan: ISubscriptionPlan | null;
client_secret: string; client_secret: string;
subscriptionReceiptId?: string;
} }
function ApplePayButton({ activeSubPlan, client_secret }: ApplePayButtonProps) { function ApplePayButton({
activeSubPlan,
client_secret,
subscriptionReceiptId,
}: ApplePayButtonProps) {
const stripe = useStripe(); const stripe = useStripe();
const elements = useElements(); const elements = useElements();
const dispatch = useDispatch();
const navigate = useNavigate();
const [paymentRequest, setPaymentRequest] = useState<PaymentRequest | null>( const [paymentRequest, setPaymentRequest] = useState<PaymentRequest | null>(
null null
); );
@ -63,13 +73,22 @@ function ApplePayButton({ activeSubPlan, client_secret }: ApplePayButtonProps) {
return; return;
} }
navigate(`${routes.client.paymentResult()}/${subscriptionReceiptId}`);
// Show a success message to your customer // Show a success message to your customer
// There's a risk of the customer closing the window before callback // There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the // execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical // payment_intent.succeeded event that handles any business critical
// post-payment actions. // post-payment actions.
}); });
}, [activeSubPlan, client_secret, elements, stripe]); }, [
activeSubPlan,
client_secret,
dispatch,
elements,
navigate,
stripe,
subscriptionReceiptId,
]);
return ( return (
<> <>

View File

@ -28,21 +28,12 @@ export function StripePage(): JSX.Element {
useState<Promise<Stripe | null> | null>(null); useState<Promise<Stripe | null> | null>(null);
const [subPlans, setSubPlans] = useState<ISubscriptionPlan[] | null>(null); const [subPlans, setSubPlans] = useState<ISubscriptionPlan[] | null>(null);
const [clientSecret, setClientSecret] = useState<string>(""); const [clientSecret, setClientSecret] = useState<string>("");
const [subscriptionReceiptId, setSubscriptionReceiptId] =
useState<string>("");
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
if (!activeSubPlan) { if (!activeSubPlan) {
navigate(routes.client.priceList()); navigate(routes.client.priceList());
} }
// const timeoutRef = useRef<NodeJS.Timeout>();
// const appleReceipt = async () => {
// const { subscription_receipt } = await api.createSubscriptionReceipt({
// token,
// receiptData: "",
// autorenewable: true,
// sandbox: true,
// });
// console.log(subscription_receipt);
// };
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -59,26 +50,6 @@ export function StripePage(): JSX.Element {
})(); })();
}, [activeSubPlan, api, locale, navigate]); }, [activeSubPlan, api, locale, navigate]);
// useEffect(() => {
// timeoutRef.current = setTimeout(async () => {
// const { subscription_receipt } = await api.createSubscriptionReceipt({
// token,
// way: "stripe",
// subscription_receipt: {
// sub_plan_id: activeSubPlan?.id || "stripe.7",
// },
// });
// const { client_secret } = subscription_receipt.data;
// setClientSecret(client_secret);
// setIsLoading(false);
// }, 4000);
// return () => {
// if (timeoutRef.current) {
// clearTimeout(timeoutRef.current);
// }
// };
// }, [api, token]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const { subscription_receipt } = await api.createSubscriptionReceipt({ const { subscription_receipt } = await api.createSubscriptionReceipt({
@ -88,7 +59,9 @@ export function StripePage(): JSX.Element {
sub_plan_id: activeSubPlan?.id || "stripe.7", sub_plan_id: activeSubPlan?.id || "stripe.7",
}, },
}); });
const { id } = subscription_receipt;
const { client_secret } = subscription_receipt.data; const { client_secret } = subscription_receipt.data;
setSubscriptionReceiptId(id);
setClientSecret(client_secret); setClientSecret(client_secret);
setIsLoading(false); setIsLoading(false);
})(); })();
@ -109,16 +82,17 @@ export function StripePage(): JSX.Element {
<p className={styles.email}>{email}</p> <p className={styles.email}>{email}</p>
</> </>
)} )}
{stripePromise && clientSecret && ( {stripePromise && clientSecret && subscriptionReceiptId && (
<Elements stripe={stripePromise} options={{ clientSecret }}> <Elements stripe={stripePromise} options={{ clientSecret }}>
<ApplePayButton <ApplePayButton
activeSubPlan={activeSubPlan} activeSubPlan={activeSubPlan}
client_secret={clientSecret} client_secret={clientSecret}
subscriptionReceiptId={subscriptionReceiptId}
/> />
{activeSubPlan && ( {activeSubPlan && (
<SubPlanInformation subPlan={activeSubPlan} subPlans={subPlans} /> <SubPlanInformation subPlan={activeSubPlan} subPlans={subPlans} />
)} )}
<CheckoutForm></CheckoutForm> <CheckoutForm subscriptionReceiptId={subscriptionReceiptId} />
</Elements> </Elements>
)} )}
</div> </div>

View File

@ -1,14 +1,14 @@
import Title from "@/components/Title"; // import Title from "@/components/Title";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import MainButton from "@/components/MainButton"; import MainButton from "@/components/MainButton";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import routes from "@/routes"; import routes from "@/routes";
import { useSelector } from "react-redux"; // import { useSelector } from "react-redux";
import { selectors } from "@/store"; // import { selectors } from "@/store";
function WorksForUsPage() { function WorksForUsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { relationshipProblem } = useSelector(selectors.selectQuestionnaire); // const { relationshipProblem } = useSelector(selectors.selectQuestionnaire);
const handleBack = () => { const handleBack = () => {
navigate(-1); navigate(-1);
@ -22,7 +22,7 @@ function WorksForUsPage() {
<section className={`${styles.page} page`}> <section className={`${styles.page} page`}>
<img src="/starry_key.svg" alt="The starry key" /> <img src="/starry_key.svg" alt="The starry key" />
<div> <div>
{relationshipProblem === "very_unhappy" && ( {/* {relationshipProblem === "very_unhappy" && (
<Title variant="h1" className={styles.title}> <Title variant="h1" className={styles.title}>
Weve got you covered! Well start with small, personalized insights Weve got you covered! Well start with small, personalized insights
into you and your partners personality traits. into you and your partners personality traits.
@ -40,7 +40,7 @@ function WorksForUsPage() {
<br /> Let's find out what's working (and what isnt) and go from <br /> Let's find out what's working (and what isnt) and go from
there. there.
</Title> </Title>
)} )} */}
<p className={styles.text}> <p className={styles.text}>
Now, we need some information about{" "} Now, we need some information about{" "}
<span className={styles.blue}>Your Partners Profile</span> to create <span className={styles.blue}>Your Partners Profile</span> to create