Merge branch 'develop' into 'main'

AW-115-lottie-optimization

See merge request witapp/aura-webapp!200
This commit is contained in:
Daniil Chemerkin 2024-06-25 00:09:26 +00:00
commit ca79e53b00
71 changed files with 1429 additions and 1418 deletions

View File

@ -5,4 +5,5 @@ AURA_SITE_HOST=https://aura.wit.life
AURA_PREFIX=api/v1
AURA_OPEN_AI_HOST=https://api.openai.com
AURA_OPEN_AI_PREFIX=v1
AURA_YANDEX_COUNTER_NUMBER=95799066
AURA_YANDEX_COUNTER_NUMBER=95799066
AURA_PERSONAL_VIDEO_TIME_LIMIT=180

View File

@ -5,4 +5,5 @@ AURA_SITE_HOST=https://aura.wit.life
AURA_PREFIX=api/v1
AURA_OPEN_AI_HOST=https://api.openai.com
AURA_OPEN_AI_PREFIX=v1
AURA_YANDEX_COUNTER_NUMBER=95799066
AURA_YANDEX_COUNTER_NUMBER=95799066
AURA_PERSONAL_VIDEO_TIME_LIMIT=120

1516
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
"build:prod": "tsc && vite build --mode production"
},
"dependencies": {
"@dotlottie/react-player": "^1.6.19",
"@lottiefiles/dotlottie-react": "^0.6.4",
"@reduxjs/toolkit": "^1.9.5",
"@smakss/react-scroll-direction": "^4.0.4",
"@stripe/react-stripe-js": "^2.3.1",
@ -24,16 +24,18 @@
"html-react-parser": "^3.0.16",
"i18next": "^22.5.0",
"i18next-react-postprocessor": "^3.1.0",
"lottie-react": "^2.4.0",
"idb": "^8.0.0",
"moment": "^2.30.1",
"react": "^18.2.0",
"react-circular-progressbar": "^2.1.0",
"react-dom": "^18.2.0",
"react-ga4": "^2.1.0",
"react-i18next": "^12.3.1",
"react-player": "^2.16.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.2",
"react-slick": "^0.30.2",
"sass": "^1.77.6",
"slick-carousel": "^1.8.1",
"unique-names-generator": "^4.7.1"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

View File

@ -29,6 +29,7 @@ import {
Palmistry,
Paywall,
Payment,
UserVideos,
} from './resources'
const api = {
@ -80,6 +81,8 @@ const api = {
getPaywallByPlacementKey: createMethod<Paywall.PayloadGet, Paywall.ResponseGet>(Paywall.createRequestGet),
// Payment
makePayment: createMethod<Payment.PayloadPost, Payment.ResponsePost>(Payment.createRequestPost),
// User videos
getUserVideos: createMethod<UserVideos.PayloadGet, UserVideos.ResponseGet>(UserVideos.createRequest),
}
export type ApiContextValue = typeof api

View File

@ -178,6 +178,8 @@ export interface ICreateAuthorizePayload {
export interface ICreateAuthorizeResponse {
token: string;
userId?: string;
generatingVideo?: boolean;
videoId?: string;
}
export const createAuthorizeRequest = (data: ICreateAuthorizePayload): Request => {

View File

@ -0,0 +1,23 @@
import routes from "@/routes";
import { getAuthHeaders } from "../utils";
interface Payload {
token: string;
}
export type PayloadGet = Payload;
export interface IUserVideo {
id: string,
videoUrl: string,
createdAt: string
}
type ResponseGetSuccess = IUserVideo[];
export type ResponseGet = ResponseGetSuccess;
export const createRequest = ({ token }: PayloadGet): Request => {
const url = new URL(routes.server.getUserVideos());
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
};

View File

@ -27,3 +27,4 @@ export * as Products from "./Products";
export * as Palmistry from "./Palmistry";
export * as Paywall from "./Paywall";
export * as Payment from "./Payment";
export * as UserVideos from "./UserVideos";

View File

@ -0,0 +1,26 @@
import './styles.scss';
import {ReactNode} from "react";
import cn from 'classnames';
type BlurComponentProps = {
children: ReactNode;
isActiveBlur?: boolean;
className: string;
}
export default function BlurComponent({children, isActiveBlur, className}: BlurComponentProps) {
return (
<div className={cn('gradient-container', className)}>
{children}
{isActiveBlur && <div className="gradient-blur">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
}
</div>
)
}

View File

@ -0,0 +1,93 @@
.gradient-container {
position: relative;
height: 100%;
width: 100%;
text-align: center;
text-align: -webkit-center;
.gradient-blur {
position: absolute;
z-index: 5;
inset: auto 0 0 0;
left: -100px;
right: -100px;
bottom: -16px;
top: -48px;
height: auto;
pointer-events: none;
& > div,
&::before,
&::after {
position: absolute;
inset: 0;
}
&::before {
content: "";
z-index: 1;
-webkit-backdrop-filter: blur(0.5px);
backdrop-filter: blur(0.5px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, black 12.5%, black 25%, rgba(0, 0, 0, 0) 37.5%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, black 12.5%, black 25%, rgba(0, 0, 0, 0) 37.5%);
}
& > div:nth-of-type(1) {
z-index: 2;
-webkit-backdrop-filter: blur(1px);
backdrop-filter: blur(1px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 12.5%, black 25%, black 37.5%, rgba(0, 0, 0, 0) 50%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 12.5%, black 25%, black 37.5%, rgba(0, 0, 0, 0) 50%);
}
& > div:nth-of-type(2) {
z-index: 3;
-webkit-backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 25%, black 37.5%, black 50%, rgba(0, 0, 0, 0) 62.5%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 25%, black 37.5%, black 50%, rgba(0, 0, 0, 0) 62.5%);
}
& > div:nth-of-type(3) {
z-index: 4;
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 37.5%, black 50%, black 62.5%, rgba(0, 0, 0, 0) 75%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 37.5%, black 50%, black 62.5%, rgba(0, 0, 0, 0) 75%);
}
& > div:nth-of-type(4) {
z-index: 5;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, black 62.5%, black 75%, rgba(0, 0, 0, 0) 87.5%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, black 62.5%, black 75%, rgba(0, 0, 0, 0) 87.5%);
}
& > div:nth-of-type(5) {
z-index: 6;
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 62.5%, black 75%, black 87.5%, rgba(0, 0, 0, 0) 100%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 62.5%, black 75%, black 87.5%, rgba(0, 0, 0, 0) 100%);
}
& > div:nth-of-type(6) {
z-index: 7;
-webkit-backdrop-filter: blur(32px);
backdrop-filter: blur(32px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 75%, black 87.5%, black 100%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 75%, black 87.5%, black 100%);
}
&::after {
content: "";
z-index: 8;
-webkit-backdrop-filter: blur(64px);
backdrop-filter: blur(64px);
-webkit-mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 87.5%, black 100%);
mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 87.5%, black 100%);
}
}
}

View File

@ -15,7 +15,7 @@ import { useAuthentication } from "@/hooks/authentication/use-authentication";
import { ESourceAuthorization } from "@/api/resources/User";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals } from "@/services/metric/metricService";
interface IEmailEnterPage {
redirectUrl?: string;
@ -89,7 +89,7 @@ function EmailEnterPage({
const handleClick = () => {
authorize();
metricService.reachGoal("EnteredEmail");
metricService.reachGoal(EGoals.ENTERED_EMAIL);
};
const authorize = async () => {

View File

@ -7,7 +7,7 @@ import MainButton from "@/components/MainButton";
import { useDispatch } from "react-redux";
import { actions } from "@/store";
import { useEffect } from "react";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals } from "@/services/metric/metricService";
function PaymentSuccessPage(): JSX.Element {
const { t } = useTranslation();
@ -20,7 +20,7 @@ function PaymentSuccessPage(): JSX.Element {
};
useEffect(() => {
metricService.reachGoal("PaymentSuccess");
metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
}, []);
return (

View File

@ -7,6 +7,7 @@ import routes from "@/routes";
import PlacePicker from "@/components/PlacePicker";
import { useState } from "react";
import QuestionnaireGreenButton from "../../../../ui/GreenButton";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
interface IBirthPlaceCustomAnswerProps {
affiliation?: "self" | "partner";
@ -27,6 +28,10 @@ function BirthPlaceCustomAnswer({
const [isKnownPartnerBirthPlace, setIsKnownPartnerBirthPlace] =
useState(true);
useLottie({
preloadKey: ELottieKeys.magnifyingGlassAndPlanet,
});
const handleChange = (birthPlace: string) => {
if (affiliation === "partner") {
return dispatch(

View File

@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import QuestionnaireGreenButton from "../../../../ui/GreenButton";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
interface IBirthdateCustomAnswerProps {
affiliation?: "self" | "partner";
@ -18,6 +19,16 @@ function BirthdateCustomAnswer({
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
useLottie({
preloadKey: ELottieKeys.darts,
});
useLottie({
preloadKey: ELottieKeys.compass,
});
useLottie({
preloadKey: ELottieKeys.cloudAndStars,
});
const selfBirthdate = useSelector(selectors.selectBirthdate);
const questionnaire = useSelector(selectors.selectQuestionnaire);
const birthdateFromStore =

View File

@ -1,12 +1,13 @@
import { IAnswer } from "@/data";
import styles from "./styles.module.css";
import styles from "./styles.module.scss";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { useNavigate } from "react-router-dom";
import { useNavigate, useLocation } from "react-router-dom";
import routes from "@/routes";
import Answer from "../../../../ui/Answer";
import QuestionnaireGreenButton from "../../../../ui/GreenButton";
import BlurComponent from "@/components/BlurComponent";
interface IMultiplyAnswersProps {
answers: IAnswer[];
@ -19,6 +20,7 @@ function MultiplyAnswers({ answers }: IMultiplyAnswersProps) {
const { currentlyAffecting, gender } = useSelector(
selectors.selectQuestionnaire
);
const location = useLocation();
const handleClick = (answer: IAnswer) => {
if (currentlyAffecting.includes(`$${answer.id}`)) {
@ -78,12 +80,18 @@ function MultiplyAnswers({ answers }: IMultiplyAnswersProps) {
))}
</div>
{!!currentlyAffecting.length && (
<QuestionnaireGreenButton
<BlurComponent
className={styles.button}
onClick={handleNext}
isActiveBlur={location.pathname.includes(
"v1/questionnaire/relationships/currentlyAffecting"
)}
>
{t("next")}
</QuestionnaireGreenButton>
<QuestionnaireGreenButton
onClick={handleNext}
>
{t("next")}
</QuestionnaireGreenButton>
</BlurComponent>
)}
</>
);

View File

@ -8,17 +8,21 @@
width: 100%;
}
.button {
position: fixed;
position: fixed !important;
height: unset !important;
bottom: calc(0dvh + 16px);
width: calc(100% - 64px);
width: calc(100% - 64px) !important;
max-width: 396px;
margin-top: 8px;
button {
z-index: 10;
}
}
.answer-active {
animation: pulse .7s ease-in-out;
animation: pulse 0.7s ease-in-out;
}
@keyframes pulse {
@ -31,4 +35,4 @@
100% {
transform: scale(1);
}
}
}

View File

@ -13,6 +13,7 @@ import Header from "../Header";
import { stepsQuestionary } from "../../data/stepsQuestionary";
import Answer from "../../ui/Answer";
import Stepper from "../Stepper";
import { useLottie } from "@/hooks/lottie/useLottie";
function QuestionnairePage(): JSX.Element {
const { question, stepId } = useParams();
@ -28,6 +29,9 @@ function QuestionnairePage(): JSX.Element {
});
const { gender } = useSelector(selectors.selectQuestionnaire);
const clickAnswerTimeOutRef = useRef<NodeJS.Timeout>();
useLottie({
preloadKey: currentQuestion?.lottie?.preloadKey,
});
useEffect(() => {
const currentStepIndex = steps.findIndex((item) => item.id === stepId);
@ -160,7 +164,9 @@ function QuestionnairePage(): JSX.Element {
height={145}
cLeftStartX={65}
cLeft={`C 32 108 11 95 0 87`}
cRight={`C ${pageWidth - 23} 123 ${pageWidth - 36} 115 ${pageWidth - 69} 112`}
cRight={`C ${pageWidth - 23} 123 ${pageWidth - 36} 115 ${
pageWidth - 69
} 112`}
/>
<Header className={styles.header} />
{currentQuestion && (!!currentStep || currentStep === 0) && (

View File

@ -6,6 +6,7 @@ import MultiplyAnswers from "../components/Questionnaire/CustomAnswers/MultipleA
import { currentlyAffectingAnswers } from "./currentlyAffectingAnswers";
import BirthdateCustomAnswer from "../components/Questionnaire/CustomAnswers/Birthdate";
import styles from "./questionary.module.css";
import { ELottieKeys } from "@/hooks/lottie/useLottie";
export const stepsQuestionary: IStep[] = [
{
@ -17,6 +18,9 @@ export const stepsQuestionary: IStep[] = [
id: "flowChoice",
question:
"So we can get to know you better, tell us about your relationship status.",
lottie: {
preloadKey: ELottieKeys.goal,
},
answers: [
{
id: "single",
@ -553,6 +557,9 @@ export const stepsQuestionary: IStep[] = [
question: "Do you agree with the statement below?",
description: "“My partner and I can talk about any issue together“",
backgroundImage: "/lovely_bedroom.png",
lottie: {
preloadKey: ELottieKeys.umbrella,
},
answers: [
{
id: "strongly_agree",
@ -718,6 +725,9 @@ export const stepsQuestionary: IStep[] = [
{
id: "irritated",
question: "Does your partner get angry or irritated easily?",
lottie: {
preloadKey: ELottieKeys.cloudAndStars,
},
answers: [
{
id: "yes",
@ -745,6 +755,9 @@ export const stepsQuestionary: IStep[] = [
id: "conflict",
question:
"Are you satisfied with the way you and your partner deal with conflict?",
lottie: {
preloadKey: ELottieKeys.compass,
},
answers: [
{
id: "yes",
@ -771,6 +784,9 @@ export const stepsQuestionary: IStep[] = [
{
id: "aboutGoals",
question: "When you think about your relationship goals, you feel...?",
lottie: {
preloadKey: ELottieKeys.darts,
},
answers: [
{
id: "optimistic",
@ -802,6 +818,9 @@ export const stepsQuestionary: IStep[] = [
id: "appreciated",
question: "Do you agree with the statement below?",
description: "“My partner makes me feel really appreciated.”",
lottie: {
preloadKey: ELottieKeys.scalesNeutral,
},
answers: [
{
id: "strongly_agree",
@ -1062,6 +1081,9 @@ export const stepsQuestionary: IStep[] = [
id: "futurePartner",
question:
"Would you describe your future partner as detail-oriented or a big-picture person?",
lottie: {
preloadKey: ELottieKeys.cloudAndStars,
},
answers: [
{
id: "detail",
@ -1083,6 +1105,9 @@ export const stepsQuestionary: IStep[] = [
{
id: "idealPartner",
question: "Is your ideal partner an introvert or extrovert?",
lottie: {
preloadKey: ELottieKeys.compass,
},
answers: [
{
id: "introvert",

View File

@ -7,12 +7,18 @@ import { selectors } from "@/store";
import { useSelector } from "react-redux";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import Header from "../../components/Header";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function AllRightPage() {
const navigate = useNavigate();
const { birthdate } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate);
const { animationData } = useLottie({
loadKey: ELottieKeys.lightBulb,
});
const handleBack = () => {
navigate(-1);
};
@ -28,7 +34,14 @@ function AllRightPage() {
classNameTitle={styles["header-title"]}
/>
<div className={styles.circle}>
<img src="/light.svg" alt="light" />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
<div className={styles["circle-small"]} />
</div>
<div>

View File

@ -4,12 +4,16 @@ import MainButton from "@/components/MainButton";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import Header from "../../components/Header";
import LottieAnimation from "@/lotties/v1/magnifyingGlassAndPlanet.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function AlmostTherePage() {
const navigate = useNavigate();
const { animationData } = useLottie({
loadKey: ELottieKeys.magnifyingGlassAndPlanet,
});
const handleBack = () => {
navigate(-1);
};
@ -26,7 +30,12 @@ function AlmostTherePage() {
isBackButtonVisible={false}
classNameTitle={styles["header-title"]}
/>
<Lottie animationData={LottieAnimation} loop={false} />
<div className={`${styles["lottie-animation"]} ym-hide-content`}>
{animationData && (
<DotLottieReact data={animationData} autoplay loop={false} />
)}
</div>
<div></div>
<div>
<Title variant="h1" className={styles.title}>
Almost there! <br /> Now let's tailor your plan by understanding the{" "}

View File

@ -1,11 +1,10 @@
.page {
position: relative;
height: fit-content;
display: grid;
justify-items: center;
grid-template-rows: repeat(4, min-content);
min-height: 100vh;
display: flex;
justify-content: start;
align-items: center;
flex-direction: column;
gap: 40px;
background-position-y: top;
background-position-x: center;
@ -35,6 +34,7 @@
text-align: center;
line-height: 140%;
max-width: 322px;
margin: 0 auto;
}
.gradient {
@ -75,4 +75,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
width: 100%;
aspect-ratio: 401.27 / 353.27;
}

View File

@ -9,8 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import Header from "../../components/Header";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import LottieScalesNeutral from "@/lotties/v1/scales-neutral.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function BothPage() {
const navigate = useNavigate();
@ -18,6 +18,10 @@ function BothPage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.scalesNeutral,
});
const handleBack = () => {
navigate(-1);
};
@ -35,11 +39,14 @@ function BothPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles["image-container"]}>
<Lottie
className={styles["lottie-animation"]}
loop={false}
animationData={LottieScalesNeutral}
/>
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -6,13 +6,17 @@ import Header from "../../components/Header";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import QuestionnaireGreenButton from "../../ui/GreenButton";
import LottieAnimation from "@/lotties/v1/hand-and-stars.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function EmailConfirmPage() {
const navigate = useNavigate();
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.handWithStars,
});
const handleNext = () => {
navigate(routes.client.onboardingV1());
};
@ -26,11 +30,11 @@ function EmailConfirmPage() {
/>
<Header className={styles.header} />
<div className={styles["top-section"]}>
<Lottie
className={styles["lottie-animation"]}
loop={false}
animationData={LottieAnimation}
/>
<div className={`${styles["lottie-animation"]} ym-hide-content`}>
{animationData && (
<DotLottieReact data={animationData} autoplay loop={false} />
)}
</div>
<Title className={styles.title}>
Get access to your{" "}
<span className={styles.gradient}>exclusive reading</span>, special

View File

@ -60,10 +60,9 @@
}
.top-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
display: grid;
justify-items: center;
}
.bottom-section {
@ -81,3 +80,8 @@
line-height: 1.2;
font-size: 12px;
}
.lottie-animation {
width: 100%;
aspect-ratio: 575 / 495;
}

View File

@ -18,7 +18,8 @@ import { ESourceAuthorization } from "@/api/resources/User";
import { useAuthentication } from "@/hooks/authentication/use-authentication";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals } from "@/services/metric/metricService";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
interface IEmailEnterPage {
redirectUrl?: string;
@ -50,6 +51,10 @@ function EmailEnterPage({
activeProductFromStore
);
useLottie({
preloadKey: ELottieKeys.handWithStars,
});
useEffect(() => {
if (subPlan) {
const targetProduct = products.find(
@ -94,7 +99,7 @@ function EmailEnterPage({
const handleClick = () => {
authorize();
metricService.reachGoal("EnteredEmail");
metricService.reachGoal(EGoals.ENTERED_EMAIL);
};
const authorize = async () => {

View File

@ -13,6 +13,7 @@ import Header from "../../components/Header";
import { genders } from "../../data/genders";
import PrivacyPolicy from "../../components/PrivacyPolicy";
import Toast from "../../components/Toast";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
interface IGenderPageProps {
productKey?: EProductKeys;
@ -78,6 +79,13 @@ function GenderPage({ productKey }: IGenderPageProps): JSX.Element {
return (
<section className={`${styles.page} page`} ref={pageRef}>
<DotLottieReact
style={{
visibility: "hidden",
height: 0,
width: 0,
}}
/>
<BackgroundTopBlob
className={styles["background-top-blob"]}
width={pageWidth}

View File

@ -6,13 +6,17 @@ import routes from "@/routes";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import LottieAnimation from "@/lotties/v1/zodiac-signs-map.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function GoalSetupPage() {
const navigate = useNavigate();
const { width: pageWidth, elementRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.goal,
});
const handleBack = () => {
navigate(-1);
};
@ -30,12 +34,14 @@ function GoalSetupPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles["image-container"]}>
<Lottie
animationData={LottieAnimation}
className={styles["lottie-animation"]}
autoplay
loop={false}
/>
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div className={styles["text-container"]}>
<Title variant="h1" className={styles.title}>

View File

@ -5,6 +5,7 @@
display: grid;
grid-template-rows: min-content min-content 1fr min-content;
align-items: start;
justify-items: center;
background: #0f1323;
color: #fff;
padding-top: 36px;
@ -86,8 +87,10 @@
display: flex;
justify-content: center;
align-items: center;
min-height: 220px;
}
.lottie-animation path {
fill: #000;
.lottie-animation {
-webkit-filter: invert(100%);
filter: invert(100%);
}

View File

@ -4,9 +4,16 @@ import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import Header from "../../components/Header";
import QuestionnaireGreenButton from "../../ui/GreenButton";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function HyperPersonalizedAstrologyPage() {
const navigate = useNavigate();
useLottie({
preloadKey: ELottieKeys.lightBulb,
});
useLottie({
preloadKey: ELottieKeys.hourglass,
});
const handleNext = () => {
navigate(`${routes.client.questionnaireV1()}/profile/birthdate`);

View File

@ -11,6 +11,7 @@ import { CircularProgressbar } from "react-circular-progressbar";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function LoadingInRelationshipPage() {
const navigate = useNavigate();
@ -22,6 +23,13 @@ function LoadingInRelationshipPage() {
}, []);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
useLottie({
preloadKey: ELottieKeys.key,
});
useLottie({
preloadKey: ELottieKeys.umbrella,
});
useEffect(() => {
intervalRef.current = setInterval(() => {
if (loadingProgress <= 0) {

View File

@ -115,9 +115,12 @@ function LoadingProfilePage() {
<Title variant="h2" className={styles["point__title"]}>
{title}
</Title>
<p className={styles["point__percentage"]} style={{
color: getProgressValue(index) === 100 ? "#1c1c1c" : "#fff"
}}>
<p
className={styles["point__percentage"]}
style={{
color: getProgressValue(index) === 100 ? "#1c1c1c" : "#fff",
}}
>
{getProgressValue(index)}%
</p>
</div>

View File

@ -6,6 +6,8 @@ import routes from "@/routes";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function NoBirthtimePage() {
const [searchParams] = useSearchParams();
@ -13,6 +15,10 @@ function NoBirthtimePage() {
const navigate = useNavigate();
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.hourglass,
});
const handleNext = () => {
if (affiliation === "partner") {
navigate(
@ -37,7 +43,14 @@ function NoBirthtimePage() {
className={styles["background-top-blob"]}
/>
<div className={styles.ellipse}>
<img src="/hourglass.svg" alt="hourglass" />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -76,4 +76,8 @@
top: 0;
left: 0;
z-index: 0;
}
.lottie-animation {
aspect-ratio: 1 / 1;
}

View File

@ -7,14 +7,18 @@ import { selectors } from "@/store";
import { useSelector } from "react-redux";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import Header from "../../components/Header";
import Lottie from "lottie-react";
import LottieUmbrella from "@/lotties/v1/umbrella.json";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function NotAlonePage() {
const navigate = useNavigate();
const { birthdate } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate);
const { animationData } = useLottie({
loadKey: ELottieKeys.umbrella,
});
const handleBack = () => {
navigate(-1);
};
@ -29,7 +33,11 @@ function NotAlonePage() {
isBackButtonVisible={false}
classNameTitle={styles["header-title"]}
/>
<Lottie loop={false} animationData={LottieUmbrella} />
<div className={`${styles["lottie-animation"]} ym-hide-content`}>
{animationData && (
<DotLottieReact data={animationData} autoplay loop={false} />
)}
</div>
<div>
<Title variant="h1" className={styles.title}>
Youre not alone.

View File

@ -2,10 +2,9 @@
position: relative;
height: fit-content;
min-height: 100vh;
display: flex;
justify-content: start;
align-items: center;
flex-direction: column;
display: grid;
justify-items: center;
grid-template-rows: repeat(4, min-content);
gap: 40px;
background: url("/DeWatermark2.png");
background-position-y: bottom;
@ -66,4 +65,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
width: 100%;
aspect-ratio: 400 / 385;
}

View File

@ -7,6 +7,10 @@ import { onboardingTitles } from "../../data/onboarding";
import ProgressBarLine from "@/components/ui/ProgressBarLine";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys } from "@/api/resources/Paywall";
import { usePersonalVideo } from "@/hooks/personalVideo/usePersonalVideo";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import metricService, { EGoals } from "@/services/metric/metricService";
function OnboardingPage() {
const navigate = useNavigate();
@ -17,21 +21,31 @@ function OnboardingPage() {
const [progress, setProgress] = useState(0);
const progressInterval = useRef<NodeJS.Timeout>();
usePaywall({ placementKey: EPlacementKeys["aura.placement.redesign.main"] });
const { isVideoReady } = usePersonalVideo();
const { createdDate, generatingVideo } = useSelector(
selectors.selectPersonalVideo
);
const handleNext = useCallback(() => {
navigate(routes.client.trialChoiceV1());
}, [navigate]);
useEffect(() => {
if (isVideoReady && progress >= 100) {
handleNext();
}
}, [activeIndexTitle, handleNext, isVideoReady, progress]);
useEffect(() => {
setPeriodClassName("to-nontransparent");
classNameTimeOut.current = setTimeout(() => {
setPeriodClassName("to-transparent");
}, 4000);
if (activeIndexTitle < onboardingTitles.length - 1) {
classNameTimeOut.current = setTimeout(() => {
setPeriodClassName("to-transparent");
}, 4000);
}
titleInterval.current = setTimeout(() => {
if (activeIndexTitle < onboardingTitles.length - 1) {
setIndexTitle((prev) => prev + 1);
} else {
handleNext();
}
}, 5000);
return () => {
@ -40,16 +54,34 @@ function OnboardingPage() {
};
}, [activeIndexTitle, handleNext]);
const getProgressIntervalTiming = useCallback(() => {
const generateTimeLimit = import.meta.env.AURA_PERSONAL_VIDEO_TIME_LIMIT;
const baseTiming = (onboardingTitles.length * 5000) / 100;
const generatingVideoTiming =
(Number(generateTimeLimit) * 1000 - new Date().getTime() + createdDate) /
30;
if (progress < 70 || isVideoReady) return baseTiming;
return generatingVideoTiming < baseTiming
? baseTiming
: generatingVideoTiming;
}, [createdDate, isVideoReady, progress]);
useEffect(() => {
progressInterval.current = setInterval(() => {
setProgress((prev) => {
if (prev === 99 && generatingVideo)
[metricService.reachGoal(EGoals.ROSE_LOADING_END)];
if (prev >= 100) return prev;
return prev + 1;
});
}, (onboardingTitles.length * 5000) / 100);
}, getProgressIntervalTiming());
return () => {
if (progressInterval.current) clearInterval(progressInterval.current);
};
}, [generatingVideo, getProgressIntervalTiming]);
useEffect(() => {
metricService.reachGoal(EGoals.ROSE_LOADING_START);
}, []);
return (

View File

@ -9,6 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function PartnerRightPlacePage() {
const navigate = useNavigate();
@ -16,6 +18,14 @@ function PartnerRightPlacePage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.darts,
preloadKey: ELottieKeys.scalesHead,
});
useLottie({
preloadKey: ELottieKeys.scalesHeart,
});
const handleBack = () => {
navigate(-1);
};
@ -33,7 +43,15 @@ function PartnerRightPlacePage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles.circle}>
<img src="/dartsV1.svg" alt="The darts" />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoResizeCanvas={true}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -51,6 +51,7 @@
text-align: center;
line-height: 140%;
max-width: 322px;
margin: 0 auto;
}
.blue {
@ -83,4 +84,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
height: 100%;
aspect-ratio: 250 / 145;
}

View File

@ -9,8 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import Header from "../../components/Header";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import LottieScalesHeart from "@/lotties/v1/compass.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function PartnerThingPage() {
const navigate = useNavigate();
@ -18,6 +18,14 @@ function PartnerThingPage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.compass,
preloadKey: ELottieKeys.scalesHead,
});
useLottie({
preloadKey: ELottieKeys.scalesHeart,
});
const handleBack = () => {
navigate(-1);
};
@ -35,7 +43,14 @@ function PartnerThingPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles.circle}>
<Lottie loop={false} animationData={LottieScalesHeart} />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -84,4 +84,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
width: 100%;
aspect-ratio: 401 / 505;
}

View File

@ -9,6 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function PartnerTotallyNormalPage() {
const navigate = useNavigate();
@ -16,6 +18,14 @@ function PartnerTotallyNormalPage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.cloudAndStars,
preloadKey: ELottieKeys.scalesHead,
});
useLottie({
preloadKey: ELottieKeys.scalesHeart,
});
const handleBack = () => {
navigate(-1);
};
@ -33,7 +43,14 @@ function PartnerTotallyNormalPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles.circle}>
<img src="/night-cloud.png" alt="Night cloud" />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -83,4 +83,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
height: 100%;
aspect-ratio: 401 / 235;
}

View File

@ -5,10 +5,10 @@ import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import LottieKey from "@/lotties/v1/key.json";
import Header from "../../components/Header";
import { textVariables } from "../../data/textVariables";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function QuestionnaireIntermediatePage() {
const navigate = useNavigate();
@ -21,6 +21,10 @@ function QuestionnaireIntermediatePage() {
? backgroundImages[problems]
: "/couple_holding_hands_1.webp";
const { animationData } = useLottie({
loadKey: ELottieKeys.key,
});
const handleBack = () => {
navigate(-1);
};
@ -45,7 +49,11 @@ function QuestionnaireIntermediatePage() {
isBackButtonVisible={false}
classNameTitle={styles["header-title"]}
/>
<Lottie loop={false} animationData={LottieKey} />
<div className={`${styles["lottie-animation"]} ym-hide-content`}>
{animationData && (
<DotLottieReact data={animationData} autoplay loop={false} />
)}
</div>
<div>
{path && (
<Title variant="h1" className={styles.title}>

View File

@ -1,81 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 197 138" width="197" height="138"
preserveAspectRatio="xMidYMid slice"
style="width: 100%; height: 100%; transform: translate3d(0px, 0px, 0px); content-visibility: visible;">
<defs>
<clipPath id="__lottie_element_298">
<rect width="197" height="138" x="0" y="0"></rect>
</clipPath>
</defs>
<g clip-path="url(#__lottie_element_298)">
<g transform="matrix(1,0,0,1,178.375,80.0739974975586)" opacity="1" style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,9.75,16.25)">
<path fill="rgb(155,171,217)" fill-opacity="1"
d=" M0,-16 C5.247000217437744,-16 9.5,-8.836000442504883 9.5,0 C9.5,8.836999893188477 5.247000217437744,16 0,16 C-5.247000217437744,16 -9.5,8.836999893188477 -9.5,0 C-9.5,-8.836000442504883 -5.247000217437744,-16 0,-16z">
</path>
</g>
<g opacity="1" transform="matrix(1,0,0,1,10.25,40.25)">
<path fill="rgb(155,171,217)" fill-opacity="1"
d=" M-5.428999900817871,-17 C-5.428999900817871,-17 4.427000045776367,-17 4.427000045776367,-17 C4.427000045776367,-17 6,17 6,17 C6,17 -6,12 -6,12 C-6,12 -5.428999900817871,-17 -5.428999900817871,-17z">
</path>
</g>
</g>
<g transform="matrix(1.0000094175338745,0,0,1.0000094175338745,141.95191955566406,21.779918670654297)"
opacity="1" style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,8.640999794006348,8.642000198364258)">
<path fill="rgb(222,227,248)" fill-opacity="1"
d=" M7.629000186920166,-0.7639999985694885 C3.8299999237060547,-0.7639999985694885 0.7639999985694885,-3.8299999237060547 0.7639999985694885,-7.629000186920166 C0.7639999985694885,-8.055999755859375 0.42800000309944153,-8.392000198364258 0.0010000000474974513,-8.392000198364258 C-0.4269999861717224,-8.392000198364258 -0.7639999985694885,-8.055999755859375 -0.7639999985694885,-7.629000186920166 C-0.7639999985694885,-3.8299999237060547 -3.8299999237060547,-0.7639999985694885 -7.630000114440918,-0.7639999985694885 C-8.055999755859375,-0.7639999985694885 -8.390999794006348,-0.42899999022483826 -8.390999794006348,-0.0010000000474974513 C-8.390999794006348,0.4259999990463257 -8.055999755859375,0.7620000243186951 -7.630000114440918,0.7620000243186951 C-3.8299999237060547,0.7620000243186951 -0.7639999985694885,3.8299999237060547 -0.7639999985694885,7.629000186920166 C-0.7639999985694885,8.055999755859375 -0.4269999861717224,8.392000198364258 0.0010000000474974513,8.392000198364258 C0.42800000309944153,8.392000198364258 0.7639999985694885,8.055999755859375 0.7639999985694885,7.629000186920166 C0.7639999985694885,3.8299999237060547 3.8299999237060547,0.7620000243186951 7.629000186920166,0.7620000243186951 C8.057000160217285,0.7620000243186951 8.390999794006348,0.4259999990463257 8.390999794006348,-0.0010000000474974513 C8.390999794006348,-0.42899999022483826 8.057000160217285,-0.7639999985694885 7.629000186920166,-0.7639999985694885z">
</path>
</g>
</g>
<g transform="matrix(0.9999973177909851,0,0,0.9999973177909851,65.25801849365234,0.58502197265625)" opacity="1"
style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,8.121000289916992,8.121999740600586)">
<path fill="rgb(155,171,217)" fill-opacity="1"
d=" M7.15500020980835,-0.7160000205039978 C3.5920000076293945,-0.7160000205039978 0.7160000205039978,-3.5929999351501465 0.7160000205039978,-7.156000137329102 C0.7160000205039978,-7.558000087738037 0.4000000059604645,-7.872000217437744 -0.0010000000474974513,-7.872000217437744 C-0.4009999930858612,-7.872000217437744 -0.7160000205039978,-7.558000087738037 -0.7160000205039978,-7.156000137329102 C-0.7160000205039978,-3.5929999351501465 -3.5920000076293945,-0.7160000205039978 -7.156000137329102,-0.7160000205039978 C-7.556000232696533,-0.7160000205039978 -7.870999813079834,-0.4020000100135803 -7.870999813079834,0 C-7.870999813079834,0.4000000059604645 -7.556000232696533,0.7160000205039978 -7.156000137329102,0.7160000205039978 C-3.5920000076293945,0.7160000205039978 -0.7160000205039978,3.5920000076293945 -0.7160000205039978,7.156000137329102 C-0.7160000205039978,7.557000160217285 -0.4009999930858612,7.872000217437744 -0.0010000000474974513,7.872000217437744 C0.4000000059604645,7.872000217437744 0.7160000205039978,7.557000160217285 0.7160000205039978,7.156000137329102 C0.7160000205039978,3.5920000076293945 3.5920000076293945,0.7160000205039978 7.15500020980835,0.7160000205039978 C7.556000232696533,0.7160000205039978 7.870999813079834,0.4000000059604645 7.870999813079834,0 C7.870999813079834,-0.4020000100135803 7.556000232696533,-0.7160000205039978 7.15500020980835,-0.7160000205039978z">
</path>
</g>
</g>
<g transform="matrix(0.9999902844429016,0,0,0.9999902844429016,16.25016212463379,4.074161529541016)" opacity="1"
style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,16.618000030517578,16.618000030517578)">
<path fill="rgb(222,227,248)" fill-opacity="1"
d=" M14.880999565124512,-1.4880000352859497 C7.46999979019165,-1.4880000352859497 1.4880000352859497,-7.46999979019165 1.4880000352859497,-14.880999565124512 C1.4880000352859497,-15.71399974822998 0.8339999914169312,-16.368000030517578 0.0010000000474974513,-16.368000030517578 C-0.8330000042915344,-16.368000030517578 -1.4880000352859497,-15.71399974822998 -1.4880000352859497,-14.880999565124512 C-1.4880000352859497,-7.46999979019165 -7.46999979019165,-1.4880000352859497 -14.880999565124512,-1.4880000352859497 C-15.71399974822998,-1.4880000352859497 -16.368000030517578,-0.8339999914169312 -16.368000030517578,0 C-16.368000030517578,0.8330000042915344 -15.71399974822998,1.4880000352859497 -14.880999565124512,1.4880000352859497 C-7.46999979019165,1.4880000352859497 -1.4880000352859497,7.46999979019165 -1.4880000352859497,14.880000114440918 C-1.4880000352859497,15.713000297546387 -0.8330000042915344,16.368000030517578 0.0010000000474974513,16.368000030517578 C0.8339999914169312,16.368000030517578 1.4880000352859497,15.713000297546387 1.4880000352859497,14.880000114440918 C1.4880000352859497,7.46999979019165 7.46999979019165,1.4880000352859497 14.880999565124512,1.4880000352859497 C15.71399974822998,1.4880000352859497 16.368000030517578,0.8330000042915344 16.368000030517578,0 C16.368000030517578,-0.8339999914169312 15.71399974822998,-1.4880000352859497 14.880999565124512,-1.4880000352859497z">
</path>
</g>
</g>
<g transform="matrix(1,0,0,1,98.5,69)" opacity="1" style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,0,0)">
<path fill="rgb(155,171,217)" fill-opacity="1"
d=" M60.3129997253418,32.625 C60.3129997253418,32.625 27.75,32.9379997253418 27.75,32.9379997253418 C27.75,32.9379997253418 27.575000762939453,40.25400161743164 28,45.25 C28.25,48.1879997253418 29.5,48.625 30.812999725341797,49.9379997253418 C32.125999450683594,51.250999450683594 34.6879997253418,51.75 34.6879997253418,51.75 C34.6879997253418,51.75 34.53099822998047,42.90700149536133 43.625,42.9379997253418 C53,42.970001220703125 53.0629997253418,52.0629997253418 53.0629997253418,52.0629997253418 C53.0629997253418,52.0629997253418 57.3129997253418,50.0629997253418 58.3129997253418,48.6879997253418 C59.3129997253418,47.3129997253418 60.125,46.125 60.125,44.75 C60.125,43.8120002746582 60.3129997253418,32.625 60.3129997253418,32.625z">
</path>
<path stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0" stroke-miterlimit="4"
stroke="rgb(155,171,217)" stroke-opacity="1" stroke-width="0"
d=" M60.3129997253418,32.625 C60.3129997253418,32.625 27.75,32.9379997253418 27.75,32.9379997253418 C27.75,32.9379997253418 27.575000762939453,40.25400161743164 28,45.25 C28.25,48.1879997253418 29.5,48.625 30.812999725341797,49.9379997253418 C32.125999450683594,51.250999450683594 34.6879997253418,51.75 34.6879997253418,51.75 C34.6879997253418,51.75 34.53099822998047,42.90700149536133 43.625,42.9379997253418 C53,42.970001220703125 53.0629997253418,52.0629997253418 53.0629997253418,52.0629997253418 C53.0629997253418,52.0629997253418 57.3129997253418,50.0629997253418 58.3129997253418,48.6879997253418 C59.3129997253418,47.3129997253418 60.125,46.125 60.125,44.75 C60.125,43.8120002746582 60.3129997253418,32.625 60.3129997253418,32.625z">
</path>
</g>
</g>
<g transform="matrix(1,0,0,1,99.25,69)" opacity="1" style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,0,0)">
<path stroke-linecap="round" stroke-linejoin="miter" fill-opacity="0" stroke-miterlimit="4"
stroke="rgb(222,228,248)" stroke-opacity="1" stroke-width="9"
d=" M-47.5,28.75 C-47.5,28.75 66.25,28.5 66.25,28.5"></path>
</g>
</g>
<g transform="matrix(1,0,0,0.8412700295448303,98.5,73.54364776611328)" opacity="1" style="display: block;">
<g opacity="1" transform="matrix(1,0,0,1,0,0)">
<path stroke-linecap="round" stroke-linejoin="miter" fill-opacity="0" stroke-miterlimit="4"
stroke="rgb(155,171,217)" stroke-opacity="1" stroke-width="8"
d=" M-12.25,20.75 C-12.25,20.75 -12.25,36.5 -12.25,36.5"></path>
</g>
</g>
<g transform="matrix(1,0,0,1,98.5,69)" opacity="1" style="display: block;">
<g opacity="1" transform="matrix(0.8959500193595886,0,0,0.8725699782371521,-71.5,28.5)">
<path stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0" stroke-miterlimit="4"
stroke="rgb(222,228,248)" stroke-opacity="1" stroke-width="8"
d=" M0,-32.75 C14.34939956665039,-32.75 26,-18.074724197387695 26,0 C26,18.074724197387695 14.34939956665039,32.75 0,32.75 C-14.34939956665039,32.75 -26,18.074724197387695 -26,0 C-26,-18.074724197387695 -14.34939956665039,-32.75 0,-32.75z">
</path>
</g>
</g>
<g transform="matrix(1,0,0,1,98.5,69)" opacity="1" style="display: block;"></g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,19 +0,0 @@
<svg width="393" height="245" viewBox="0 0 393 245" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3117_7398)">
<path d="M204.775 101.66L204.76 88.036L183.843 88.065L183.865 101.689L161.989 101.719C164.914 96.9031 166.642 91.4559 167.028 85.8342C167.413 80.2125 166.444 74.5803 164.203 69.4104C161.962 64.2404 158.513 59.6833 154.147 56.1216C149.78 52.5598 144.623 50.0971 139.108 48.9402C133.593 47.7834 127.882 47.9661 122.452 49.473C117.022 50.9798 112.033 53.7669 107.903 57.6004C103.773 61.4339 100.623 66.2018 98.7167 71.5045C96.8103 76.8071 96.2037 82.4897 96.9473 88.0753C93.0076 87.711 89.0353 88.1797 85.2886 89.451C81.5419 90.7223 78.1047 92.7677 75.2002 95.4544C72.2958 98.141 69.9891 101.409 68.4301 105.045C66.8712 108.682 66.0949 112.605 66.1516 116.561C66.2083 120.518 67.0967 124.417 68.7592 128.008C70.4217 131.598 72.8211 134.798 75.8013 137.4C78.7816 140.003 82.2761 141.949 86.0577 143.112C89.8393 144.276 93.8234 144.63 97.751 144.153C97.3756 149.674 98.3058 155.205 100.466 160.299C102.627 165.394 105.957 169.907 110.187 173.475C114.416 177.042 119.427 179.563 124.813 180.834C130.198 182.104 135.807 182.088 141.186 180.786C146.564 179.485 151.56 176.935 155.769 173.344C159.978 169.752 163.283 165.219 165.414 160.113C167.545 155.006 168.443 149.47 168.036 143.951C167.629 138.433 165.928 133.088 163.07 128.35L183.899 128.321L183.912 139.843L204.816 139.801L204.8 128.276L260.052 128.199L260.091 158.536L270.505 158.521C270.123 156.912 270.111 155.237 270.468 153.622C270.825 152.008 271.543 150.494 272.568 149.196C273.592 147.898 274.898 146.848 276.386 146.126C277.873 145.404 279.505 145.027 281.159 145.025C282.813 145.022 284.446 145.394 285.936 146.113C287.426 146.831 288.734 147.877 289.763 149.172C290.791 150.468 291.513 151.979 291.875 153.593C292.237 155.207 292.228 156.881 291.851 158.492L304.342 158.474L304.3 128.134L319.116 128.117L319.079 101.488L204.775 101.66ZM119.684 71.3241C122.085 68.9164 125.146 67.2748 128.48 66.6068C131.813 65.9389 135.271 66.2746 138.414 67.5715C141.557 68.8683 144.245 71.068 146.138 73.8925C148.031 76.717 149.044 80.0394 149.048 83.4396C149.053 86.8398 148.05 90.1649 146.164 92.9947C144.279 95.8244 141.597 98.0315 138.458 99.3371C135.318 100.643 131.862 100.988 128.526 100.329C125.191 99.6705 122.125 98.0374 119.718 95.6365C118.12 94.0423 116.852 92.1491 115.986 90.0648C115.119 87.9806 114.672 85.7461 114.67 83.4891C114.667 81.232 115.109 78.9966 115.971 76.9104C116.832 74.8242 118.096 72.9282 119.69 71.3304L119.684 71.3241ZM148.582 114.493L148.582 114.942C148.434 114.869 148.282 114.806 148.146 114.747L148.582 114.493ZM84.586 106.443C86.5071 104.516 88.9568 103.201 91.6252 102.666C94.2936 102.13 97.0609 102.398 99.577 103.436C102.093 104.473 104.245 106.233 105.761 108.494C107.276 110.754 108.087 113.413 108.092 116.135C108.096 118.856 107.293 121.518 105.784 123.783C104.276 126.048 102.129 127.815 99.6164 128.861C97.1036 129.906 94.3372 130.182 91.6672 129.655C88.9971 129.128 86.5433 127.821 84.6162 125.899C82.0356 123.322 80.5835 119.826 80.579 116.179C80.5746 112.533 82.018 109.033 84.5924 106.45L84.586 106.443ZM145.064 158.636C142.663 161.043 139.602 162.685 136.269 163.353C132.935 164.021 129.477 163.685 126.334 162.388C123.191 161.092 120.503 158.892 118.61 156.067C116.717 153.243 115.704 149.92 115.7 146.52C115.695 143.12 116.699 139.795 118.584 136.965C120.469 134.135 123.151 131.928 126.29 130.623C129.43 129.317 132.886 128.972 136.222 129.631C139.557 130.289 142.623 131.922 145.031 134.323C146.63 135.917 147.899 137.811 148.766 139.895C149.633 141.98 150.081 144.215 150.085 146.473C150.088 148.73 149.646 150.967 148.784 153.054C147.923 155.141 146.659 157.038 145.064 158.636Z" fill="#CDE3EC" fill-opacity="0.5"/>
<path d="M303.5 117L278.5 132.5M139 173L167 117H74L138 64" stroke="#696969" stroke-width="2"/>
<path d="M74 106L72.5683 111.727C72.2889 112.844 72.1494 113.403 71.8584 113.857C71.6011 114.26 71.2595 114.601 70.8574 114.858C70.4028 115.149 69.844 115.289 68.7266 115.568L63 117L68.7266 118.432C69.844 118.711 70.4028 118.851 70.8574 119.142C71.2595 119.399 71.6011 119.74 71.8584 120.143C72.1494 120.597 72.2889 121.156 72.5683 122.273L74 128L75.4317 122.273C75.7111 121.156 75.8506 120.597 76.1416 120.143C76.3989 119.74 76.7404 119.399 77.1426 119.142C77.5972 118.851 78.156 118.711 79.2734 118.432L85 117L79.2734 115.568C78.156 115.289 77.5972 115.149 77.1426 114.858C76.7404 114.601 76.3989 114.26 76.1416 113.857C75.8506 113.403 75.7111 112.844 75.4317 111.727L74 106Z" fill="white"/>
<path d="M138 54L136.959 58.1648C136.756 58.9775 136.654 59.3838 136.442 59.7145C136.255 60.0069 136.007 60.2553 135.714 60.4425C135.384 60.6541 134.977 60.7556 134.165 60.9588L130 62L134.165 63.0412C134.977 63.2444 135.384 63.3459 135.714 63.5575C136.007 63.7446 136.255 63.993 136.442 64.2855C136.654 64.6162 136.756 65.0226 136.959 65.8352L138 70L139.041 65.8352C139.244 65.0226 139.346 64.6162 139.558 64.2855C139.745 63.993 139.993 63.7446 140.286 63.5575C140.616 63.3459 141.023 63.2444 141.835 63.0412L146 62L141.835 60.9588C141.023 60.7556 140.616 60.6541 140.286 60.4425C139.993 60.2553 139.745 60.0069 139.558 59.7145C139.346 59.3838 139.244 58.9775 139.041 58.1648L138 54Z" fill="white"/>
<path d="M166 104L164.308 110.768C163.978 112.088 163.813 112.749 163.469 113.286C163.165 113.761 162.761 114.165 162.286 114.469C161.749 114.813 161.088 114.978 159.768 115.308L153 117L159.768 118.692C161.088 119.022 161.749 119.187 162.286 119.531C162.761 119.835 163.165 120.239 163.469 120.714C163.813 121.251 163.978 121.912 164.308 123.232L166 130L167.692 123.232C168.022 121.912 168.187 121.251 168.531 120.714C168.835 120.239 169.239 119.835 169.714 119.531C170.251 119.187 170.912 119.022 172.232 118.692L179 117L172.232 115.308C170.912 114.978 170.251 114.813 169.714 114.469C169.239 114.165 168.835 113.761 168.531 113.286C168.187 112.749 168.022 112.088 167.692 110.768L166 104Z" fill="white"/>
<path d="M303.5 105L302.003 110.987C301.711 112.155 301.565 112.739 301.261 113.215C300.992 113.635 300.635 113.992 300.215 114.261C299.739 114.565 299.155 114.711 297.987 115.003L292 116.5L297.987 117.997C299.155 118.289 299.739 118.435 300.215 118.739C300.635 119.008 300.992 119.365 301.261 119.785C301.565 120.261 301.711 120.845 302.003 122.013L303.5 128L304.997 122.013C305.289 120.845 305.435 120.261 305.739 119.785C306.008 119.365 306.365 119.008 306.785 118.739C307.261 118.435 307.845 118.289 309.013 117.997L315 116.5L309.013 115.003C307.845 114.711 307.261 114.565 306.785 114.261C306.365 113.992 306.008 113.635 305.739 113.215C305.435 112.739 305.289 112.155 304.997 110.987L303.5 105Z" fill="white"/>
<path d="M136.5 165L135.264 169.946C135.022 170.911 134.902 171.393 134.65 171.786C134.428 172.133 134.133 172.428 133.786 172.65C133.393 172.902 132.911 173.022 131.946 173.264L127 174.5L131.946 175.736C132.911 175.978 133.393 176.098 133.786 176.35C134.133 176.572 134.428 176.867 134.65 177.214C134.902 177.607 135.022 178.089 135.264 179.054L136.5 184L137.736 179.054C137.978 178.089 138.098 177.607 138.35 177.214C138.572 176.867 138.867 176.572 139.214 176.35C139.607 176.098 140.089 175.978 141.054 175.736L146 174.5L141.054 173.264C140.089 173.022 139.607 172.902 139.214 172.65C138.867 172.428 138.572 172.133 138.35 171.786C138.098 171.393 137.978 170.911 137.736 169.946L136.5 165Z" fill="white"/>
<path d="M280 123L278.959 127.165C278.756 127.977 278.654 128.384 278.442 128.714C278.255 129.007 278.007 129.255 277.714 129.442C277.384 129.654 276.977 129.756 276.165 129.959L272 131L276.165 132.041C276.977 132.244 277.384 132.346 277.714 132.558C278.007 132.745 278.255 132.993 278.442 133.286C278.654 133.616 278.756 134.023 278.959 134.835L280 139L281.041 134.835C281.244 134.023 281.346 133.616 281.558 133.286C281.745 132.993 281.993 132.745 282.286 132.558C282.616 132.346 283.023 132.244 283.835 132.041L288 131L283.835 129.959C283.023 129.756 282.616 129.654 282.286 129.442C281.993 129.255 281.745 129.007 281.558 128.714C281.346 128.384 281.244 127.977 281.041 127.165L280 123Z" fill="white"/>
<path d="M196 61.5L195.089 65.1442C194.911 65.8553 194.822 66.2108 194.637 66.5002C194.473 66.7561 194.256 66.9734 194 67.1372C193.711 67.3223 193.355 67.4111 192.644 67.5889L189 68.5L192.644 69.4111C193.355 69.5889 193.711 69.6777 194 69.8628C194.256 70.0266 194.473 70.2439 194.637 70.4998C194.822 70.7891 194.911 71.1447 195.089 71.8558L196 75.5L196.911 71.8558C197.089 71.1447 197.178 70.7891 197.363 70.4998C197.527 70.2439 197.744 70.0266 198 69.8628C198.289 69.6777 198.645 69.5889 199.356 69.4111L203 68.5L199.356 67.5889C198.645 67.4111 198.289 67.3223 198 67.1372C197.744 66.9734 197.527 66.7561 197.363 66.5002C197.178 66.2108 197.089 65.8553 196.911 65.1442L196 61.5Z" fill="white"/>
<path d="M217.5 149L216.264 153.685C216.022 154.6 215.902 155.057 215.65 155.429C215.428 155.758 215.133 156.037 214.786 156.248C214.393 156.486 213.911 156.6 212.946 156.829L208 158L212.946 159.171C213.911 159.4 214.393 159.514 214.786 159.752C215.133 159.963 215.428 160.242 215.65 160.571C215.902 160.943 216.022 161.4 216.264 162.315L217.5 167L218.736 162.315C218.978 161.4 219.098 160.943 219.35 160.571C219.572 160.242 219.867 159.963 220.214 159.752C220.607 159.514 221.089 159.4 222.054 159.171L227 158L222.054 156.829C221.089 156.6 220.607 156.486 220.214 156.248C219.867 156.037 219.572 155.758 219.35 155.429C219.098 155.057 218.978 154.6 218.736 153.685L217.5 149Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_3117_7398">
<rect width="406" height="245" fill="white" transform="translate(-6)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -2,10 +2,9 @@
position: relative;
height: fit-content;
min-height: 100vh;
display: flex;
justify-content: start;
align-items: center;
flex-direction: column;
display: grid;
justify-items: center;
grid-template-rows: repeat(4, min-content);
gap: 40px;
background: url("/couple_holding_hands_1.webp");
background-position-y: bottom;
@ -67,3 +66,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
width: 100%;
aspect-ratio: 401 / 242;
}

View File

@ -4,12 +4,17 @@ import MainButton from "@/components/MainButton";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import Header from "../../components/Header";
import LottieAnimation from "@/lotties/v1/magnifyingGlassAndPlanet.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function RelationshipAlmostTherePage() {
const navigate = useNavigate();
const { animationData } = useLottie({
loadKey: ELottieKeys.magnifyingGlassAndPlanet,
preloadKey: ELottieKeys.sun,
});
const handleBack = () => {
navigate(-1);
};
@ -24,7 +29,11 @@ function RelationshipAlmostTherePage() {
classNameTitle={styles["header-title"]}
isBackButtonVisible={false}
/>
<Lottie animationData={LottieAnimation} loop={false} />
<div className={`${styles["lottie-animation"]} ym-hide-content`}>
{animationData && (
<DotLottieReact data={animationData} autoplay loop={false} />
)}
</div>
<div>
<Title variant="h1" className={styles.title}>
<strong>Almost there!</strong> <br /> Now let's begin tailoring your

View File

@ -1,11 +1,9 @@
.page {
position: relative;
display: grid;
grid-template-rows: repeat(4, min-content);
height: fit-content;
min-height: 100vh;
display: flex;
justify-content: start;
align-items: center;
flex-direction: column;
background: url("/relationship-almost-there.png");
background-position: center;
background-size: cover;
@ -68,3 +66,8 @@
background-color: #fff;
color: #0F1323;
}
.lottie-animation {
width: 100%;
aspect-ratio: 575 / 506;
}

View File

@ -7,9 +7,8 @@ import { selectors } from "@/store";
import { useSelector } from "react-redux";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import Header from "../../components/Header";
import LottieSun from "@/lotties/v1/sun.json";
import LottieUmbrella from "@/lotties/v1/umbrella.json";
import Lottie from "lottie-react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
function Satisfied() {
const navigate = useNavigate();
@ -21,6 +20,14 @@ function Satisfied() {
getZodiacSignByDate(partnerBirthdate),
];
const { animationData: animationSun } = useLottie({
loadKey: satisfied === "yes" ? ELottieKeys.sun : undefined,
});
const { animationData: animationUmbrella } = useLottie({
loadKey: satisfied === "no" ? ELottieKeys.umbrella : undefined,
});
const handleBack = () => {
navigate(-1);
};
@ -42,9 +49,39 @@ function Satisfied() {
classNameTitle={styles["header-title"]}
isBackButtonVisible={false}
/>
{satisfied === "yes" && <Lottie loop={false} animationData={LottieSun} />}
{satisfied === "yes" && (
<div
style={{
width: "100%",
aspectRatio: "337 / 406",
}}
>
{animationSun && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationSun}
autoplay
loop={false}
/>
)}
</div>
)}
{satisfied === "no" && (
<Lottie loop={false} animationData={LottieUmbrella} />
<div
style={{
width: "100%",
aspectRatio: "310 / 300",
}}
>
{animationUmbrella && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationUmbrella}
autoplay
loop={false}
/>
)}
</div>
)}
<div>
{satisfied === "yes" && (

View File

@ -0,0 +1,84 @@
import React, { useState } from "react";
import ReactPlayer from "react-player";
import styles from "./styles.module.css";
import Loader from "@/components/Loader";
import PlayButton from "../../../../ui/PlayButton";
import metricService, { EGoals } from "@/services/metric/metricService";
import PlayPauseButton from "../../../../ui/PlayPauseButton";
interface IPersonalVideoProps {
gender: string;
url: string;
}
const PersonalVideo = React.memo<IPersonalVideoProps>(({ url, gender }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [isStarted, setIsStarted] = useState(false);
const [isError, setIsError] = useState(false);
const onError = (error: unknown) => {
if (!error) return;
setIsError(true);
setIsPlaying(false);
};
const onStart = () => {
setIsStarted(true);
metricService.reachGoal(EGoals.ROSE_VIDEO_PLAY_START);
};
const onEnded = () => {
metricService.reachGoal(EGoals.ROSE_VIDEO_PLAY_END);
};
const handlePlayPause = () => {
setIsPlaying((prev) => !prev);
if (isPlaying) metricService.reachGoal(EGoals.ROSE_VIDEO_PLAY_USER_PLAY);
if (!isPlaying) metricService.reachGoal(EGoals.ROSE_VIDEO_PLAY_USER_STOP);
};
return (
<div className={`${styles.container} ym-hide-content`}>
{!isPlaying && !isError && !isStarted && (
<Loader className={styles.loader} />
)}
{isError && (
<PlayButton
backgroundFill={gender === "male" ? "#85B6FF" : "#D1ACF2"}
className={styles["play-button"]}
onClick={() => {
setIsError(false);
setIsPlaying(true);
}}
/>
)}
<ReactPlayer
url={url}
controls={false}
light={false}
muted={false}
playing={isPlaying}
stopOnUnmount={true}
width="100%"
onStart={onStart}
onReady={() => setIsPlaying(true)}
onEnded={onEnded}
onError={onError}
playsinline={true}
height={"auto"}
style={{
aspectRatio: "16 / 9",
}}
/>
{!isError && isStarted && (
<PlayPauseButton
state={isPlaying ? "pause" : "play"}
onClick={handlePlayPause}
className={styles["play-pause-button"]}
/>
)}
</div>
);
});
export default PersonalVideo;

View File

@ -0,0 +1,27 @@
.container {
width: 100%;
margin: 24px 0 16px;
position: relative;
overflow: hidden;
border-radius: 10px;
background-image: url("/personal_video_preview.webp");
background-size: contain;
background-position: 0 0;
background-repeat: no-repeat;
}
.play-button, .loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.play-pause-button {
position: absolute;
bottom: 10px;
left: 8px;
width: 34px;
height: auto;
z-index: 10;
}

View File

@ -24,6 +24,7 @@ import { useDynamicSize } from "@/hooks/useDynamicSize";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import PaymentModal from "@/components/PaymentModal";
import PersonalVideo from "./components/PersonalVideo";
function TrialPaymentPage() {
const dispatch = useDispatch();
@ -53,6 +54,7 @@ function TrialPaymentPage() {
"single" | "partner"
>("single");
const { subPlan } = useParams();
const { videoUrl } = useSelector(selectors.selectPersonalVideo);
useEffect(() => {
if (subPlan) {
@ -131,6 +133,7 @@ function TrialPaymentPage() {
/>
<Header className={styles.header} />
<TrialPaymentHeader buttonClick={openStripeModal} />
{!!videoUrl.length && <PersonalVideo gender={gender} url={videoUrl} />}
{singleOrWithPartner === "partner" && (
<WithPartnerInformation
zodiacSign={zodiacSign}

View File

@ -9,8 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import LottieScalesWithHead from "@/lotties/v1/scales-head.json";
import Lottie from "lottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function WithHeadPage() {
const navigate = useNavigate();
@ -18,6 +18,10 @@ function WithHeadPage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.scalesHead,
});
const handleBack = () => {
navigate(-1);
};
@ -35,11 +39,14 @@ function WithHeadPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles["image-container"]}>
<Lottie
className={styles["lottie-animation"]}
loop={false}
animationData={LottieScalesWithHead}
/>
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -9,8 +9,8 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useDynamicSize } from "@/hooks/useDynamicSize";
import BackgroundTopBlob from "../../ui/BackgroundTopBlob";
import Header from "../../components/Header";
import LottieScalesHeart from "@/lotties/v1/scales-heart.json";
import Lottie from "lottie-react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
function WithHeartPage() {
const navigate = useNavigate();
@ -18,6 +18,10 @@ function WithHeartPage() {
const zodiacSign = getZodiacSignByDate(birthdate);
const { width: pageWidth, elementRef: pageRef } = useDynamicSize({});
const { animationData } = useLottie({
loadKey: ELottieKeys.scalesHeart,
});
const handleBack = () => {
navigate(-1);
};
@ -35,7 +39,14 @@ function WithHeartPage() {
/>
<Header isBackButtonVisible={false} />
<div className={styles["image-container"]}>
<Lottie className={styles["lottie-animation"]} loop={false} animationData={LottieScalesHeart} />
{animationData && (
<DotLottieReact
className={`${styles["lottie-animation"]} ym-hide-content`}
data={animationData}
autoplay
loop={false}
/>
)}
</div>
<div>
<Title variant="h1" className={styles.title}>

View File

@ -5,7 +5,6 @@
width: 100%;
background: linear-gradient(90deg, #3a716d31 9%, #3c3c3c31 72%, #21888931 96%),
linear-gradient(90deg, #05d4b6 23%, #22908e 74%, #0cc3ac 94%);
max-width: 330px;
border-radius: 8px;
font-size: 29px;
line-height: 125%;

View File

@ -0,0 +1,52 @@
import { SVGProps } from "react";
import styles from "./styles.module.css";
interface IPlayButtonProps {
backgroundFill?: string;
}
function PlayButton({
backgroundFill = "#32BEA6",
className = "",
...props
}: IPlayButtonProps & SVGProps<SVGSVGElement>) {
return (
<>
<svg
height="48px"
width="48px"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="-4.96 -4.96 506.08 506.08"
xmlSpace="preserve"
fill="#000000"
stroke="#000000"
strokeWidth="7.938528000000001"
className={`${styles.svg} ${className}`}
{...props}
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g
id="SVGRepo_tracerCarrier"
strokeLinecap="round"
strokeLinejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
{" "}
<path
style={{ fill: backgroundFill }}
d="M496.158,248.085c0-137.021-111.07-248.082-248.076-248.082C111.07,0.002,0,111.062,0,248.085 c0,137.002,111.07,248.071,248.083,248.071C385.088,496.155,496.158,385.086,496.158,248.085z"
></path>{" "}
<path
style={{ fill: "#FFFFFF" }}
d="M370.805,235.242L195.856,127.818c-4.776-2.934-11.061-3.061-15.951-0.322 c-4.979,2.785-8.071,8.059-8.071,13.762v214c0,5.693,3.083,10.963,8.046,13.752c2.353,1.32,5.024,2.02,7.725,2.02 c2.897,0,5.734-0.797,8.205-2.303l174.947-106.576c4.657-2.836,7.556-7.986,7.565-13.44 C378.332,243.258,375.452,238.096,370.805,235.242z"
></path>{" "}
</g>
</svg>
</>
);
}
export default PlayButton;

View File

@ -0,0 +1,4 @@
.svg {
cursor: pointer;
z-index: 10;
}

View File

@ -0,0 +1,64 @@
import { SVGProps } from "react";
interface IPlayPauseButtonProps {
state?: "play" | "pause";
}
function PlayPauseButton({
state = "play",
...props
}: IPlayPauseButtonProps & SVGProps<SVGSVGElement>) {
return (
<svg
width="71"
height="50"
viewBox="0 0 71 50"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g filter="url(#filter0_b_201_4)">
<rect width="71" height="50" rx="8" fill="#4D4D4D" fillOpacity="0.88" />
</g>
{state === "play" && (
<path
d="M43.5 24.134C44.1667 24.5189 44.1667 25.4811 43.5 25.866L31.5 32.7942C30.8333 33.1791 30 32.698 30 31.9282L30 18.0718C30 17.302 30.8333 16.8209 31.5 17.2058L43.5 24.134Z"
fill="#DADADA"
/>
)}
{state === "pause" && (
<>
<rect x="27" y="17" width="5" height="16" rx="2" fill="#D9D9D9" />
<rect x="39" y="17" width="5" height="16" rx="2" fill="#D9D9D9" />
</>
)}
<defs>
<filter
id="filter0_b_201_4"
x="-6.7"
y="-6.7"
width="84.4"
height="63.4"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="3.35" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_201_4"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_201_4"
result="shape"
/>
</filter>
</defs>
</svg>
);
}
export default PlayPauseButton;

View File

@ -5,7 +5,7 @@ import MainButton from "@/components/MainButton";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { useEffect } from "react";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals } from "@/services/metric/metricService";
function SuccessPaymentPage(): JSX.Element {
const { t } = useTranslation();
@ -16,7 +16,7 @@ function SuccessPaymentPage(): JSX.Element {
: "The information has been sent to your email";
useEffect(() => {
metricService.reachGoal("PaymentSuccess");
metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
}, []);
return (

View File

@ -9,7 +9,7 @@ import Title from "@/components/Title";
import Loader, { LoaderColor } from "@/components/Loader";
import { useAuthentication } from "@/hooks/authentication/use-authentication";
import { ESourceAuthorization } from "@/api/resources/User";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals } from "@/services/metric/metricService";
const emailRegex = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
@ -44,7 +44,7 @@ export default function StepEmail() {
const authorize = async () => {
await authorization(email, ESourceAuthorization["aura.palmistry"]);
metricService.reachGoal("EnteredEmail");
metricService.reachGoal(EGoals.ENTERED_EMAIL);
setIsAuth(true);
};

View File

@ -6,6 +6,7 @@ import BirthPlaceCustomAnswer from "./components/pages/Questionnaire/CustomAnswe
import WorksForUsDescription from "./components/pages/QuestionnaireIntermediate/WorksForUsDescription";
import WorksTraitsDescription from "./components/pages/QuestionnaireIntermediate/WorksTraitsDescription";
import MultiplyAnswers from "./components/pages/Questionnaire/CustomAnswers/MultipleAnswers";
import { ELottieKeys } from "./hooks/lottie/useLottie";
export const predictionMoonsPeriods: IPredictionMoon[] = [
{
@ -61,6 +62,10 @@ export interface IQuestion {
description?: string;
backgroundImage?: string;
backgroundColor?: string;
lottie?: {
preloadKey?: ELottieKeys;
loadKey?: ELottieKeys;
};
textColor?: {
title?: "#fff";
description?: "#fff";

3
src/env.d.ts vendored
View File

@ -6,5 +6,6 @@ interface ImportMetaEnv {
AURA_PREFIX: string,
AURA_OPEN_AI_HOST: string,
AURA_OPEN_AI_PREFIX: string,
AURA_YANDEX_COUNTER_NUMBER: string
AURA_YANDEX_COUNTER_NUMBER: string,
AURA_PERSONAL_VIDEO_TIME_LIMIT: string
}

View File

@ -4,6 +4,7 @@ import { useAuth } from "@/auth";
import { getClientTimezone } from "@/locales";
import { getDateAsString } from "@/services/date";
import { filterNullKeysOfObject } from "@/services/filter-object";
import metricService, { EGoals } from "@/services/metric/metricService";
import { actions, selectors } from "@/store";
import moment from "moment";
import { useCallback, useMemo, useState } from "react";
@ -120,10 +121,11 @@ export const useAuthentication = () => {
setIsLoading(true);
setError(null)
const payload = getAuthorizationPayload(email, source);
const { token, userId } = await api.authorization(payload);
const { token, userId, generatingVideo, videoId } = await api.authorization(payload);
const { user } = await api.getUser({ token });
if (userId?.length && !!window.ym && typeof window.ym === 'function') {
window.ym(95799066, 'userParams', {
hasPersonalVideo: generatingVideo || false,
email: user.email,
UserID: userId
})
@ -131,6 +133,11 @@ export const useAuthentication = () => {
}
signUp(token, user);
setToken(token);
dispatch(actions.personalVideo.updateStatus({ generatingVideo: generatingVideo || false, videoId: videoId || "" }));
if (generatingVideo) {
metricService.reachGoal(EGoals.ROSE_VIDEO_CREATION_START)
}
dispatch(actions.status.update("registred"));
} catch (error) {
setError((error as Error).message);

View File

@ -0,0 +1,92 @@
import { Data } from "@lottiefiles/dotlottie-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import indexedDB, { EObjectStores } from "@/services/indexedDB";
export enum ELottieKeys {
goal = "goal",
magnifyingGlassAndPlanet = "magnifyingGlassAndPlanet",
scalesNeutral = "scalesNeutral",
scalesHead = "scalesHead",
scalesHeart = "scalesHeart",
compass = "compass",
handWithStars = "handWithStars",
key = "key",
cloudAndStars = "cloudAndStars",
darts = "darts",
umbrella = "umbrella",
hourglass = "hourglass",
lightBulb = "lightBulb",
sun = "sun",
}
export const lottieUrls = {
[ELottieKeys.goal]: "https://lottie.host/a86e1531-7028-4688-a836-ea9d71dafa3b/Pe5G1g9s9L.lottie",
[ELottieKeys.magnifyingGlassAndPlanet]: "https://lottie.host/7e1e77e6-46a3-4122-8584-7555539d1b15/ASrDx8yoMC.lottie",
[ELottieKeys.scalesNeutral]: "https://lottie.host/ddd2cb46-d62f-4808-a10d-1dd5ce8d42d2/6hgUBBGjaJ.lottie",
[ELottieKeys.scalesHead]: "https://lottie.host/19fe41d7-d26f-431c-b063-8e123ce3d57a/HiucMMidQT.lottie",
[ELottieKeys.scalesHeart]: "https://lottie.host/9eb3f7a1-83c2-495a-9342-c234bfebc40c/0T90l2xSWl.lottie",
[ELottieKeys.compass]: "https://lottie.host/15b235d7-b8c9-487f-8d65-73143afc9ecc/czTjX9Lwp1.lottie",
[ELottieKeys.handWithStars]: "https://lottie.host/25105d46-cc0a-4f76-9ad0-5e64e3eb0e52/OenfEsMruV.lottie",
[ELottieKeys.key]: "https://lottie.host/a80ec293-6f3d-4d21-a19e-9dfb40b86a14/clQys1OEAL.lottie",
[ELottieKeys.cloudAndStars]: "https://lottie.host/6010e02c-da90-4089-982c-177f3b5dbc05/fXkYv6hGPc.lottie",
[ELottieKeys.darts]: "https://lottie.host/c3856d09-bfe9-44de-8712-f935f5deed67/rtD0j4YfnN.lottie",
[ELottieKeys.umbrella]: "https://lottie.host/e353e80c-fd4a-4eca-a930-d9bf923466e0/G4sxbtkhIA.lottie",
[ELottieKeys.hourglass]: "https://lottie.host/c1b52c33-1a3c-4759-9c5d-090ed2a62c77/IqHW4RCqVH.lottie",
[ELottieKeys.lightBulb]: "https://lottie.host/07e33753-d13c-4469-ad33-26e57017b0ec/qMVfYwwLqs.lottie",
[ELottieKeys.sun]: "https://lottie.host/8ae9682d-93d3-4988-8745-e7134daed217/lZG1RZgqaP.lottie",
}
interface IUseLottieProps {
preloadKey?: ELottieKeys;
loadKey?: ELottieKeys;
}
export const useLottie = ({ preloadKey, loadKey }: IUseLottieProps) => {
const [animationData, setAnimationData] = useState<Data>();
const [isError, setIsError] = useState(false);
const getAnimationDataFromLottie = async (key: ELottieKeys) => {
try {
const animation = await fetch(lottieUrls[key]);
const arrayBuffer = await animation.arrayBuffer();
return arrayBuffer;
} catch (error) {
console.error(error);
setIsError(true);
return;
}
}
const preload = useCallback(async (key: ELottieKeys) => {
const arrayBuffer = await getAnimationDataFromLottie(key);
indexedDB.set(EObjectStores.Lottie, key, arrayBuffer);
}, [])
const load = useCallback(async (key: ELottieKeys) => {
const animationFromDB = await indexedDB.get<ArrayBuffer>(EObjectStores.Lottie, key)
if (animationFromDB) {
return setAnimationData(animationFromDB);
}
const arrayBuffer = await getAnimationDataFromLottie(key);
if (!arrayBuffer) return;
setAnimationData(arrayBuffer)
await indexedDB.set<ArrayBuffer>(EObjectStores.Lottie, key, arrayBuffer);
}, [])
useEffect(() => {
if (preloadKey) {
preload(preloadKey);
}
if (loadKey) {
load(loadKey);
}
}, [load, loadKey, preload, preloadKey])
return useMemo(() => ({
animationData,
isError
}), [
animationData,
isError
])
}

View File

@ -0,0 +1,59 @@
import { useApi } from "@/api";
import metricService, { EGoals } from "@/services/metric/metricService";
import { actions, selectors } from "@/store";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
export const usePersonalVideo = () => {
const api = useApi();
const dispatch = useDispatch();
const token = useSelector(selectors.selectToken);
const { generatingVideo, videoId, createdDate } = useSelector(
selectors.selectPersonalVideo
);
const personalVideoTimeLimit = useMemo(() => import.meta.env.AURA_PERSONAL_VIDEO_TIME_LIMIT, [])
const requestVideoInterval = useRef<NodeJS.Timeout>();
const [isVideoReady, setIsVideoReady] = useState(false);
const clearPersonalVideoInterval = useCallback(() => {
if (!requestVideoInterval.current) return;
if (isVideoReady) return clearInterval(requestVideoInterval.current);
}, [isVideoReady])
const getTargetUserVideo = useCallback(async () => {
const videos = await api.getUserVideos({
token,
});
return videos.find((video) => video.id === videoId);
}, [api, token, videoId]);
useEffect(() => {
clearPersonalVideoInterval();
return () => {
clearPersonalVideoInterval();
};
}, [clearPersonalVideoInterval])
useEffect(() => {
if (!generatingVideo) return setIsVideoReady(true);
requestVideoInterval.current = setInterval(async () => {
const video = await getTargetUserVideo();
if (!video) return setIsVideoReady(true);
if (video.videoUrl.length) {
metricService.reachGoal(EGoals.ROSE_VIDEO_CREATED)
dispatch(actions.personalVideo.updateUrl(video.videoUrl));
return setIsVideoReady(true);
}
if (new Date().getTime() - createdDate > Number(personalVideoTimeLimit) * 1000) {
return setIsVideoReady(true);
}
}, 5000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return useMemo(() => ({ isVideoReady }), [isVideoReady])
}

View File

@ -274,6 +274,10 @@ const routes = {
makePayment: () =>
[dApiHost, dApiPrefix, "payment", "checkout"].join("/"),
// User videos
getUserVideos: () =>
[dApiHost, "users", "videos", "combined"].join("/"),
},
openAi: {
createThread: () => [openAIHost, openAiPrefix, "threads"].join("/"),

View File

@ -0,0 +1,38 @@
import { openDB } from 'idb';
export enum EObjectStores {
Lottie = 'lottie',
}
const objectStores: EObjectStores[] = [EObjectStores.Lottie];
const dbPromise = openDB('aura-store', 1, {
upgrade(db) {
db.createObjectStore('lottie');
},
});
async function get<T>(objectStore: string, key: string): Promise<T> {
return (await dbPromise).get(objectStore, key);
}
async function set<T>(objectStore: string, key: string, val: T) {
return (await dbPromise).put(objectStore, val, key);
}
async function del(objectStore: string, key: string) {
return (await dbPromise).delete(objectStore, key);
}
async function clear() {
return objectStores.map(async (objectStore: EObjectStores) => {
return (await dbPromise).clear(objectStore);
});
}
async function keys() {
return objectStores.map(async (objectStore: EObjectStores) => {
return {
objectStore: objectStore,
keys: (await dbPromise).getAllKeys(objectStore)
};
});
}
export default { get, set, del, clear, keys };

View File

@ -1,3 +1,16 @@
export enum EGoals {
ENTERED_EMAIL = "EnteredEmail",
PAYMENT_SUCCESS = "PaymentSuccess",
ROSE_VIDEO_CREATION_START = 'RoseVideoCreationStart',
ROSE_LOADING_START = "RoseLoadingStart",
ROSE_VIDEO_CREATED = "RoseVideoCreated",
ROSE_LOADING_END = "RoseLoadingEnd",
ROSE_VIDEO_PLAY_START = "RoseVideoPlayStart",
ROSE_VIDEO_PLAY_END = "RoseVideoPlayEnd",
ROSE_VIDEO_PLAY_USER_STOP = "RoseVideoPlayUserStop",
ROSE_VIDEO_PLAY_USER_PLAY = "RoseVideoPlayUserPlay"
}
interface IUserParams {
UserID: number;
genderFrom: string;
@ -21,7 +34,7 @@ const userParams = (parameters: Partial<IUserParams>) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}
const reachGoal = (goal: "EnteredEmail" | "PaymentSuccess") => {
const reachGoal = (goal: EGoals) => {
if (typeof window.ym !== "function") return console.error("Yandex.Metric not found");
window.ym(metricCounterNumber, "reachGoal", goal)

View File

@ -72,6 +72,7 @@ import palmistry, {
} from "./palmistry";
import { selectPaywallsIsMustUpdate, selectPaywalls } from "./paywalls";
import privacyPolicy, { actions as privacyPolicyActions, selectPrivacyPolicy } from "./privacyPolicy";
import personalVideo, { actions as personalVideoActions, selectPersonalVideo } from "./personalVideo";
const preloadedState = loadStore();
export const actions = {
@ -92,6 +93,7 @@ export const actions = {
userConfig: userConfigActions,
palmistry: palmistryActions,
privacyPolicy: privacyPolicyActions,
personalVideo: personalVideoActions,
reset: createAction("reset"),
};
export const selectors = {
@ -127,6 +129,7 @@ export const selectors = {
selectPaywallsIsMustUpdate,
selectPrivacyPolicy,
selectStripeButton,
selectPersonalVideo,
...formSelectors,
};
@ -147,7 +150,8 @@ export const reducer = combineReducers({
userConfig,
palmistry,
paywalls,
privacyPolicy
privacyPolicy,
personalVideo
});
export type RootState = ReturnType<typeof reducer>;

View File

@ -0,0 +1,37 @@
import { createSlice, createSelector } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface IPersonalVideo {
generatingVideo: boolean
videoId: string
videoUrl: string
createdDate: number
}
const initialState: IPersonalVideo = {
generatingVideo: false,
videoId: "",
videoUrl: "",
createdDate: 0
}
const personalVideoSlice = createSlice({
name: 'personalVideo',
initialState,
reducers: {
updateStatus(state, action: PayloadAction<Omit<IPersonalVideo, 'createdDate' | 'videoUrl'>>) {
return { ...state, ...action.payload, createdDate: new Date().getTime() }
},
updateUrl(state, action: PayloadAction<string>) {
return { ...state, videoUrl: action.payload }
}
},
extraReducers: (builder) => builder.addCase('reset', () => initialState),
})
export const { actions } = personalVideoSlice
export const selectPersonalVideo = createSelector(
(state: { personalVideo: IPersonalVideo }) => state.personalVideo,
(personalVideo) => personalVideo
)
export default personalVideoSlice.reducer