feat: add EnergyVampirism, MoonPhaseTracker, NameHoroscope, PredictionMoon, Thermal, WallpapersZodiacSign,

This commit is contained in:
gofnnp 2023-11-26 02:48:31 +04:00
parent bb815df7f7
commit 2a59a7e597
33 changed files with 2346 additions and 47 deletions

19
public/ButtonReload.svg Normal file
View File

@ -0,0 +1,19 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b_1_8)">
<circle cx="36" cy="36" r="36" fill="white" fill-opacity="0.45"/>
</g>
<g clip-path="url(#clip0_1_8)">
<path d="M22.1944 36.0539C22.8689 36.0539 23.3888 35.534 23.3888 34.8595V33.5246C23.3888 31.1077 25.0328 29.534 27.5761 29.534H38.5363V32.8361C38.5363 33.3981 38.8736 33.7213 39.4497 33.7213C39.7025 33.7213 39.9696 33.623 40.1663 33.4543L45.4777 29.0562C45.9274 28.6909 45.9556 28.1148 45.4777 27.7213L40.1663 23.3091C39.9696 23.1405 39.7025 23.0562 39.4497 23.0562C38.8736 23.0562 38.5363 23.3653 38.5363 23.9415V27.2014H27.8009C23.6838 27.2014 21 29.548 21 33.356V34.8595C21 35.534 21.5059 36.0539 22.1944 36.0539ZM49.8056 35.0562C49.1171 35.0562 48.6113 35.5621 48.6113 36.2506V37.5855C48.6113 40.0023 46.9673 41.5621 44.4098 41.5621H33.4496V38.3022C33.4496 37.726 33.1265 37.4168 32.5504 37.4168C32.2834 37.4168 32.0305 37.5011 31.8197 37.6698L26.5082 42.068C26.0585 42.4614 26.0445 43.0234 26.5082 43.4028L31.8197 47.815C32.0305 47.9836 32.2834 48.0819 32.5504 48.0819C33.1265 48.0819 33.4496 47.7587 33.4496 47.1967V43.9086H44.185C48.3162 43.9086 51 41.5621 51 37.7541V36.2506C51 35.5621 50.4801 35.0562 49.8056 35.0562Z" fill="black" fill-opacity="0.6"/>
</g>
<defs>
<filter id="filter0_b_1_8" x="-10" y="-10" width="92" height="92" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="5"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1_8"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_1_8" result="shape"/>
</filter>
<clipPath id="clip0_1_8">
<rect width="30" height="25.0819" fill="white" transform="translate(21 23)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

19
public/ButtonSave.svg Normal file
View File

@ -0,0 +1,19 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b_257_339)">
<circle cx="36" cy="36" r="36" fill="white" fill-opacity="0.45"/>
</g>
<g clip-path="url(#clip0_257_339)">
<path d="M26.307 53.4901H45.693C49.2253 53.4901 51 51.7324 51 48.2506V31.3831C51 27.9014 49.2253 26.1436 45.693 26.1436H40.9775V28.8648H45.6423C47.3155 28.8648 48.2788 29.7774 48.2788 31.5352V48.0986C48.2788 49.8563 47.3155 50.7689 45.6423 50.7689H26.3408C24.6507 50.7689 23.7211 49.8563 23.7211 48.0986V31.5352C23.7211 29.7774 24.6507 28.8648 26.3408 28.8648H31.0225V26.1436H26.307C22.7746 26.1436 21 27.9014 21 31.3831V48.2506C21 51.7324 22.7746 53.4901 26.307 53.4901ZM35.9915 42.369C36.3465 42.369 36.6338 42.2676 36.9718 41.9296L42.6845 36.4027C42.938 36.1494 43.0902 35.8788 43.0902 35.5239C43.0902 34.8309 42.5493 34.3408 41.8564 34.3408C41.5183 34.3408 41.1803 34.4761 40.9436 34.7465L38.3747 37.4676L37.2422 38.6675L37.3436 36.1324V18.3183C37.3436 17.6084 36.7183 17 35.9915 17C35.2648 17 34.6563 17.6084 34.6563 18.3183V36.1324L34.7577 38.6675L33.6084 37.4676L31.0563 34.7465C30.8197 34.4761 30.4479 34.3408 30.1268 34.3408C29.4169 34.3408 28.9098 34.8309 28.9098 35.5239C28.9098 35.8788 29.0451 36.1494 29.2986 36.4027L35.0113 41.9296C35.3662 42.2676 35.6535 42.369 35.9915 42.369Z" fill="black" fill-opacity="0.6"/>
</g>
<defs>
<filter id="filter0_b_257_339" x="-10" y="-10" width="92" height="92" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="5"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_257_339"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_257_339" result="shape"/>
</filter>
<clipPath id="clip0_257_339">
<rect width="30" height="37.3352" fill="white" transform="translate(21 17)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -8,9 +8,52 @@ const dateFormatter = (date: string): string => {
.join("-");
};
interface IBestiesHoroscopePayload {
birthDate: string;
sign: string;
}
interface IMyHoroscopePayload {
birthDate: string;
sign: string;
period: string;
}
interface IPredictionMoonsPayload {
birthDate: string;
sign: string;
period: string;
}
interface IThermalPayload {
birthDate: string;
sign: string;
period: string;
}
interface IMoonPhaseTrackerPayload {
birthDate: string;
sign: string;
period: string;
}
interface IEnergyVampirismPayload {
birthDate: string;
sign: string;
period: string;
}
export type AIRequestPayload =
| IBestiesHoroscopePayload
| IPredictionMoonsPayload
| IMyHoroscopePayload
| IThermalPayload
| IMoonPhaseTrackerPayload
| IEnergyVampirismPayload;
export interface Payload {
promptKey: string;
aiRequest: IAIRequestPayload;
aiRequest: AIRequestPayload;
token: string;
}
@ -19,11 +62,6 @@ export interface PayloadGet {
token: string;
}
interface IAIRequestPayload {
birthDate: string;
sign: string;
}
export interface Response {
ai_request: IAiResponse;
meta: IMeta;
@ -74,18 +112,42 @@ export interface IAIRequest {
};
}
const getBody = (aiRequest: AIRequestPayload, promptKey: string): string => {
if (["horoscope_besties"].includes(promptKey)) {
return JSON.stringify({
ai_request: {
birth_date: dateFormatter(aiRequest.birthDate),
sign: aiRequest.sign,
},
});
}
if (
[
"horoscope_name",
"prediction_moons",
"compatibility_thermal",
"moonse_phase",
"energy_vampirism",
].includes(promptKey)
) {
return JSON.stringify({
ai_request: {
birth_date: dateFormatter(aiRequest.birthDate),
sign: aiRequest.sign,
period: (aiRequest as IPredictionMoonsPayload).period,
},
});
}
return JSON.stringify({ aiRequest });
};
export const createRequest = ({
promptKey,
aiRequest,
token,
}: Payload): Request => {
const url = new URL(routes.server.aiRequestsV2(promptKey));
const body = JSON.stringify({
ai_request: {
birth_date: dateFormatter(aiRequest.birthDate),
sign: aiRequest.sign,
},
});
const body = getBody(aiRequest, promptKey);
return new Request(url, {
method: "POST",
headers: getAuthHeaders(token),

View File

@ -53,6 +53,12 @@ import AuthPage from "../AuthPage";
import AuthResultPage from "../AuthResultPage";
import MagicBallPage from "../pages/MagicBall";
import BestiesHoroscopeResult from "../pages/BestiesHoroscopeResult";
import PredictionMoonResult from "../pages/PredictionMoonResult";
import MyHoroscopeResult from "../pages/MyHoroscopeResult";
import ThermalResult from "../pages/ThermalResult";
import MoonPhaseTrackerResult from "../pages/MoonPhaseTrackerResult";
import EnergyVampirismResult from "../pages/EnergyVampirismResult";
import NameHoroscopeResult from "../pages/NameHoroscopeResult";
function App(): JSX.Element {
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
@ -225,6 +231,30 @@ function App(): JSX.Element {
path={routes.client.horoscopeBestiesResult()}
element={<BestiesHoroscopeResult />}
/>
<Route
path={routes.client.predictionMoonResult()}
element={<PredictionMoonResult />}
/>
<Route
path={routes.client.myHoroscopeResult()}
element={<MyHoroscopeResult />}
/>
<Route
path={routes.client.thermalResult()}
element={<ThermalResult />}
/>
<Route
path={routes.client.moonPhaseTracker()}
element={<MoonPhaseTrackerResult />}
/>
<Route
path={routes.client.energyVampirismResult()}
element={<EnergyVampirismResult />}
/>
<Route
path={routes.client.nameHoroscopeResult()}
element={<NameHoroscopeResult />}
/>
</Route>
</Route>
<Route path="*" element={<NotFoundPage />} />
@ -351,7 +381,6 @@ function PrivateOutlet(): JSX.Element {
function PrivateSubscriptionOutlet(): JSX.Element {
// const isProduction = import.meta.env.MODE === "production";
const isProduction = false;
console.log(isProduction);
const status = useSelector(selectors.selectStatus);
return status === "subscribed" || !isProduction ? (
<Outlet />

View File

@ -0,0 +1,52 @@
import { useTranslation } from "react-i18next";
import styles from "./styles.module.css";
import { useApi } from "@/api";
import { getRandomArbitrary } from "@/services/random-value";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
interface IEnergyVampirismProps {
onClick: () => void;
}
function EnergyVampirism({ onClick }: IEnergyVampirismProps): JSX.Element {
const api = useApi();
const { t } = useTranslation();
const [backgroundUrl, setBackgroundUrl] = useState("");
const name = useSelector(selectors.selectUser).username;
const getImage = async () => {
try {
const categoryId = "au.energy_vampirism";
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset?.url || "");
} catch (error) {
console.error("Error: ", error);
}
};
useEffect(() => {
getImage();
}, []);
return (
<div
className={styles.container}
style={{ backgroundImage: `url(${backgroundUrl})` }}
onClick={onClick}
>
<p className={styles.period}>Today</p>
<p className={styles.text}>
{name?.length ? `${name}, ` : ""}
Discover the {t("Find out if you're an energy vampire today or not?")}
</p>
{/* <div className={styles.blur}></div> */}
</div>
);
}
export default EnergyVampirism;

View File

@ -0,0 +1,29 @@
.container {
width: 100%;
height: 410px;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
padding: 18px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 16px;
}
.period {
font-size: 17px;
font-weight: 500;
line-height: 20px;
color: #b1b0b0;
}
.text {
font-size: 23px;
font-weight: 500;
line-height: 27px;
color: #fff;
}

View File

@ -16,13 +16,22 @@ import {
import { actions, selectors } from "@/store";
import { getRandomArbitrary } from "@/services/random-value";
import Title from "../Title";
import { UserDailyForecast } from "@/api/resources/UserDailyForecasts";
// import { UserDailyForecast } from "@/api/resources/UserDailyForecasts";
import { EPathsFromHome } from "@/store/siteConfig";
import { buildFilename, saveFile } from "../WallpaperPage/utils";
import Onboarding from "../Onboarding";
import TextWithFinger from "../TextWithFinger";
import Slider from "../Slider";
import BestiesHoroscopeSlider, { Horoscope } from "../BestiesHoroscopeSlider";
import PredictionMoonsSlider, {
IPredictionMoon,
} from "../PredictionMoonsSlider";
import { predictionMoonsPeriods } from "@/data";
import WallpapersZodiacSign from "../WallpapersZodiacSign";
import ThermalSlider from "../ThermalSlider";
import MoonPhaseTracker from "../MoonPhaseTracker";
import EnergyVampirism from "../EnergyVampirism";
import NameHoroscopeSlider from "../NameHoroscopeSlider";
const buttonTextFormatter = (text: string): JSX.Element => {
const sentences = text.split(".");
@ -37,16 +46,23 @@ const buttonTextFormatter = (text: string): JSX.Element => {
function HomePage(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const buttonsRef = useRef<HTMLDivElement>(null);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const [asset, setAsset] = useState<Asset>();
const [moonsAssets, setMoonsAssets] = useState<Asset[]>([]);
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const isShowNavbar = homeConfig.isShowNavbar;
const onboardingConfigHome = useSelector(selectors.selectOnboardingHome);
const bestiesHoroscopes = useSelector(selectors.selectCompatibilities);
const compatibilities = useSelector(selectors.selectCompatibilities);
const [isShowOnboardingHome, setIsShowOnboardingHome] = useState(
!onboardingConfigHome?.isShown
@ -80,17 +96,14 @@ function HomePage(): JSX.Element {
navigate(routes.client.breath());
};
const handleMyHoroscope = () => {
navigate(routes.client.myHoroscopeResult());
};
const handleMagicBall = () => {
navigate(routes.client.magicBall());
};
const { i18n } = useTranslation();
const locale = i18n.language;
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const [asset, setAsset] = useState<Asset>();
const api = useApi();
const assetsData = useCallback(async () => {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(zodiacSign, asset_categories);
@ -121,15 +134,32 @@ function HomePage(): JSX.Element {
// isPending
} = useApiCall<UserAura>(auraData);
const dailyForecastData = useCallback(async () => {
const { user_daily_forecast } = await api.getDailyForecasts({ token });
return user_daily_forecast;
}, [api, token]);
// const dailyForecastData = useCallback(async () => {
// const { user_daily_forecast } = await api.getDailyForecasts({ token });
// return user_daily_forecast;
// }, [api, token]);
const {
data: dailyForecast,
// isPending
} = useApiCall<UserDailyForecast>(dailyForecastData);
// const {
// data: dailyForecast,
// // isPending
// } = useApiCall<UserDailyForecast>(dailyForecastData);
useEffect(() => {
getMoonsImages();
}, []);
const getMoonsImages = async () => {
const assets = (
await api.getAssets({ category: String("au.prediction_moons" || "1") })
).assets;
const randomAssets: Asset[] = [];
for (let i = 0; i < predictionMoonsPeriods.length; i++) {
const index = getRandomArbitrary(0, assets.length - 1);
randomAssets.push(assets[index]);
assets.splice(index, 1);
}
setMoonsAssets(randomAssets);
};
const downloadImg = () => {
if (!asset) return;
@ -143,6 +173,30 @@ function HomePage(): JSX.Element {
);
};
const handleThermal = (item: Horoscope) => {
const { name, birthDate } = item;
navigate(
`${routes.client.thermalResult()}?name=${name}&birthDate=${birthDate}`
);
};
const handlePredictionMoon = (item: IPredictionMoon) => {
const { period } = item;
navigate(`${routes.client.predictionMoonResult()}?period=${period}`);
};
const handleMoonPhaseTracker = () => {
navigate(`${routes.client.moonPhaseTracker()}?period=today`);
};
const handleEnergyVampirism = () => {
navigate(`${routes.client.energyVampirismResult()}?period=today`);
};
const handleNameHoroscope = (item: IPredictionMoon) => {
navigate(`${routes.client.nameHoroscopeResult()}?period=${item.period}`);
};
return (
<section
className={`${styles.page} page`}
@ -182,9 +236,11 @@ function HomePage(): JSX.Element {
>
<div
ref={buttonsRef}
className={`${styles["content__buttons"]} ${
isShowNavbar ? styles["content__buttons--hidden"] : ""
}`}
className={`${styles["content__buttons"]}
`}
// ${
// isShowNavbar ? styles["content__buttons--hidden"] : ""
// }
>
<Onboarding
targetRef={buttonsRef.current as HTMLDivElement}
@ -209,6 +265,15 @@ function HomePage(): JSX.Element {
>
{buttonTextFormatter(t("aura-10_breath-button"))}
</BlurringSubstrate>
<BlurringSubstrate
style={{ color: "#FD433F" }}
className={styles["content__buttons-item"]}
clickHandler={handleMyHoroscope}
>
{buttonTextFormatter(
t("Receive an In-Depth Analysis and Todays Horoscope")
)}
</BlurringSubstrate>
</div>
<div className={`${styles["content__buttons"]}`}>
<div
@ -221,12 +286,12 @@ function HomePage(): JSX.Element {
{/* SLIDERS */}
<div className={styles.sliders}>
<div>
<div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}>
{"Your Besties' Horoscope"}
</Title>
<Slider>
{bestiesHoroscopes.map((item, index) => (
{compatibilities.map((item, index) => (
<BestiesHoroscopeSlider
data={item}
key={index}
@ -237,10 +302,81 @@ function HomePage(): JSX.Element {
))}
</Slider>
</div>
<div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}>
{"Prediction Based on Your Moons"}
</Title>
<Slider>
{predictionMoonsPeriods.map((item, index) => (
<PredictionMoonsSlider
image={moonsAssets[index]?.url}
data={item}
key={index}
onClick={() => {
handlePredictionMoon(item);
}}
/>
))}
</Slider>
</div>
</div>
{/* END SLIDERS */}
<div className={styles["content__daily-forecast"]}>
<Title variant="h2" className={styles["sliders__title"]}>
{"Energy Vampirism"}
</Title>
<EnergyVampirism onClick={handleEnergyVampirism} />
{/* SLIDERS */}
<div className={styles.sliders}>
<div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}>
{t("Your Name's Horoscope")}
</Title>
<Slider>
{predictionMoonsPeriods.map((item, index) => (
<NameHoroscopeSlider
data={{ ...item, name: "Victor Ershov" }}
key={index}
onClick={() => {
handleNameHoroscope(item);
}}
/>
))}
</Slider>
</div>
</div>
{/* END SLIDERS */}
<Title variant="h2" className={styles["sliders__title"]}>
{"AI-based unique Walpapers"}
</Title>
<WallpapersZodiacSign />
{/* SLIDERS */}
<div className={styles.sliders}>
<div className={styles["slider"]}>
<Title variant="h2" className={styles["sliders__title"]}>
{t("au.thermal_compatibility.result_title")}
</Title>
<Slider>
{compatibilities.map((item, index) => (
<ThermalSlider
data={item}
key={index}
onClick={() => {
handleThermal(item);
}}
/>
))}
</Slider>
</div>
</div>
{/* END SLIDERS */}
<MoonPhaseTracker onClick={handleMoonPhaseTracker} />
{/* <div className={styles["content__daily-forecast"]}>
{dailyForecast &&
dailyForecast.forecasts.map((forecast, index) => (
<div
@ -251,7 +387,6 @@ function HomePage(): JSX.Element {
variant="h3"
className={styles["content__daily-forecast-title"]}
>
{/* {forecast.category} */}
{t("aura.personal_aura.button")}
</Title>
<p className={styles["content__daily-forecast-body"]}>
@ -259,7 +394,7 @@ function HomePage(): JSX.Element {
</p>
</div>
))}
</div>
</div> */}
</div>
{/* </div> */}
</section>

View File

@ -187,6 +187,7 @@
text-align: left;
padding: 0 12px;
margin-bottom: 12px;
width: 100%;
}
@keyframes pulse {

View File

@ -0,0 +1,53 @@
import { useTranslation } from "react-i18next";
import styles from "./styles.module.css";
import { useApi } from "@/api";
import { getRandomArbitrary } from "@/services/random-value";
import { useEffect, useState } from "react";
interface IMoonPhaseTracker {
onClick: () => void;
}
function MoonPhaseTracker({ onClick }: IMoonPhaseTracker): JSX.Element {
const api = useApi();
const { t } = useTranslation();
const [backgroundUrl, setBackgroundUrl] = useState("");
const getImage = async () => {
try {
const categoryId = "au.moonse_phase";
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
};
useEffect(() => {
getImage();
}, []);
const removeLastWord = (sentence: string): string => {
const lastIndex = sentence.lastIndexOf(" ");
return sentence.substring(0, lastIndex);
};
return (
<div
className={styles.container}
style={{ backgroundImage: `url(${backgroundUrl})` }}
onClick={onClick}
>
<p className={styles.text}>Today</p>
<p className={styles.text}>
Discover the {removeLastWord(t("au.moonse_phase.result_title"))}
</p>
{/* <div className={styles.blur}></div> */}
</div>
);
}
export default MoonPhaseTracker;

View File

@ -0,0 +1,38 @@
.container {
position: relative;
width: 100%;
height: 300px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 16px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border-radius: 17px;
padding: 16px;
}
.text {
position: relative;
color: #fff;
font-size: 17px;
line-height: 20px;
font-weight: 500;
z-index: 2;
}
.blur {
content: "";
position: absolute;
left: 0;
bottom: 0;
height: 35%;
width: 100%;
filter: blur(10px);
z-index: 1;
-webkit-backdrop-filter: blur(50px);
backdrop-filter: blur(50px);
}

View File

@ -0,0 +1,67 @@
import { useEffect, useState } from "react";
import styles from "./styles.module.css";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useTranslation } from "react-i18next";
import { useApi } from "@/api";
import { getRandomArbitrary } from "@/services/random-value";
interface INameHoroscopeSliderProps {
data: INameHoroscope;
onClick: () => void;
}
export interface INameHoroscope {
period: string;
name: string;
}
function NameHoroscopeSlider({
data,
onClick,
}: INameHoroscopeSliderProps): JSX.Element {
const api = useApi();
const { i18n } = useTranslation();
const locale = i18n.language;
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, []);
return (
<div
style={{ backgroundImage: `url(${backgroundUrl})` }}
className={styles.container}
onClick={onClick}
>
<p className={styles.period}>{data.period}</p>
<p className={styles.name}>
<b>{data.name}</b>
</p>
</div>
);
}
export default NameHoroscopeSlider;

View File

@ -0,0 +1,35 @@
.container {
width: 280px;
height: 200px;
border-radius: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding: 24px 12px;
background-repeat: no-repeat !important;
background-size: cover !important;
background-position: center !important;
background-color: #000 !important;
cursor: pointer;
}
.period {
color: #b1b0b0;
font-weight: 500;
font-size: 17px;
line-height: 20px;
text-align: center;
}
.period::first-letter {
text-transform: uppercase;
}
.name {
color: #fff;
font-weight: 700;
font-size: 23px;
line-height: 27px;
text-align: center;
}

View File

@ -0,0 +1,31 @@
import styles from "./styles.module.css";
interface IPredictionMoonsSliderProps {
image: string;
data: IPredictionMoon;
onClick: () => void;
}
export interface IPredictionMoon {
period: string;
}
function PredictionMoonsSlider({
image,
data,
onClick,
}: IPredictionMoonsSliderProps): JSX.Element {
return (
<div
className={styles.container}
style={{ backgroundImage: `url(${image})` }}
onClick={onClick}
>
<p className={styles.text}>
<b>{data.period}</b>
</p>
</div>
);
}
export default PredictionMoonsSlider;

View File

@ -0,0 +1,32 @@
.container {
width: 200px;
height: 200px;
border-radius: 17px;
display: flex;
align-items: flex-end;
justify-content: start;
padding: 8px 12px;
background-repeat: no-repeat !important;
background-size: cover !important;
background-position: center !important;
background-color: #000 !important;
cursor: pointer;
}
.text {
color: #fff;
font-weight: 400;
font-size: 17px;
line-height: 20px;
text-align: left;
}
.text::first-letter {
text-transform: uppercase;
}
.text > b {
color: #fff;
font-weight: 700;
font-size: 18px;
}

View File

@ -0,0 +1,61 @@
import { useTranslation } from "react-i18next";
import styles from "./styles.module.css";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { useEffect, useState } from "react";
import { useApi } from "@/api";
import { getRandomArbitrary } from "@/services/random-value";
interface ThermalProps {
data: Thermal;
onClick: () => void;
}
export interface Thermal {
name: string;
birthDate: string;
}
function ThermalSlider({ data, onClick }: ThermalProps): JSX.Element {
const api = useApi();
const { i18n } = useTranslation();
const locale = i18n.language;
const zodiacSign = getZodiacSignByDate(data.birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, []);
return (
<div
className={styles.container}
onClick={onClick}
>
<img className={styles.background} src={backgroundUrl} alt="background image" />
<div className={styles["name-container"]}>
<p>with {data.name}</p>
</div>
<span className={styles.period}>Today</span>
</div>
);
}
export default ThermalSlider;

View File

@ -0,0 +1,58 @@
.container {
position: relative;
padding: 4px;
margin: 26px 0;
width: 200px;
height: 200px;
border-radius: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
background-repeat: no-repeat !important;
background-size: cover !important;
background-position: center !important;
background-color: #000 !important;
cursor: pointer;
}
.background {
position: absolute;
top: -15px;
left: -15px;
width: calc(100% + 30px);
height: calc(100% + 30px);
object-fit: cover;
border-radius: 100%;
filter: blur(9px);
}
.name-container {
position: relative;
z-index: 100;
margin-top: 28px;
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 64px;
text-align: center;
color: #000;
text-overflow: ellipsis;
overflow: hidden;
font-weight: 600;
font-size: 21px;
line-height: 25px;
background-color: #d9d9d985;
border-radius: 100px;
}
.period {
position: relative;
z-index: 100;
color: #fff;
font-size: 17px;
line-height: 20px;
text-align: center;
}

View File

@ -0,0 +1,64 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAuth } from "@/auth";
import { useApi, Assets } from "@/api";
import { saveFile, buildFilename } from "../WallpaperPage/utils";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function WallpapersZodiacSign(): JSX.Element {
const api = useApi();
const { i18n } = useTranslation();
const locale = i18n.language;
const [asset, setAsset] = useState<Assets.Asset>();
const {
user,
// token
} = useAuth();
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const category = user?.profile.sign?.sign || "";
const getZodiacWallpaper = async () => {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(zodiacSign, asset_categories);
const { assets } = await api.getAssets({
category: String(categoryId || "1"),
});
const asset = assets[getRandomArbitrary(0, assets.length - 1)];
setAsset(asset);
};
useEffect(() => {
getZodiacWallpaper();
}, []);
const saveImage = () =>
asset &&
saveFile(asset.url.replace("http://", "https://"), buildFilename(category));
return (
<div
className={styles["wallpaper"]}
style={{ backgroundImage: `url(${asset?.url})` }}
>
<div className={styles["buttons-container"]}>
<img src="/ButtonSave.svg" alt="Save image" onClick={saveImage} />
<img
src="/ButtonReload.svg"
alt="Get new image"
onClick={getZodiacWallpaper}
/>
</div>
</div>
);
}
export default WallpapersZodiacSign;

View File

@ -0,0 +1,22 @@
.wallpaper {
width: 100%;
height: 520px;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
padding: 26px 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
}
.buttons-container {
width: fit-content;
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
}

View File

@ -50,7 +50,7 @@
.cross {
width: 24px;
height: 24px;
border: solid 2px #bdbdbd;
/* border: solid 2px #bdbdbd; */
border-radius: 100%;
rotate: 45deg;
cursor: pointer;
@ -62,7 +62,7 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
width: 20px;
height: 2px;
background-color: #bdbdbd;
}
@ -74,6 +74,6 @@
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 10px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,165 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useRef, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function EnergyVampirismResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const name = useSelector(selectors.selectUser).username;
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const timeoutRef = useRef<NodeJS.Timeout>();
const progressBars: ProgressBar[] = [
{
label: t("au.energy_vampirism.loading1"),
},
{
label: t("au.energy_vampirism.loading2"),
},
{
label: t("au.energy_vampirism.loading3"),
},
{
label: t("au.energy_vampirism.loading4"),
},
];
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
},
promptKey: "energy_vampirism",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
timeoutRef.current = setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">
{t("au.energy_vampirism.loading_title")}{" "}
<span className={styles["loading-name"]}>{name}</span>
</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<Title variant="h2">
<span className={styles["loading-name"]}>{name}</span>{" "}
{t("au.energy_vampirism.result_title")}
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default EnergyVampirismResult;

View File

@ -0,0 +1,79 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
width: 100%;
height: 446px;
color: #fd433f;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,157 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useRef, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate, useSearchParams } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function MoonPhaseTrackerResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const [searchParams] = useSearchParams();
const period = searchParams.get("period");
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const timeoutRef = useRef<NodeJS.Timeout>();
const progressBars: ProgressBar[] = [
{
label: t("au.moonse_phase.loading1"),
},
{
label: t("au.moonse_phase.loading2"),
},
{
label: t("au.moonse_phase.loading3"),
},
{
label: t("au.moonse_phase.loading4"),
},
];
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
period: period?.toLowerCase() || "today",
},
promptKey: "moonse_phase",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
timeoutRef.current = setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate, period]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const categoryId = "au.moonse_phase";
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">{t("au.moonse_phase.loading_title")}</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<Title variant="h2">
{t("au.moonse_phase.loading_title")} {period}
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default MoonPhaseTrackerResult;

View File

@ -0,0 +1,79 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
width: 100%;
height: 446px;
color: #fd433f;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,161 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useRef, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function MyHoroscopeResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const timeoutRef = useRef<NodeJS.Timeout>();
const progressBars: ProgressBar[] = [
{
label: t("au.my_horoscope.loading1"),
},
{
label: t("au.my_horoscope.loading2"),
},
{
label: t("au.my_horoscope.loading3"),
},
{
label: t("au.my_horoscope.loading4"),
},
];
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
period: "today",
},
promptKey: "horoscope_name",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
timeoutRef.current = setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">{t("au.my_horoscope.loading_title")}</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<Title variant="h2">
{t("Receive an In-Depth Analysis and Todays Horoscope")}
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default MyHoroscopeResult;

View File

@ -0,0 +1,79 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
width: 100%;
height: 446px;
color: #fd433f;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,163 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useRef, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate, useSearchParams } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function NameHoroscopeResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const [searchParams] = useSearchParams();
const period = searchParams.get("period") || "today";
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const timeoutRef = useRef<NodeJS.Timeout>();
const progressBars: ProgressBar[] = [
{
label: t("au.name_horoscope.loading1"),
},
{
label: t("au.name_horoscope.loading2"),
},
{
label: t("au.name_horoscope.loading3"),
},
{
label: t("au.name_horoscope.loading4"),
},
];
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
period,
},
promptKey: "horoscope_name",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
timeoutRef.current = setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">{t("au.name_horoscope.loading_title")}</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<Title variant="h2">
{t("Receive an In-Depth Analysis and Todays Horoscope")}
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default NameHoroscopeResult;

View File

@ -0,0 +1,79 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
width: 100%;
height: 446px;
color: #fd433f;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,156 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate, useSearchParams } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function PredictionMoonResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const [searchParams] = useSearchParams();
const period = searchParams.get("period");
const birthDate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const progressBars: ProgressBar[] = [
{
label: t("au.prediction_moons.loading1"),
},
{
label: t("au.prediction_moons.loading2"),
},
{
label: t("au.prediction_moons.loading3"),
},
{
label: t("au.prediction_moons.loading4"),
},
];
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
period: period?.toLowerCase() || "today",
},
promptKey: "prediction_moons",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate, period]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">{t("au.prediction_moons.loading_title")}</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<Title variant="h2">
{t("au.prediction_moons.result_title")} {period}
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default PredictionMoonResult;

View File

@ -0,0 +1,79 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
width: 100%;
height: 446px;
color: #fd433f;
border-radius: 17px;
background-color: #000;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

View File

@ -0,0 +1,160 @@
import { useTranslation } from "react-i18next";
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { useCallback, useEffect, useState } from "react";
import { AIRequestsV2, useApi, useApiCall } from "@/api";
import { useNavigate, useSearchParams } from "react-router-dom";
import routes from "@/routes";
import FullScreenModal from "@/components/FullScreenModal";
import ProgressBarsModal, { ProgressBar } from "@/components/ProgressBarsModal";
import {
getCategoryIdByZodiacSign,
getZodiacSignByDate,
} from "@/services/zodiac-sign";
import { getRandomArbitrary } from "@/services/random-value";
function ThermalResult(): JSX.Element {
const token = useSelector(selectors.selectToken);
const { i18n, t } = useTranslation();
const locale = i18n.language;
const navigate = useNavigate();
const api = useApi();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
const [text, setText] = useState("Loading...");
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
const [searchParams] = useSearchParams();
const name = searchParams.get("name");
const birthDate = searchParams.get("birthDate") || "";
const zodiacSign = getZodiacSignByDate(birthDate);
const [backgroundUrl, setBackgroundUrl] = useState("");
const progressBars: ProgressBar[] = [
{
label: t("au.thermal_compatibility.loading1"),
},
{
label: t("au.thermal_compatibility.loading2"),
},
{
label: t("au.thermal_compatibility.loading3"),
},
{
label: t("au.thermal_compatibility.loading4"),
},
];
const handleNext = () => {
return navigate(routes.client.home());
};
const loadData = useCallback(async () => {
const payload: AIRequestsV2.Payload = {
aiRequest: {
birthDate,
sign: getZodiacSignByDate(birthDate).toLowerCase(),
period: 'today'
},
promptKey: "compatibility_thermal",
token,
};
const aIRequest = await api.AIRequestsV2(payload);
if (aIRequest.ai_request.state !== "ready") {
const getAIRequest = async () => {
const aIRequestById = await api.getAIRequestsV2({
id: aIRequest.ai_request.id,
token,
});
if (aIRequestById.ai_request.state !== "ready") {
setTimeout(getAIRequest, 3000);
}
setText(aIRequestById?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
checkLoading();
return aIRequestById.ai_request;
};
return await getAIRequest();
}
setIsDataLoading(false);
checkLoading();
setText(aIRequest?.ai_request?.response?.response?.body || "Loading...");
return aIRequest?.ai_request?.response;
}, [api, token, birthDate]);
useApiCall<AIRequestsV2.IAIRequest>(loadData);
useEffect(() => {
(async () => {
try {
const { asset_categories } = await api.getAssetCategories({ locale });
const categoryId = getCategoryIdByZodiacSign(
zodiacSign,
asset_categories
);
const assets = (
await api.getAssets({ category: String(categoryId || "1") })
).assets;
const randomAsset = assets[getRandomArbitrary(0, assets.length - 1)];
setBackgroundUrl(randomAsset.url);
} catch (error) {
console.error("Error: ", error);
}
})();
}, [api, locale, zodiacSign]);
const getPaddingBottomPage = () => {
if (showNavbarFooter) return "164px";
return "108px";
};
function checkLoading() {
if (isVisualLoading || isDataLoading) {
setIsOpenModal(true);
} else {
setIsOpenModal(false);
}
}
return (
<section
className={`${styles.page} page`}
style={{ paddingBottom: getPaddingBottomPage() }}
>
<FullScreenModal isOpen={isOpenModal}>
<ProgressBarsModal
progressBars={progressBars}
onEndLoading={() => {
setIsVisualLoading(false);
checkLoading();
}}
>
<Title variant="h2">
{t("au.thermal_compatibility.loading_title")}
</Title>
<></>
</ProgressBarsModal>
</FullScreenModal>
<div className={styles["cross-container"]}>
<div className={styles.cross} onClick={handleNext}></div>
</div>
<div
className={styles["sign-image"]}
style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<img className={styles["thermal-image"]} src={backgroundUrl} alt="background image" />
<Title className={styles.title} variant="h2">
{t("au.thermal_compatibility.result_title")}{" "}
<span className={styles["loading-name"]}>{name}</span>
</Title>
</div>
<p className={styles.text}>{text}</p>
</section>
);
}
export default ThermalResult;

View File

@ -0,0 +1,94 @@
.page {
position: relative;
height: fit-content;
min-height: 100vh;
flex: auto;
/* max-height: -webkit-fill-available; */
background-color: #000;
color: #fff;
overflow-y: scroll;
padding-bottom: 180px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.loading-name {
color: #fff;
}
.sign-image {
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
color: #fd433f;
border-radius: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.thermal-image {
position: absolute;
top: -15px;
left: -15px;
width: calc(100% + 30px);
height: calc(100% + 30px);
object-fit: cover;
border-radius: 100%;
filter: blur(36px);
z-index: 99;
}
.title {
position: relative;
z-index: 100;
}
.text {
font-size: 18px;
line-height: 22px;
font-weight: 400;
padding: 0 8px;
text-align: left;
margin-top: 30px;
}
.cross-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.cross {
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: 20px;
height: 2px;
background-color: #bdbdbd;
}
.cross::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background-color: #bdbdbd;
}

16
src/data.ts Normal file
View File

@ -0,0 +1,16 @@
import { IPredictionMoon } from "./components/PredictionMoonsSlider";
export const predictionMoonsPeriods: IPredictionMoon[] = [
{
period: "today",
},
{
period: "week",
},
{
period: "month",
},
{
period: "year",
},
];

View File

@ -34,11 +34,19 @@ const routes = {
home: () => [host, "home"].join("/"),
breathResult: () => [host, "breath", "result"].join("/"),
magicBall: () => [host, "magic-ball"].join("/"),
horoscopeBestiesResult: () => [host, "horoscope", "besties"].join("/"),
horoscopeBestiesResult: () => [host, "horoscope-besties"].join("/"),
predictionMoonResult: () => [host, "prediction-moon"].join("/"),
myHoroscopeResult: () => [host, "my-horoscope"].join("/"),
thermalResult: () => [host, "thermal"].join("/"),
moonPhaseTracker: () => [host, "moon-phase-tracker"].join("/"),
energyVampirismResult: () => [host, "energy-vampirism"].join("/"),
nameHoroscopeResult: () => [host, "name-horoscope"].join("/"),
},
server: {
appleAuth: (origin: string) => [apiHost, "auth", "apple", `gate?origin=${origin}`].join("/"),
googleAuth: (origin: string) => [apiHost, "auth", "google", `gate?origin=${origin}`].join("/"),
appleAuth: (origin: string) =>
[apiHost, "auth", "apple", `gate?origin=${origin}`].join("/"),
googleAuth: (origin: string) =>
[apiHost, "auth", "google", `gate?origin=${origin}`].join("/"),
user: () => [apiHost, prefix, "user.json"].join("/"),
token: () => [apiHost, prefix, "auth", "token.json"].join("/"),
elements: () => [apiHost, prefix, "elements.json"].join("/"),
@ -82,8 +90,12 @@ const routes = {
getUserCallbacks: (id: string) =>
[apiHost, prefix, "user", "callbacks", `${id}.json`].join("/"),
getTranslations: () => [siteHost, "api/v2", "t.json"].join("/"),
aiRequestsV2: (promptKey: string) => [apiHost, "api/v2", "ai", "prompts", promptKey, "requests.json"].join("/"),
getAiRequestsV2: (id: string) => [apiHost, "api/v2", "ai", "requests", `${id}.json`].join("/"),
aiRequestsV2: (promptKey: string) =>
[apiHost, "api/v2", "ai", "prompts", promptKey, "requests.json"].join(
"/"
),
getAiRequestsV2: (id: string) =>
[apiHost, "api/v2", "ai", "requests", `${id}.json`].join("/"),
},
};
@ -137,6 +149,12 @@ export const withoutFooterRoutes = [
routes.client.paymentStripe(),
routes.client.magicBall(),
routes.client.horoscopeBestiesResult(),
routes.client.predictionMoonResult(),
routes.client.myHoroscopeResult(),
routes.client.thermalResult(),
routes.client.moonPhaseTracker(),
routes.client.energyVampirismResult(),
routes.client.nameHoroscopeResult(),
];
export const hasNoFooter = (path: string) =>
!withoutFooterRoutes.includes(path);
@ -161,6 +179,13 @@ export const withoutHeaderRoutes = [
routes.client.paymentFail(),
routes.client.magicBall(),
routes.client.horoscopeBestiesResult(),
routes.client.predictionMoonResult(),
routes.client.myHoroscopeResult(),
routes.client.myHoroscopeResult(),
routes.client.thermalResult(),
routes.client.moonPhaseTracker(),
routes.client.energyVampirismResult(),
routes.client.nameHoroscopeResult(),
];
export const hasNoHeader = (path: string) =>
!withoutHeaderRoutes.includes(path);