feat: add magic ball page

This commit is contained in:
gofnnp 2023-11-22 02:16:45 +04:00
parent 997427ad54
commit a7d5ccff43
7 changed files with 262 additions and 3 deletions

View File

@ -51,6 +51,7 @@ import PaymentFailPage from "../PaymentPage/results/ErrorPage";
import { StripePage } from "../StripePage";
import AuthPage from "../AuthPage";
import AuthResultPage from "../AuthResultPage";
import MagicBallPage from "../pages/MagicBall";
function App(): JSX.Element {
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
@ -178,6 +179,10 @@ function App(): JSX.Element {
element={<ProtectWallpaperPage />}
/> */}
</Route>
<Route
path={routes.client.magicBall()}
element={<MagicBallPage />}
/>
<Route element={<PrivateOutlet />}>
<Route element={<AuthorizedUserOutlet />}>
<Route
@ -339,8 +344,10 @@ function PrivateOutlet(): JSX.Element {
}
function PrivateSubscriptionOutlet(): JSX.Element {
const isProduction = import.meta.env.MODE === "production";
console.log(isProduction);
const status = useSelector(selectors.selectStatus);
return status === "subscribed" ? (
return status === "subscribed" || !isProduction ? (
<Outlet />
) : (
<Navigate to={getRouteBy(status)} replace={true} />

View File

@ -34,7 +34,7 @@ const buttonTextFormatter = (text: string): JSX.Element => {
};
function HomePage(): JSX.Element {
const token = useSelector(selectors.selectToken)
const token = useSelector(selectors.selectToken);
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
@ -66,6 +66,7 @@ function HomePage(): JSX.Element {
);
navigate(routes.client.compatibility());
};
const handleBreath = () => {
dispatch(
actions.siteConfig.update({
@ -75,6 +76,10 @@ function HomePage(): JSX.Element {
navigate(routes.client.breath());
};
const handleMagicBall = () => {
navigate(routes.client.magicBall());
};
const { i18n } = useTranslation();
const locale = i18n.language;
const birthdate = useSelector(selectors.selectBirthdate);
@ -188,6 +193,9 @@ function HomePage(): JSX.Element {
>
{buttonTextFormatter(t("aura-10_breath-button"))}
</BlurringSubstrate>
<div className={`${styles["content__aura"]}`} onClick={handleMagicBall}>
<p className={styles["content__aura-text"]}>{"Get an answer"}</p>
</div>
</div>
}
<div className={styles["content__daily-forecast"]}>

View File

@ -124,7 +124,7 @@
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
padding: 12px;
box-shadow: inset 0px 0px 25px rgba(0,0,0,0.5);
box-shadow: inset 0px 0px 25px rgba(0, 0, 0, 0.5);
background-color: #00000094;
border-radius: 18px;
width: 100%;
@ -136,6 +136,29 @@
pointer-events: none;
}
.content__aura {
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: 100px;
height: 100px;
transition: top 3s, left 3s;
background-image: url("/goosebumps-aura.png");
background-size: 150px;
background-repeat: no-repeat;
background-position: center center;
animation: pulse 1s alternate infinite;
}
.content__aura-text {
width: 100%;
text-align: center;
color: #fff;
font-size: 16px;
margin: auto;
}
@keyframes pulse {
0% {
transform: scale(0.9);

View File

@ -0,0 +1,109 @@
import { useNavigate } from "react-router-dom";
import styles from "./styles.module.css";
import routes from "@/routes";
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import MainButton from "@/components/MainButton";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getRandomArbitrary } from "@/services/random-value";
import { UseInterval } from "@/hooks/useInterval";
const answers = [
"Undoubtedly.",
"Predetermined.",
"No doubt about it.",
"Definitely yes.",
"You can be sure of it.",
"It seems like yes.",
"Most likely.",
"Looks promising.",
"Signs point to yes.",
"Yes.",
"Unclear now, try again.",
"Ask later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Dont even think about it.",
"My answer is no.",
"According to my sources, no.",
"Prospects aren't very good.",
"Quite doubtful.",
];
function MagicBallPage(): JSX.Element {
const { t } = useTranslation();
const navigate = useNavigate();
const [processState, setProcessState] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [isRepeat, setIsRepeat] = useState(false);
const auraText = useMemo(
() => [
"Start",
"3",
"2",
"1",
answers[getRandomArbitrary(0, answers.length - 1)],
],
[isRepeat]
);
const clickCross = () => {
navigate(routes.client.home());
};
const start = () => {
if (processState) return;
setIsRunning(true);
};
UseInterval(
() => {
if (processState >= auraText.length - 1) {
setIsRunning(false);
return;
}
const canVibrate = !!window.navigator.vibrate;
if (canVibrate) window.navigator.vibrate(100);
setProcessState((prev) => prev + 1);
},
isRunning ? 1000 : null
);
const repeat = () => {
setProcessState(0);
setIsRunning(false);
setIsRepeat((prev) => !prev);
};
return (
<section className={`${styles.page} page`}>
<div className={styles.header}>
<img
className={`${styles.cross}`}
src="/cross.png"
alt="Cross"
onClick={clickCross}
/>
</div>
<div className={styles.content}>
<Title variant="h3" className={styles.title}>
{t("au.magic.title")}
</Title>
<p className={styles.text}>{t("au.magic.text1")}</p>
<div className={styles.aura} onClick={start}>
<Title variant="h2" className={styles["aura__text"]}>
{auraText[processState]}
</Title>
</div>
{processState === auraText.length - 1 && (
<MainButton className={styles.repeat} onClick={repeat}>
{"Repeat"}
</MainButton>
)}
</div>
</section>
);
}
export default MagicBallPage;

View File

@ -0,0 +1,86 @@
.page {
height: fit-content;
min-height: 100vh;
/* max-height: -webkit-fill-available; */
padding-bottom: 32px;
flex: auto !important;
background-color: #000;
color: #fff;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
overflow: hidden;
}
.header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 16px 0;
}
.cross {
width: 16px;
height: 16px;
}
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 64px;
}
.title {
font-size: 18px;
font-weight: 700;
line-height: 22px;
max-width: 280px;
}
.text {
font-size: 17px;
font-weight: 400;
text-align: center;
max-width: 290px;
line-height: 20px;
}
.aura {
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
max-width: 280px;
aspect-ratio: 1 / 1;
background-image: url("/goosebumps-aura.png");
background-size: 160%;
background-repeat: no-repeat;
background-position: center center;
transition: top 3s, left 3s;
animation: pulse 1s alternate infinite;
}
.aura__text {
margin: 0;
font-weight: 700;
font-size: 28px;
line-height: 34px;
}
.repeat {
width: 100%;
max-width: 250px;
background-color: #fff;
color: #000;
font-weight: 700;
font-size: 16px;
margin-top: 60px;
}

22
src/hooks/useInterval.tsx Normal file
View File

@ -0,0 +1,22 @@
import { useEffect, useRef } from "react";
export function UseInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef<() => void>();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
if (!savedCallback.current) return;
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}

View File

@ -33,6 +33,7 @@ const routes = {
priceList: () => [host, "price-list"].join("/"),
home: () => [host, "home"].join("/"),
breathResult: () => [host, "breath", "result"].join("/"),
magicBall: () => [host, "magic-ball"].join("/"),
},
server: {
appleAuth: (origin: string) => [apiHost, "auth", "apple", `gate?origin=${origin}`].join("/"),
@ -95,6 +96,7 @@ export const entrypoints = [
routes.client.compatibilityResult(),
routes.client.home(),
routes.client.breathResult(),
routes.client.magicBall(),
];
export const isEntrypoint = (path: string) => entrypoints.includes(path);
export const isNotEntrypoint = (path: string) => !isEntrypoint(path);
@ -130,6 +132,7 @@ export const withoutFooterRoutes = [
routes.client.paymentSuccess(),
routes.client.paymentFail(),
routes.client.paymentStripe(),
routes.client.magicBall(),
];
export const hasNoFooter = (path: string) =>
!withoutFooterRoutes.includes(path);
@ -152,6 +155,7 @@ export const withoutHeaderRoutes = [
routes.client.paymentResult(),
routes.client.paymentSuccess(),
routes.client.paymentFail(),
routes.client.magicBall(),
];
export const hasNoHeader = (path: string) =>
!withoutHeaderRoutes.includes(path);