feat: changed the order of the pages add auth form to subscription page

This commit is contained in:
gofnnp 2023-12-02 01:38:30 +04:00
parent ea93569c03
commit 490bf6f8da
11 changed files with 312 additions and 74 deletions

View File

@ -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 />}

View File

@ -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`}>

View File

@ -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;

View File

@ -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);

View File

@ -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") });

View File

@ -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 };

View File

@ -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,

View File

@ -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>
<>

View File

@ -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;
}

View File

@ -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",