fix: add subscription page and made edits

This commit is contained in:
gofnnp 2023-09-14 22:51:06 +04:00
parent 6213bebda2
commit 792f64580b
12 changed files with 302 additions and 143 deletions

View File

@ -58,9 +58,9 @@ function App(): JSX.Element {
<Route path={routes.client.priceList()} element={<PriceListPage />} />
<Route path={routes.client.home()} element={<HomePage />} />
<Route path={routes.client.breathResult()} element={<UserCallbacksPage />} />
<Route path={routes.client.subscription()} element={<SubscriptionPage />} />
<Route path={routes.client.paymentMethod()} element={<PaymentPage />} />
<Route element={<PrivateOutlet />}>
<Route path={routes.client.subscription()} element={<SubscriptionPage />} />
<Route path={routes.client.paymentMethod()} element={<PaymentPage />} />
<Route path={routes.client.wallpaper()} element={<ProtectWallpaperPage />} />
</Route>
<Route path="*" element={<NotFoundPage />} />

View File

@ -1,48 +1,53 @@
import styles from "./styles.module.css";
import BreathCircle from "../BreathCircle";
import { useCallback, useEffect, useState } from "react";
import { UserCallbacks, useApi, useApiCall } from "@/api";
import { Asset } from "@/api/resources/Assets";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { useDispatch } from "react-redux";
import { actions } from "@/store";
import { useTranslation } from "react-i18next";
import { getRandomArbitrary } from "@/services/random-value";
import FullScreenModal from "../FullScreenModal";
import StartBreathModalChild from "../StartBreathModalChild";
import Title from "../Title";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
function BreathPage(): JSX.Element {
const { i18n } = useTranslation();
const locale = i18n.language;
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const { t } = useTranslation();
const [asset, setAsset] = useState<Asset>();
const [isOpenModal, setIsOpenModal] = useState<boolean>(true);
const api = useApi();
const dispatch = useDispatch();
const navigate = useNavigate();
const assetsData = useCallback(async () => {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(zodiacSign, asset_categories);
const { assets } = await api.getAssets({
category: String(categoryId || "1"),
category: String("au"),
});
return assets;
}, [api, locale, zodiacSign]);
}, [api]);
const {
data,
// isPending
} = useApiCall<Asset[]>(assetsData);
useEffect(() => {
if (isOpenModal) return;
const timeOut = setTimeout(() => {
navigate(routes.client.breathResult());
}, 50_000);
return () => {
clearTimeout(timeOut);
};
}, [navigate, isOpenModal]);
useEffect(() => {
if (data) {
setAsset(data[getRandomArbitrary(0, data?.length || 0)]);
}
}, [data]);
}, [data, isOpenModal]);
const beginBreath = () => {
setIsOpenModal(false);
@ -89,12 +94,29 @@ function BreathPage(): JSX.Element {
<>
<section
className={`${styles.page} page`}
style={{ backgroundImage: `url(${asset?.url})` }}
style={{
backgroundImage: !isOpenModal ? `url(${asset?.url})` : "none",
}}
>
<FullScreenModal isOpen={isOpenModal}>
<StartBreathModalChild handleBegin={beginBreath} />
</FullScreenModal>
{!isOpenModal && <BreathCircle />}
{!isOpenModal && (
<div className={styles["text-container"]}>
<Title
variant="h2"
className={`${styles.text} ${styles["breath-in"]}`}
>
{t("breathIn")}
</Title>
<Title
variant="h2"
className={`${styles.text} ${styles["breath-out"]}`}
>
{t("breathOut")}
</Title>
</div>
)}
</section>
</>
);

View File

@ -1,12 +1,80 @@
.page {
position: relative;
height: calc(100vh - 50px);
flex: auto;
justify-content: center;
display: grid;
grid-template-rows: 1fr 96px;
justify-items: center;
background-color: #01010b;
background-size: cover;
background-repeat: no-repeat;
}
position: relative;
height: calc(100vh - 50px);
display: flex;
justify-content: center;
align-items: center;
background-color: #01010b;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
flex: auto;
}
.text-container {
position: relative;
width: 138px;
height: 200px;
}
.text {
position: absolute;
bottom: 0;
color: #fff;
will-change: scale, opacity;
}
.breath-in {
animation-name: breath-in;
animation-duration: 10s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.breath-out {
animation-name: breath-out;
animation-duration: 10s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes breath-in {
0% {
opacity: 0;
scale: 1;
}
10% {
opacity: 1;
}
40% {
opacity: 1;
}
50% {
opacity: 0;
scale: 2;
}
100% {
opacity: 0;
}
}
@keyframes breath-out {
0% {
opacity: 0;
scale: 1;
}
50% {
opacity: 0;
scale: 2;
}
60% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
scale: 1;
}
}

View File

@ -21,7 +21,7 @@ function CompatibilityPage(): JSX.Element {
const [isDisabledDate, setIsDisabledDate] = useState(true);
const [name, setName] = useState<string>('');
const [date, setDate] = useState<string | IDate>('');
const [compatCategory, setCompatCategory] = useState(2);
const [compatCategory, setCompatCategory] = useState(1);
const handleNext = () => {
dispatch(actions.compatibility.update({
rightUser: {

View File

@ -11,10 +11,11 @@ type HeaderProps = {
openMenu?: () => void
showBack?: boolean
showCross?: boolean
classCross?: CSSModuleClasses | string
clickCross?: () => void
}
function Header({ openMenu, showBack, showCross, clickCross = () => {undefined}, ...props }: HeaderProps & React.HTMLAttributes<HTMLDivElement>): JSX.Element {
function Header({ openMenu, showBack, showCross, classCross, clickCross = () => {undefined}, ...props }: HeaderProps & React.HTMLAttributes<HTMLDivElement>): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const location = useLocation()
@ -44,9 +45,11 @@ function Header({ openMenu, showBack, showCross, clickCross = () => {undefined},
return (
<header className={styles.header} {...props}>
{ (showBackButton || showBack) ? <BackButton className='pa' onClick={goBack} /> : null }
<img src={iconUrl} alt="logo" width="40" height="40" />
<span className={styles["header__title"]}>{t('app_name')}</span>
{(showCrossButton || showCross) ? <img className={styles.cross} src="/cross.png" alt="Cross" onClick={clickCross} /> : null}
<div className={styles['header__logo-container']}>
<img src={iconUrl} alt="logo" width="40" height="40" />
<span className={styles["header__title"]}>{t('app_name')}</span>
</div>
{(showCrossButton || showCross) ? <img className={`${styles.cross} ${classCross || ''}`} src="/cross.png" alt="Cross" onClick={clickCross} /> : null}
{showMenuButton ? <div className={styles["header__menu-btn"]} onClick={openMenu}>
<img src={menuUrl} alt="menu" width="40" height="40" />
</div> : null}

View File

@ -36,3 +36,9 @@
height: 33%;
width: auto;
}
.header__logo-container {
display: flex;
flex-direction: row;
align-items: center;
}

View File

@ -10,7 +10,7 @@
background-size: calc(100% + 186px);
background-position: center;
background-repeat: no-repeat;
padding: 16px 12px;
padding: 16px 12px 0;
overflow-y: scroll;
}
@ -62,11 +62,11 @@
background-repeat: no-repeat;
background-position: center center;
-webkit-backdrop-filter: blur(14px);
background-color: #ffffff69;
background-color: #696969;
backdrop-filter: blur(14px);
border-radius: 100%;
cursor: pointer;
background-blend-mode: exclusion;
/* background-blend-mode: exclusion; */
}
.header__save > a {
@ -108,11 +108,28 @@
}
.content__daily-forecast-body {
color: #a4a4a4;
color: #cbcbcb;
line-height: 130%;
font-weight: 500;
}
.content__daily-forecast {
display: flex;
flex-direction: column;
gap: 12px;
}
.content__daily-forecast-item {
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
padding: 12px;
box-shadow: inset 0px 0px 25px rgba(0,0,0,0.5);
background-color: #00000094;
border-radius: 18px;
width: 100vw;
/* margin-left: 13px; */
}
@keyframes pulse {
0% {
transform: scale(0.9);

View File

@ -16,7 +16,7 @@ function PriceListPage(): JSX.Element {
const email = useSelector(selectors.selectEmail)
const handleNext = () => {
navigate(routes.client.home())
navigate(routes.client.subscription())
}
return (

View File

@ -1,49 +1,60 @@
import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { selectors } from '@/store'
import MainButton from '../MainButton'
import Policy from '../Policy'
import Countdown from '../Countdown'
import PaymentTable, { Currency, Locale } from '../PaymentTable'
import UserHeader from '../UserHeader'
import CallToAction from '../CallToAction'
import routes from '@/routes'
import './styles.css'
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { selectors } from "@/store";
import MainButton from "../MainButton";
import Policy from "../Policy";
import Countdown from "../Countdown";
import PaymentTable, { Currency, Locale } from "../PaymentTable";
import UserHeader from "../UserHeader";
import CallToAction from "../CallToAction";
import routes from "@/routes";
import styles from "./styles.module.css";
import Header from "../Header";
const currency = Currency.USD
const locale = Locale.EN
const itemPriceId = 'aura-membership-2-week-USD'
const currency = Currency.USD;
const locale = Locale.EN;
const itemPriceId = "aura-membership-2-week-USD";
const paymentItems = [
{
title: 'Per 7-Day Trial For',
price: 1.00,
description: '2-Week Plan',
title: "Per 7-Day Trial For",
price: 1.0,
description: "2-Week Plan",
},
]
];
function SubscriptionPage(): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const email = useSelector(selectors.selectEmail)
const itemPrice = useSelector(selectors.selectPlanById(itemPriceId))
const handleClick = () => navigate(routes.client.paymentMethod())
const policyLink = <a href='https://aura.wit.life/' target='_blank' rel='noopener noreferrer'>{t('subscription_policy')}</a>
console.log({ itemPrice })
const { t } = useTranslation();
const navigate = useNavigate();
const email = useSelector(selectors.selectEmail);
const itemPrice = useSelector(selectors.selectPlanById(itemPriceId));
const handleClick = () => navigate(routes.client.paymentMethod());
const handleCross = () => navigate(routes.client.home());
const policyLink = (
<a href="https://aura.wit.life/" target="_blank" rel="noopener noreferrer">
{t("subscription_policy")}
</a>
);
console.log({ itemPrice });
return (
<>
<Header classCross={styles.cross} clickCross={handleCross} />
<UserHeader email={email} />
<section className='page'>
<section className="page">
<CallToAction />
<Countdown start={10}/>
<PaymentTable items={paymentItems} currency={currency} locale={locale}/>
<div className='subscription-action'>
<MainButton onClick={handleClick}>{t('get_access')}</MainButton>
<Countdown start={10} />
<PaymentTable
items={paymentItems}
currency={currency}
locale={locale}
/>
<div className={styles["subscription-action"]}>
<MainButton onClick={handleClick}>{t("get_access")}</MainButton>
</div>
<Policy>{t('subscription_text', { policyLink })}</Policy>
<Policy>{t("subscription_text", { policyLink })}</Policy>
</section>
</>
)
);
}
export default SubscriptionPage
export default SubscriptionPage;

View File

@ -7,4 +7,8 @@
justify-content: center;
background-color: #fff;
padding: 15px;
}
.cross {
left: 28px;
}

View File

@ -75,7 +75,7 @@ div[class^=divider] {
}
::-webkit-scrollbar {
width: 13px;
/* width: 13px; */
}
::-webkit-scrollbar-track {

View File

@ -1,54 +1,74 @@
import type { UserStatus } from "./types"
import type { UserStatus } from "./types";
const host = ''
const apiHost = 'https://aura.wit.life'
const prefix = 'api/v1'
const host = "";
const apiHost = "https://aura.wit.life";
const prefix = "api/v1";
const routes = {
client: {
root: () => [host, ''].join('/'),
birthday: () => [host, 'birthday'].join('/'),
didYouKnow: () => [host, 'did-you-know'].join('/'),
freePeriodInfo: () => [host, 'free-period'].join('/'),
birthtime: () => [host, 'birthtime'].join('/'),
emailEnter: () => [host, 'email'].join('/'),
subscription: () => [host, 'subscription'].join('/'),
createProfile: () => [host, 'profile', 'create'].join('/'),
attention: () => [host, 'attention'].join('/'),
feedback: () => [host, 'feedback'].join('/'),
paymentMethod: () => [host, 'payment', 'method'].join('/'),
wallpaper: () => [host, 'wallpaper'].join('/'),
static: () => [host, 'static', ':typeId'].join('/'),
legal: (type: string) => [host, 'static', type].join('/'),
compatibility: () => [host, 'compatibility'].join('/'),
compatibilityResult: () => [host, 'compatibility', 'result'].join('/'),
breath: () => [host, 'breath'].join('/'),
priceList: () => [host, 'price-list'].join('/'),
home: () => [host, 'home'].join('/'),
breathResult: () => [host, 'breath', 'result'].join('/'),
root: () => [host, ""].join("/"),
birthday: () => [host, "birthday"].join("/"),
didYouKnow: () => [host, "did-you-know"].join("/"),
freePeriodInfo: () => [host, "free-period"].join("/"),
birthtime: () => [host, "birthtime"].join("/"),
emailEnter: () => [host, "email"].join("/"),
subscription: () => [host, "subscription"].join("/"),
createProfile: () => [host, "profile", "create"].join("/"),
attention: () => [host, "attention"].join("/"),
feedback: () => [host, "feedback"].join("/"),
paymentMethod: () => [host, "payment", "method"].join("/"),
wallpaper: () => [host, "wallpaper"].join("/"),
static: () => [host, "static", ":typeId"].join("/"),
legal: (type: string) => [host, "static", type].join("/"),
compatibility: () => [host, "compatibility"].join("/"),
compatibilityResult: () => [host, "compatibility", "result"].join("/"),
breath: () => [host, "breath"].join("/"),
priceList: () => [host, "price-list"].join("/"),
home: () => [host, "home"].join("/"),
breathResult: () => [host, "breath", "result"].join("/"),
},
server: {
user: () => [apiHost, prefix, 'user.json'].join('/'),
token: () => [apiHost, prefix, 'auth', 'token.json'].join('/'),
elements: () => [apiHost, prefix, 'elements.json'].join('/'),
element: (type: string) => [apiHost, prefix, 'elements', `${type}.json`].join('/'),
apps: (bundleId: string) => [apiHost, prefix, 'apps', `${bundleId}.json`].join('/'),
assets: (category: string) => [apiHost, prefix, 'assets', 'categories', `${category}.json`].join('/'),
assetCategories: () => [apiHost, prefix, 'assets', 'categories.json'].join('/'),
dailyForecasts: () => [apiHost, prefix, 'user', 'daily_forecast.json'].join('/'),
auras: () => [apiHost, prefix, 'user', 'aura.json'].join('/'),
paymentIntents: () => [apiHost, prefix, 'user', 'payment_intents.json'].join('/'),
subscriptionItems: () => [apiHost, prefix, 'user', 'subscription', 'item_prices.json'].join('/'),
subscriptionCheckout: () => [apiHost, prefix, 'user', 'subscription', 'checkout', 'new.json'].join('/'),
subscriptionStatus: () => [apiHost, prefix, 'user', 'subscription_receipts', 'status.json'].join('/'),
subscriptionReceipts: () => [apiHost, prefix, 'user', 'subscription_receipts.json'].join('/'),
subscriptionReceipt: (id: string) => [apiHost, prefix, 'user', 'subscription_receipts', `${id}.json`].join('/'),
compatCategories: () => [apiHost, prefix, 'ai', 'compat_categories.json'].join('/'),
compat: () => [apiHost, prefix, 'ai', 'compats.json'].join('/'),
createUserCallbacks: () => [apiHost, prefix, 'user', 'callbacks.json'].join('/'),
getUserCallbacks: (id: string) => [apiHost, prefix, 'user', 'callbacks', `${id}.json`].join('/'),
user: () => [apiHost, prefix, "user.json"].join("/"),
token: () => [apiHost, prefix, "auth", "token.json"].join("/"),
elements: () => [apiHost, prefix, "elements.json"].join("/"),
element: (type: string) =>
[apiHost, prefix, "elements", `${type}.json`].join("/"),
apps: (bundleId: string) =>
[apiHost, prefix, "apps", `${bundleId}.json`].join("/"),
assets: (category: string) =>
[apiHost, prefix, "assets", "categories", `${category}.json`].join("/"),
assetCategories: () =>
[apiHost, prefix, "assets", "categories.json"].join("/"),
dailyForecasts: () =>
[apiHost, prefix, "user", "daily_forecast.json"].join("/"),
auras: () => [apiHost, prefix, "user", "aura.json"].join("/"),
paymentIntents: () =>
[apiHost, prefix, "user", "payment_intents.json"].join("/"),
subscriptionItems: () =>
[apiHost, prefix, "user", "subscription", "item_prices.json"].join("/"),
subscriptionCheckout: () =>
[apiHost, prefix, "user", "subscription", "checkout", "new.json"].join(
"/"
),
subscriptionStatus: () =>
[apiHost, prefix, "user", "subscription_receipts", "status.json"].join(
"/"
),
subscriptionReceipts: () =>
[apiHost, prefix, "user", "subscription_receipts.json"].join("/"),
subscriptionReceipt: (id: string) =>
[apiHost, prefix, "user", "subscription_receipts", `${id}.json`].join(
"/"
),
compatCategories: () =>
[apiHost, prefix, "ai", "compat_categories.json"].join("/"),
compat: () => [apiHost, prefix, "ai", "compats.json"].join("/"),
createUserCallbacks: () =>
[apiHost, prefix, "user", "callbacks.json"].join("/"),
getUserCallbacks: (id: string) =>
[apiHost, prefix, "user", "callbacks", `${id}.json`].join("/"),
},
}
};
export const entrypoints = [
routes.client.root(),
@ -62,15 +82,20 @@ export const entrypoints = [
routes.client.compatibilityResult(),
routes.client.home(),
routes.client.breathResult(),
]
export const isEntrypoint = (path: string) => entrypoints.includes(path)
export const isNotEntrypoint = (path: string) => !isEntrypoint(path)
export const withNavigationRoutes = [routes.client.wallpaper()]
export const hasNavigation = (path: string) => withNavigationRoutes.includes(path)
export const hasNoNavigation = (path: string) => !hasNavigation(path)
];
export const isEntrypoint = (path: string) => entrypoints.includes(path);
export const isNotEntrypoint = (path: string) => !isEntrypoint(path);
export const withNavigationRoutes = [routes.client.wallpaper()];
export const hasNavigation = (path: string) =>
withNavigationRoutes.includes(path);
export const hasNoNavigation = (path: string) => !hasNavigation(path);
export const withCrossButtonRoutes = [routes.client.attention()]
export const hasCrossButton = (path: string) => withCrossButtonRoutes.includes(path)
export const withCrossButtonRoutes = [
routes.client.attention(),
routes.client.subscription(),
];
export const hasCrossButton = (path: string) =>
withCrossButtonRoutes.includes(path);
export const withoutFooterRoutes = [
routes.client.didYouKnow(),
@ -84,26 +109,29 @@ export const withoutFooterRoutes = [
routes.client.compatibilityResult(),
routes.client.home(),
routes.client.breathResult(),
]
export const hasNoFooter = (path: string) => !withoutFooterRoutes.includes(path)
];
export const hasNoFooter = (path: string) =>
!withoutFooterRoutes.includes(path);
export const withoutHeaderRoutes = [
routes.client.compatibility(),
]
export const hasNoHeader = (path: string) => !withoutHeaderRoutes.includes(path)
routes.client.subscription(),
];
export const hasNoHeader = (path: string) =>
!withoutHeaderRoutes.includes(path);
export const getRouteBy = (status: UserStatus): string => {
switch (status) {
case 'lead':
return routes.client.birthday()
case 'registred':
case 'unsubscribed':
return routes.client.subscription()
case 'subscribed':
return routes.client.wallpaper()
case "lead":
return routes.client.birthday();
case "registred":
case "unsubscribed":
return routes.client.subscription();
case "subscribed":
return routes.client.wallpaper();
default:
throw new Error(`Unknown user status, received status is "${status}"`)
throw new Error(`Unknown user status, received status is "${status}"`);
}
}
};
export default routes
export default routes;