feat: changed the order of the pages add auth form to subscription page
This commit is contained in:
parent
ea93569c03
commit
490bf6f8da
@ -186,12 +186,18 @@ function App(): JSX.Element {
|
||||
element={<ProtectWallpaperPage />}
|
||||
/> */}
|
||||
</Route>
|
||||
<Route element={<PrivateOutlet />}>
|
||||
<Route element={<AuthorizedUserOutlet />}>
|
||||
<Route
|
||||
path={routes.client.subscription()}
|
||||
element={<SubscriptionPage />}
|
||||
/>
|
||||
</Route>
|
||||
<Route element={<PrivateOutlet />}>
|
||||
<Route element={<AuthorizedUserOutlet />}>
|
||||
{/* <Route
|
||||
path={routes.client.subscription()}
|
||||
element={<SubscriptionPage />}
|
||||
/> */}
|
||||
<Route
|
||||
path={routes.client.paymentMethod()}
|
||||
element={<PaymentPage />}
|
||||
|
||||
@ -17,7 +17,7 @@ function AttentionPage({
|
||||
}: AttentionPageProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const handleNext = () => navigate(routes.client.feedback());
|
||||
const handleNext = () => navigate(routes.client.priceList());
|
||||
|
||||
return (
|
||||
<section className={`${styles.page} page`}>
|
||||
|
||||
@ -1,31 +1,40 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './styles.css'
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
type CountdownProps = {
|
||||
start: number
|
||||
}
|
||||
start: number;
|
||||
className?: string;
|
||||
timeClassName?: string;
|
||||
};
|
||||
|
||||
function Countdown({ start }: CountdownProps): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
const [time, setTime] = useState(start * 60 - 1)
|
||||
function Countdown({
|
||||
start,
|
||||
className,
|
||||
timeClassName,
|
||||
}: CountdownProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const [time, setTime] = useState(start * 60 - 1);
|
||||
const formatTime = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = seconds % 60
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (time === 0) return
|
||||
const timer = setTimeout(() => setTime(time - 1), 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [time])
|
||||
if (time === 0) return;
|
||||
const timer = setTimeout(() => setTime(time - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [time]);
|
||||
|
||||
return (
|
||||
<div className="countdown mb-24">
|
||||
<p>{t('reserved_for')}{formatTime(time)}</p>
|
||||
<div className={`${styles.countdown} mb-24 ${className || ""}`}>
|
||||
<p>
|
||||
{t("reserved_for")}
|
||||
<span className={`${timeClassName || ""}`}>{formatTime(time)}</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Countdown
|
||||
export default Countdown;
|
||||
|
||||
@ -11,7 +11,7 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
|
||||
function DidYouKnowPage(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const handleNext = () => navigate(routes.client.freePeriodInfo());
|
||||
const handleNext = () => navigate(routes.client.feedback());
|
||||
const birthdate = useSelector(selectors.selectBirthdate);
|
||||
const zodiacSign = getZodiacSignByDate(birthdate);
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ function FeedbackPage(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const api = useApi();
|
||||
const handleNext = () => navigate(routes.client.auth());
|
||||
const handleNext = () => navigate(routes.client.freePeriodInfo());
|
||||
|
||||
const assetsData = useCallback(async () => {
|
||||
const { assets } = await api.getAssets({ category: String("au") });
|
||||
|
||||
@ -1,53 +1,63 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Price, { Currency, Locale } from './Price'
|
||||
import './styles.css'
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Price, { Currency, Locale } from "./Price";
|
||||
import styles from "./styles.module.css";
|
||||
import Countdown from "../Countdown";
|
||||
|
||||
type PaymentItem = {
|
||||
title: string
|
||||
price: number
|
||||
description: string
|
||||
}
|
||||
title: string;
|
||||
price: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type PaymentTableProps = {
|
||||
currency: Currency
|
||||
locale: Locale
|
||||
items: PaymentItem[]
|
||||
}
|
||||
currency: Currency;
|
||||
locale: Locale;
|
||||
items: PaymentItem[];
|
||||
};
|
||||
|
||||
function PaymentTable({ currency, locale, items }: PaymentTableProps): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
const total = items.reduce((acc, item) => acc + item.price, 0)
|
||||
const totalPrice = new Price(total, currency, locale)
|
||||
const toItem = (item: typeof items[0], idx: number) => {
|
||||
const price = new Price(item.price, currency, locale)
|
||||
function PaymentTable({
|
||||
currency,
|
||||
locale,
|
||||
items,
|
||||
}: PaymentTableProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const total = items.reduce((acc, item) => acc + item.price, 0);
|
||||
const totalPrice = new Price(total, currency, locale);
|
||||
const toItem = (item: (typeof items)[0], idx: number) => {
|
||||
const price = new Price(item.price, currency, locale);
|
||||
return (
|
||||
<div key={idx} className='payment__item'>
|
||||
<div className='payment__item-summary'>
|
||||
<div className='payment__item-title'>{item.title}</div>
|
||||
<div className='payment__item-price'>{price.format()}</div>
|
||||
<div key={idx} className={styles["payment__item"]}>
|
||||
<div className={styles["payment__item-summary"]}>
|
||||
<div className={styles["payment__item-title"]}>{item.title}</div>
|
||||
<div className={styles["payment__item-price"]}>{price.format()}</div>
|
||||
</div>
|
||||
<div className='payment__item-description'>
|
||||
<div className={styles["payment__item-description"]}>
|
||||
<p>{item.description}</p>
|
||||
{/* <p>One dollar thirty six cents per day</p> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className='payment'>
|
||||
<div className='payment__table'>
|
||||
<div className='payment__total'>
|
||||
<div className='payment__total-title'>{t('total_today')}</div>
|
||||
<div className='payment__total-price'>{totalPrice.format()}</div>
|
||||
<div className={styles.payment}>
|
||||
<Countdown className={styles["payment__countdown"]} timeClassName={styles["payment__countdown-time"]} start={10} />
|
||||
<div className={styles["payment__table"]}>
|
||||
<div className={styles["payment__total"]}>
|
||||
<div className={styles["payment__total-title"]}>
|
||||
{t("total_today")}
|
||||
</div>
|
||||
<div className='payment__items'>
|
||||
{items.map(toItem)}
|
||||
<div className={styles["payment__total-price"]}>
|
||||
{totalPrice.format()}
|
||||
</div>
|
||||
</div>
|
||||
<div className='payment__information'>{t('charged_only', {price: totalPrice.format()})}</div>
|
||||
<div className={styles["payment__items"]}>{items.map(toItem)}</div>
|
||||
</div>
|
||||
)
|
||||
<div className={styles["payment__information"]}>
|
||||
{t("charged_only", { price: totalPrice.format() })}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaymentTable
|
||||
export { Price, Currency, Locale }
|
||||
export default PaymentTable;
|
||||
export { Price, Currency, Locale };
|
||||
|
||||
@ -1,6 +1,22 @@
|
||||
.payment {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.payment__countdown {
|
||||
position: absolute;
|
||||
border-radius: 14px !important;
|
||||
padding: 10px 16px !important;
|
||||
width: fit-content;
|
||||
right: 12px;
|
||||
top: -18px;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.payment__countdown-time {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment__table {
|
||||
@ -11,6 +27,7 @@
|
||||
padding: 0 25px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.payment__total,
|
||||
@ -1,7 +1,7 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { selectors } from "@/store";
|
||||
import { actions, selectors } from "@/store";
|
||||
import MainButton from "../MainButton";
|
||||
import Policy from "../Policy";
|
||||
import Countdown from "../Countdown";
|
||||
@ -14,9 +14,15 @@ import Header from "../Header";
|
||||
import SpecialWelcomeOffer from "../SpecialWelcomeOffer";
|
||||
import { useState } from "react";
|
||||
import { ITrial } from "@/api/resources/SubscriptionPlans";
|
||||
import { ApiError, extractErrorMessage, useApi } from "@/api";
|
||||
import { useAuth } from "@/auth";
|
||||
import { getClientLocale, getClientTimezone } from "@/locales";
|
||||
import Loader from "../Loader";
|
||||
import Title from "../Title";
|
||||
import ErrorText from "../ErrorText";
|
||||
|
||||
const currency = Currency.USD;
|
||||
const locale = Locale.EN;
|
||||
const locale = getClientLocale() as Locale;
|
||||
|
||||
const getPriceFromTrial = (trial: ITrial | null) => {
|
||||
if (!trial) {
|
||||
@ -26,9 +32,26 @@ const getPriceFromTrial = (trial: ITrial | null) => {
|
||||
};
|
||||
|
||||
function SubscriptionPage(): JSX.Element {
|
||||
const [isOpenModal, setIsOpenModal] = useState(false);
|
||||
const api = useApi();
|
||||
const timezone = getClientTimezone();
|
||||
const { signUp } = useAuth();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [isOpenModal, setIsOpenModal] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [emailError, setEmailError] = useState<null | string>(
|
||||
"Email is invalid"
|
||||
);
|
||||
const [name, setName] = useState("");
|
||||
const [nameError, setNameError] = useState<null | string>("Name is invalid");
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [isAuth, setIsAuth] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [apiError, setApiError] = useState<ApiError | null>(null);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const activeSubPlan = useSelector(selectors.selectActiveSubPlan);
|
||||
const birthday = useSelector(selectors.selectBirthday);
|
||||
const paymentItems = [
|
||||
{
|
||||
title: activeSubPlan?.name || "Per 7-Day Trial For",
|
||||
@ -39,9 +62,52 @@ function SubscriptionPage(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const navigate = useNavigate();
|
||||
const email = useSelector(selectors.selectEmail);
|
||||
const handleClick = () => navigate(routes.client.paymentMethod());
|
||||
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 } },
|
||||
token,
|
||||
};
|
||||
const updatedUser = await api.updateUser(payload).catch((error) => {
|
||||
console.log("Error: ", error);
|
||||
});
|
||||
if (updatedUser?.user) {
|
||||
dispatch(actions.user.update(updatedUser.user));
|
||||
}
|
||||
dispatch(actions.status.update("registred"));
|
||||
setIsLoading(false);
|
||||
setIsAuth(true);
|
||||
setTimeout(() => {
|
||||
navigate(routes.client.paymentMethod());
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof ApiError) {
|
||||
setApiError(error as ApiError);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// const email = useSelector(selectors.selectEmail);
|
||||
const handleClick = async () => {
|
||||
setIsSubmit(true);
|
||||
|
||||
if (!isValidEmail(email) || !isValidName(name)) {
|
||||
return;
|
||||
}
|
||||
await authorization();
|
||||
console.log("sdvdvsdv");
|
||||
// navigate(routes.client.paymentMethod())
|
||||
};
|
||||
const handleCross = () => setIsOpenModal(true);
|
||||
const policyLink = (
|
||||
<a href="https://aura.wit.life/" target="_blank" rel="noopener noreferrer">
|
||||
@ -49,21 +115,110 @@ function SubscriptionPage(): JSX.Element {
|
||||
</a>
|
||||
);
|
||||
|
||||
const isValidEmail = (email: string) => {
|
||||
return /\S+@\S+\.\S+/.test(email);
|
||||
};
|
||||
|
||||
const isValidName = (name: string) => {
|
||||
return !!(name.length > 0 && name.length < 30);
|
||||
};
|
||||
|
||||
const handleChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const email = event.target.value;
|
||||
if (!isValidEmail(email)) {
|
||||
setEmailError("Email is invalid");
|
||||
} else {
|
||||
setEmailError(null);
|
||||
}
|
||||
setEmail(email);
|
||||
};
|
||||
|
||||
const handleChangeName = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const name = event.target.value;
|
||||
if (!isValidName(name)) {
|
||||
setNameError("Name is invalid");
|
||||
} else {
|
||||
setNameError(null);
|
||||
}
|
||||
setName(name);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpecialWelcomeOffer open={isOpenModal} onClose={handleClick} />
|
||||
<Header classCross={styles.cross} clickCross={handleCross} />
|
||||
<UserHeader email={email} />
|
||||
{/* <UserHeader email={email} /> */}
|
||||
<section className={`${styles.page} page`}>
|
||||
<CallToAction />
|
||||
<Countdown start={10} />
|
||||
{/* <Countdown start={10} /> */}
|
||||
<PaymentTable
|
||||
items={paymentItems}
|
||||
currency={currency}
|
||||
locale={locale}
|
||||
/>
|
||||
<div className={styles["inputs-container"]}>
|
||||
<div className={styles["inputs-container__input-container"]}>
|
||||
<input
|
||||
className={`${styles["inputs-container__input"]} ${
|
||||
nameError && isSubmit && styles["inputs-container__input-error"]
|
||||
}`}
|
||||
type="name"
|
||||
name="name"
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={handleChangeName}
|
||||
placeholder="Your name"
|
||||
/>
|
||||
{isSubmit && !!nameError && (
|
||||
<span className={styles["inputs-container__label-error"]}>
|
||||
{nameError}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles["inputs-container__input-container"]}>
|
||||
<input
|
||||
className={`${styles["inputs-container__input"]} ${
|
||||
emailError &&
|
||||
isSubmit &&
|
||||
styles["inputs-container__input-error"]
|
||||
}`}
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={handleChangeEmail}
|
||||
placeholder="Your email"
|
||||
/>
|
||||
{isSubmit && !!emailError && (
|
||||
<span className={styles["inputs-container__label-error"]}>
|
||||
{emailError}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isLoading &&
|
||||
isSubmit &&
|
||||
isValidEmail(email) &&
|
||||
isValidName(name) && <Loader />}
|
||||
{(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}
|
||||
/>
|
||||
)}
|
||||
{!apiError && !error && !isLoading && isAuth && (
|
||||
<img src="/SuccessIcon.png" alt="Success Icon" />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles["subscription-action"]}>
|
||||
<MainButton onClick={handleClick}>{t("get_access")}</MainButton>
|
||||
<MainButton onClick={handleClick}>
|
||||
Start ${getPriceFromTrial(activeSubPlan?.trial || null)}
|
||||
</MainButton>
|
||||
</div>
|
||||
<Policy>
|
||||
<>
|
||||
|
||||
@ -16,3 +16,44 @@
|
||||
.cross {
|
||||
left: 28px;
|
||||
}
|
||||
|
||||
.inputs-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.inputs-container__input {
|
||||
width: 100%;
|
||||
border: solid #000 2px;
|
||||
border-radius: 20px;
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
padding: 16px 25px;
|
||||
}
|
||||
|
||||
.inputs-container__input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputs-container__input-error {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.inputs-container__label-error {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: red;
|
||||
width: 100%;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ export default {
|
||||
nasa_data_using: "We use NASA data to determine the exact position of the planets in the sky at the time of your birth to create wallpapers that are just right for you.",
|
||||
cta_title: "Start your 7-day trial",
|
||||
cta_subtitle: "No pressure. Cancel anytime.",
|
||||
reserved_for: "Reserved for ",
|
||||
reserved_for: "RESERVED for ",
|
||||
creating_profile: "Creating your profile",
|
||||
zodiac_analysis: "Zodiac data analysis",
|
||||
drawing_wallpaper: "Drawing Wallpapers",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user