feat: add user path from home, add preview before breath, fix home buttons background color

This commit is contained in:
gofnnp 2023-09-16 02:07:33 +04:00
parent ed5cd812ce
commit 30d1ded4f5
16 changed files with 499 additions and 48 deletions

View File

@ -16,6 +16,7 @@ function BreathPage(): JSX.Element {
const { t } = useTranslation();
const [asset, setAsset] = useState<Asset>();
const [isOpenModal, setIsOpenModal] = useState<boolean>(true);
const [isShowPreview, setIsShowPreview] = useState<boolean>(true);
const api = useApi();
const dispatch = useDispatch();
const navigate = useNavigate();
@ -27,26 +28,33 @@ function BreathPage(): JSX.Element {
return assets;
}, [api]);
const {
data,
} = useApiCall<Asset[]>(assetsData);
const { data } = useApiCall<Asset[]>(assetsData);
useEffect(() => {
if (isOpenModal) return;
const previewTimeOut = setTimeout(() => {
setIsShowPreview(false);
}, 10_000);
const timeOut = setTimeout(() => {
const navigateTimeOut = setTimeout(() => {
navigate(routes.client.breathResult());
}, 50_000);
}, 60_000);
return () => {
clearTimeout(timeOut);
clearTimeout(navigateTimeOut);
clearTimeout(previewTimeOut);
};
}, [navigate, isOpenModal]);
useEffect(() => {
if (data) {
if (!data) return;
const leoTimeOut = setTimeout(() => {
setAsset(data[getRandomArbitrary(0, data?.length || 0)]);
}
}, 10_000);
return () => {
clearTimeout(leoTimeOut);
};
}, [data, isOpenModal]);
const beginBreath = () => {
@ -95,13 +103,26 @@ function BreathPage(): JSX.Element {
<section
className={`${styles.page} page`}
style={{
backgroundImage: !isOpenModal ? `url(${asset?.url})` : "none",
backgroundImage: `url(${
!isOpenModal && !isShowPreview ? asset?.url : "none"
})`,
}}
>
{!isOpenModal && isShowPreview && (
<div className={styles.preview}>
<img className={styles.leo} src="/leo.png" alt="leo" />
<Title
variant="h2"
className={`${styles.text} ${styles["breath-relax"]}`}
>
{t("aura.breath_relax.text")}
</Title>
</div>
)}
<FullScreenModal isOpen={isOpenModal}>
<StartBreathModalChild handleBegin={beginBreath} />
</FullScreenModal>
{!isOpenModal && (
{!isOpenModal && !isShowPreview && (
<div className={styles["text-container"]}>
<Title
variant="h2"

View File

@ -5,8 +5,8 @@
justify-content: center;
align-items: center;
background-color: #01010b;
background-size: cover;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
flex: auto;
}
@ -38,6 +38,85 @@
animation-timing-function: linear;
}
.preview {
position: relative;
width: 100%;
display: flex;
justify-content: center;
}
.leo {
width: 90%;
animation-name: leo;
animation-duration: 10s;
animation-iteration-count: 1;
animation-timing-function: linear;
}
.breath-relax {
animation-name: breath-relax;
animation-duration: 10s;
animation-iteration-count: 1;
animation-timing-function: linear;
}
@keyframes leo {
0% {
opacity: 0;
scale: 1;
}
5% {
opacity: 1;
}
10% {
scale: 1;
}
25% {
scale: 1.1;
}
35% {
scale: 1.1;
}
50% {
scale: 1;
}
60% {
scale: 1;
}
75% {
scale: 1.1;
}
85% {
scale: 1.1;
}
95% {
opacity: 1;
}
100% {
scale: 1;
opacity: 0;
}
}
@keyframes breath-relax {
0% {
opacity: 0;
}
5% {
opacity: 0;
}
10% {
opacity: 1;
}
95% {
opacity: 1;
}
100% {
scale: 1;
opacity: 0;
}
}
@keyframes breath-in {
0% {
opacity: 0;

View File

@ -1,21 +1,41 @@
import { useTranslation } from "react-i18next";
import Title from "../Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { useCallback, useState } from "react";
import { AICompats, AIRequests, useApi, useApiCall } from "@/api";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { EPathsFromHome } from "@/store/siteConfig";
function CompatResultPage(): JSX.Element {
const token =
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
const { t } = useTranslation();
const navigate = useNavigate();
const api = useApi();
const dispatch = useDispatch();
const birthdate = useSelector(selectors.selectBirthdate);
const rightUser = useSelector(selectors.selectRightUser);
const categoryId = useSelector(selectors.selectCategoryId);
const homeConfig = useSelector(selectors.selectHome);
const [text, setText] = useState("Loading...");
const handleNext = () => {
dispatch(
actions.siteConfig.update({
home: { pathFromHome: homeConfig.pathFromHome, isShowNavbar: true },
})
);
if (homeConfig.pathFromHome === EPathsFromHome.breath) {
return navigate(routes.client.home());
}
if (homeConfig.pathFromHome === EPathsFromHome.compatibility) {
return navigate(routes.client.breath());
}
};
const loadData = useCallback(async () => {
const right_bday =
typeof rightUser.birthDate === "string"
@ -55,6 +75,7 @@ function CompatResultPage(): JSX.Element {
return (
<section className={`${styles.page} page`}>
{text !== "Loading..." && <div className={styles.cross} onClick={handleNext}></div>}
<div className={styles["title-container"]}>
<Title variant="h2">{t("you_and", { user: rightUser.name })}</Title>
</div>
@ -64,6 +85,19 @@ function CompatResultPage(): JSX.Element {
</Title>
<p className={styles["result-container__text"]}>{text}</p>
</div>
{text !== "Loading..." && (
<div className={styles["button-container"]}>
<p className={styles["button-container__text"]}>
{t("now-you-know")}
</p>
<button
className={styles["button-container__button"]}
onClick={handleNext}
>
{t("go-through")}
</button>
</div>
)}
</section>
);
}

View File

@ -2,30 +2,100 @@
position: relative;
height: calc(100vh - 50px);
flex: auto;
max-height: -webkit-fill-available;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
}
.title-container {
color: #e9445a;
color: #e9445a;
}
.percent {
margin-bottom: 0;
margin-bottom: 0;
}
.result-container {
width: 100%;
width: 100%;
}
.result-container__title {
width: 100%;
text-align: left;
width: 100%;
text-align: left;
}
.result-container__text {
white-space:pre-wrap;
line-height: 1.2;
}
white-space: pre-wrap;
line-height: 1.2;
}
.button-container {
position: fixed;
width: 100%;
max-width: 560px;
bottom: 0;
border-radius: 16px 16px 0 0;
background-color: rgba(128, 128, 128, 0.546);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
display: flex;
align-items: center;
flex-direction: column;
gap: 16px;
padding: 16px 0 24px 0;
}
.button-container__text {
max-width: calc(100% - 64px);
text-align: center;
color: #7ce4fc;
}
.button-container__button {
border: none;
bottom: 24px;
width: calc(100% - 64px);
max-width: 496px;
border-radius: 32px;
padding: 24px 0;
background: -moz-linear-gradient(to bottom, #2dc8e2, #2b7ed6);
background: -webkit-linear-gradient(to bottom, #2dc8e2, #2b7ed6);
background: linear-gradient(to bottom, #2dc8e2, #2b7ed6);
color: #fff;
}
.cross {
position: absolute;
top: 24px;
right: 24px;
width: 24px;
height: 24px;
border: solid 2px #bdbdbd;
border-radius: 100%;
rotate: 45deg;
cursor: pointer;
}
.cross::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 10px;
background-color: #bdbdbd;
}

View File

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

View File

@ -8,13 +8,19 @@ import { useCallback, useEffect, useState } from "react";
import BlurringSubstrate from "../BlurringSubstrate";
import EnergyValues from "../EnergyValues";
import { UserAura } from "@/api/resources/Auras";
import { useSelector } from "react-redux";
import { getCategoryIdByZodiacSign, getZodiacSignByDate } from "@/services/zodiac-sign";
import { selectors } from "@/store";
import { useDispatch, useSelector } from "react-redux";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { actions, selectors } from "@/store";
import { getRandomArbitrary } from "@/services/random-value";
import Title from "../Title";
import { UserDailyForecast } from "@/api/resources/UserDailyForecasts";
import { download } from "@/services/download";
import { EPathsFromHome } from "@/store/siteConfig";
import NavbarHome, { INavbarHomeItems } from "../NavbarHome";
const buttonTextFormatter = (text: string): JSX.Element => {
const sentences = text.split(".");
@ -32,12 +38,54 @@ function HomePage(): JSX.Element {
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIzNjEyLCJpYXQiOjE2OTM0MTg5MTAsImV4cCI6MTcwMjA1ODkxMCwianRpIjoiNzg5MjkwYWItODg0YS00MGUyLTkyNjEtOWI2OGEyNjkwNmE0IiwiZW1haWwiOiJvdGhlckBleGFtcGxlLmNvbSIsInN0YXRlIjoicHJvdmVuIiwibG9jIjoiZW4iLCJ0eiI6LTI4ODAwLCJ0eXBlIjoiZW1haWwiLCJpc3MiOiJjb20ubGlmZS5hdXJhIn0.J2ocWIv5jKzuKMcwMgWMiNMyGg5qLlMAeln-bQm_9lw";
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const homeConfig = useSelector(selectors.selectHome);
const isShowNavbar = homeConfig.isShowNavbar;
const handleCompatibility = () => {
dispatch(
actions.siteConfig.update({
home: { pathFromHome: EPathsFromHome.compatibility, isShowNavbar },
})
);
navigate(routes.client.compatibility());
};
const handleBreath = () => {
dispatch(
actions.siteConfig.update({
home: { pathFromHome: EPathsFromHome.breath, isShowNavbar },
})
);
navigate(routes.client.breath());
};
const navbarHomeItems: INavbarHomeItems[] = [
{
title: 'Breathing',
path: routes.client.breath(),
image: '',
onClick: handleBreath
},
{
title: 'Aura',
path: routes.client.home(),
image: '',
active: true,
onClick: () => null
},
{
title: 'Compatibility',
path: routes.client.compatibility(),
image: '',
onClick: handleCompatibility
},
{
title: 'My Moon',
path: routes.client.home(),
image: '',
onClick: () => null
}
]
const { i18n } = useTranslation();
const locale = i18n.language;
@ -49,9 +97,11 @@ function HomePage(): JSX.Element {
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") });
const { assets } = await api.getAssets({
category: String(categoryId || "1"),
});
return assets;
}, [api]);
}, [api, locale, zodiacSign]);
const {
data: assets,
@ -77,7 +127,7 @@ function HomePage(): JSX.Element {
const dailyForecastData = useCallback(async () => {
const { user_daily_forecast } = await api.getDailyForecasts({ token });
return user_daily_forecast;
}, [api, token])
}, [api, token]);
const {
data: dailyForecast,
@ -85,14 +135,16 @@ function HomePage(): JSX.Element {
} = useApiCall<UserDailyForecast>(dailyForecastData);
const downloadImg = () => {
if( !asset ) return;
download(asset.url, 'image.png');
}
if (!asset) return;
download(asset.url, "image.png");
};
return (
<section
className={`${styles.page} page`}
style={{ backgroundImage: `url(${asset?.url.replace('http://', 'https://')})` }}
style={{
backgroundImage: `url(${asset?.url.replace("http://", "https://")})`,
}}
>
<div className={styles.header}>
<BlurringSubstrate>
@ -128,18 +180,26 @@ function HomePage(): JSX.Element {
</BlurringSubstrate>
</div>
<div className={styles["content__daily-forecast"]}>
{dailyForecast && dailyForecast.forecasts.map((forecast, index) => (
<div className={styles["content__daily-forecast-item"]} key={index}>
<Title variant="h3" className={styles["content__daily-forecast-title"]}>
{forecast.category}
</Title>
<p className={styles["content__daily-forecast-body"]}>
{forecast.body}
</p>
</div>
))}
{dailyForecast &&
dailyForecast.forecasts.map((forecast, index) => (
<div
className={styles["content__daily-forecast-item"]}
key={index}
>
<Title
variant="h3"
className={styles["content__daily-forecast-title"]}
>
{forecast.category}
</Title>
<p className={styles["content__daily-forecast-body"]}>
{forecast.body}
</p>
</div>
))}
</div>
</div>
{isShowNavbar && <NavbarHome items={navbarHomeItems} />}
</section>
);
}

View File

@ -97,6 +97,7 @@
border-radius: 25px !important;
text-align: center;
color: #fff;
background-color: #00000094;
cursor: pointer;
}

View File

@ -0,0 +1,33 @@
import { Link } from 'react-router-dom'
import styles from './styles.module.css'
export interface INavbarHomeItems {
title: string
path: string
image: string
active?: boolean
onClick?: () => void
}
interface INavbarHomeProps {
items: INavbarHomeItems[]
}
function NavbarHome({items}: INavbarHomeProps): JSX.Element {
return (
<div className={`${styles['container']}`}>
{items.map((item, index) => (
<div className={`${styles['navbar-item']} ${item.active ? styles['navbar-item--active'] : ''}`} key={index}>
<Link to={item.path} onClick={item.onClick}>
{/* <img src={item.image} alt={item.title} /> */}
<p>{item.title}</p>
</Link>
</div>
))}
</div>
)
}
export default NavbarHome

View File

@ -0,0 +1,23 @@
.container {
width: 100vw;
max-width: 560px;
background-color: #232322;
padding: 12px 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
position: fixed;
bottom: 0;
}
.navbar-item {
font-size: 12px;
display: flex;
flex-direction: column;
justify-content: center;
color: #dedede;
}
.navbar-item--active {
color: #f24058;
}

View File

@ -2,8 +2,8 @@ import { useNavigate } from 'react-router-dom'
import routes from '@/routes'
import styles from './styles.module.css'
import UserHeader from '../UserHeader'
import { useSelector } from 'react-redux'
import { selectors } from '@/store'
import { useDispatch, useSelector } from 'react-redux'
import { actions, selectors } from '@/store'
import Title from '../Title'
import { useTranslation } from 'react-i18next'
import EmailsList from '../EmailsList'
@ -12,10 +12,17 @@ import PriceList from '../PriceList'
function PriceListPage(): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const dispatch = useDispatch();
const homeConfig = useSelector(selectors.selectHome);
const selectedPrice = useSelector(selectors.selectSelectedPrice)
const email = useSelector(selectors.selectEmail)
const handleNext = () => {
dispatch(
actions.siteConfig.update({
home: { pathFromHome: homeConfig.pathFromHome, isShowNavbar: false },
})
);
navigate(routes.client.subscription())
}

View File

@ -1,15 +1,33 @@
import Title from "../Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { EPathsFromHome } from "@/store/siteConfig";
function UserCallbacksPage(): JSX.Element {
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const statChanges = useSelector(selectors.selectUserCallbacksPrevStat);
const text = useSelector(selectors.selectUserCallbacksDescription);
const homeConfig = useSelector(selectors.selectHome);
const handleNext = () => {
dispatch(actions.siteConfig.update({ home: { pathFromHome: homeConfig.pathFromHome, isShowNavbar: true } }));
if (homeConfig.pathFromHome === EPathsFromHome.compatibility) {
return navigate(routes.client.home());
}
if (homeConfig.pathFromHome === EPathsFromHome.breath) {
return navigate(routes.client.compatibility());
}
};
return (
<section className={`${styles.page} page`}>
<div className={styles.cross} onClick={handleNext}></div>
<div className={styles["title-container"]}>
<Title variant="h3" className={styles.percent}>
<>
@ -37,6 +55,9 @@ function UserCallbacksPage(): JSX.Element {
</div>
<p className={styles["result-container__text"]}>{text}</p>
</div>
<button className={styles.button} onClick={handleNext}>
{t("use-all-power")}
</button>
</section>
);
}

View File

@ -1,12 +1,13 @@
.page {
position: relative;
height: calc(100vh - 50px);
max-height: -webkit-fill-available;
/* max-height: -webkit-fill-available; */
flex: auto;
background-color: #000;
color: #bababb;
overflow-y: scroll;
text-align: center;
padding-bottom: 114px;
}
.result-container__values {
@ -35,3 +36,51 @@
line-height: 1.2;
margin-top: 32px;
}
.button {
position: fixed;
border: none;
bottom: 24px;
width: calc(100% - 64px);
max-width: 496px;
border-radius: 32px;
padding: 24px 0;
background: -moz-linear-gradient(to bottom, #01d400, #196e17);
background: -webkit-linear-gradient(to bottom, #01d400, #196e17);
background: linear-gradient(to bottom, #01d400, #196e17);
color: #fff;
}
.cross {
position: absolute;
top: 24px;
right: 24px;
width: 24px;
height: 24px;
border: solid 2px #bdbdbd;
border-radius: 100%;
rotate: 45deg;
cursor: pointer;
}
.cross::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 10px;
background-color: #bdbdbd;
}

View File

@ -85,5 +85,9 @@ export default {
"breathe-subtitle": "Breathing practice will help improve your aura. Breath in the positive energy, breathe out the negative...",
"breathe-title": "Stop and breathe to help you relax and focus on what really matters.",
"aura-begin_breathe-button": "BEGIN",
"aura.breath_relax.text": "Breath & Relax",
"use-all-power": "Use all the power of your Aura",
"go-through": "Go through practicing recovery",
"now-you-know": "Now you know who`s causing your financial energy loss.",
},
}

View File

@ -0,0 +1,5 @@
export const getQueryParam = (paramName: string) => {
const search = window.location.search;
const params = new URLSearchParams(search);
return params.get(paramName);
};

View File

@ -3,6 +3,7 @@ import token, { actions as tokenActions, selectToken } from './token'
import user, { actions as userActions, selectUser } from './user'
import form, { actions as formActions, selectors as formSelectors } from './form'
import aura, { actions as auraActions } from './aura'
import siteConfig, { selectHome, actions as siteConfigActions } from './siteConfig'
import payment, { actions as paymentActions } from './payment'
import subscriptionPlans, { actions as subscriptionPlasActions, selectPlanById } from './subscriptionPlan'
import status, { actions as userStatusActions, selectStatus } from './status'
@ -15,8 +16,9 @@ import { selectRightUser, selectCategoryId } from './compatibility'
import { selectUserCallbacksDescription, selectUserCallbacksPrevStat } from './userCallbacks'
const preloadedState = loadStore()
export const reducer = combineReducers({ token, user, form, status, subscriptionPlans, aura, payment, compatibility, userCallbacks })
export const reducer = combineReducers({ token, user, form, status, subscriptionPlans, aura, payment, compatibility, userCallbacks, siteConfig })
export const actions = {
token: tokenActions,
user: userActions,
@ -24,6 +26,7 @@ export const actions = {
status: userStatusActions,
subscriptionPlan: subscriptionPlasActions,
aura: auraActions,
siteConfig: siteConfigActions,
compatibility: compatibilityActions,
payment: paymentActions,
userCallbacks: userCallbacksActions,
@ -40,6 +43,7 @@ export const selectors = {
selectSelectedPrice,
selectUserCallbacksDescription,
selectUserCallbacksPrevStat,
selectHome,
...formSelectors,
}
export type RootState = ReturnType<typeof reducer>

39
src/store/siteConfig.ts Normal file
View File

@ -0,0 +1,39 @@
import { createSlice, createSelector } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
export enum EPathsFromHome {
compatibility,
breath
}
interface ISiteConfig {
home: {
isShowNavbar: boolean;
pathFromHome: EPathsFromHome;
};
}
const initialState: ISiteConfig = {
home: {
isShowNavbar: false,
pathFromHome: EPathsFromHome.compatibility
},
};
const siteConfigSlice = createSlice({
name: "siteConfig",
initialState,
reducers: {
update(state, action: PayloadAction<Partial<ISiteConfig>>) {
return { ...state, ...action.payload };
},
},
extraReducers: (builder) => builder.addCase("reset", () => initialState),
});
export const { actions } = siteConfigSlice;
export const selectHome = createSelector(
(state: { siteConfig: ISiteConfig }) => state.siteConfig.home,
(siteConfig) => siteConfig
);
export default siteConfigSlice.reducer;