Merge branch 'develop' into 'main'

AW-429-comp-v1-ios-ab

See merge request witapp/aura-webapp!738
This commit is contained in:
Daniil Chemerkin 2025-04-18 18:54:01 +00:00
commit ba35a2fba7
143 changed files with 2479 additions and 5828 deletions

33
package-lock.json generated
View File

@ -15,8 +15,6 @@
"@mui/material": "^5.15.21", "@mui/material": "^5.15.21",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@smakss/react-scroll-direction": "^4.0.4", "@smakss/react-scroll-direction": "^4.0.4",
"@stripe/react-stripe-js": "^2.3.1",
"@stripe/stripe-js": "^2.1.9",
"@unleash/proxy-client-react": "^4.5.2", "@unleash/proxy-client-react": "^4.5.2",
"apng-js": "^1.1.1", "apng-js": "^1.1.1",
"core-js": "^3.37.1", "core-js": "^3.37.1",
@ -1524,24 +1522,6 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
}, },
"node_modules/@stripe/react-stripe-js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.3.1.tgz",
"integrity": "sha512-vXiwcG2ZjAF4AezjP7DJ8jiwxfCWCen/X2rBhyXaKrfQ7+pwmXhsoUlKRa0eLWioY1oelOQOafauNUiwTwFHgQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.44.1 || ^2.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.9.tgz",
"integrity": "sha512-0RSvCJrzEVx52e8hbSAcZ2vv6OzoFj5fe5XC50GSrcev1Y4t2XDE6W5CIhR/Y6l3CPgO/P4luqoLWuvpUkBhig=="
},
"node_modules/@svgr/babel-plugin-add-jsx-attribute": { "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
@ -6475,19 +6455,6 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
}, },
"@stripe/react-stripe-js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.3.1.tgz",
"integrity": "sha512-vXiwcG2ZjAF4AezjP7DJ8jiwxfCWCen/X2rBhyXaKrfQ7+pwmXhsoUlKRa0eLWioY1oelOQOafauNUiwTwFHgQ==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@stripe/stripe-js": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.9.tgz",
"integrity": "sha512-0RSvCJrzEVx52e8hbSAcZ2vv6OzoFj5fe5XC50GSrcev1Y4t2XDE6W5CIhR/Y6l3CPgO/P4luqoLWuvpUkBhig=="
},
"@svgr/babel-plugin-add-jsx-attribute": { "@svgr/babel-plugin-add-jsx-attribute": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",

View File

@ -22,8 +22,6 @@
"@mui/material": "^5.15.21", "@mui/material": "^5.15.21",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@smakss/react-scroll-direction": "^4.0.4", "@smakss/react-scroll-direction": "^4.0.4",
"@stripe/react-stripe-js": "^2.3.1",
"@stripe/stripe-js": "^2.1.9",
"@unleash/proxy-client-react": "^4.5.2", "@unleash/proxy-client-react": "^4.5.2",
"apng-js": "^1.1.1", "apng-js": "^1.1.1",
"core-js": "^3.37.1", "core-js": "^3.37.1",

View File

@ -26,6 +26,12 @@
"males": "Males", "males": "Males",
"females": "Females", "females": "Females",
"/try-app": { "/try-app": {
"title": "Your Compatibility Reading is READY",
"description": "Работа по созданию твоего Прогноза нашими сотрудниками стоила нам <price>, ты можешь купить его на всегда или прочитать его у нас в Приложении БЕСПЛАТНО.",
"description-timer": "Ваш разбор по Совместимости действителен <bold>, после чего он будет удален.",
"description-timer-bold": "<time> мин.",
"buy": "Buy for <price>",
"read-in-app": "Read for FREE<br> in the App",
"header": { "header": {
"title": "Your Personalized Offer Reserved", "title": "Your Personalized Offer Reserved",
"get-prediction-in-app": "Get prediction in<br>the App" "get-prediction-in-app": "Get prediction in<br>the App"
@ -37,12 +43,12 @@
"life": "Life line ✅" "life": "Life line ✅"
}, },
"reading_ready": { "reading_ready": {
"title": "Your Palm Reading is READY and available in the app for your iPhone!" "title": "Your Compatibility Reading is READY and available in the app for your iPhone!"
}, },
"your_access_code": "Your Access Code", "your_access_code": "Your Access Code",
"copy": "COPY", "copy": "COPY",
"instruction_point_1": "1. Copy Your Access Code", "instruction_point_1": "1. Copy Your Access Code",
"instruction_point_2": "2. Download the App", "instruction_point_2": "2. Download the App and Enter Your Access Code",
"instruction_point_3": "3. Enter Your Access Code in the App", "instruction_point_3": "3. Enter Your Access Code in the App",
"instruction_point_4": "1. Copy Your Access Code", "instruction_point_4": "1. Copy Your Access Code",
"instruction_point_5": "2. Enter Your Access Code in the App 👇", "instruction_point_5": "2. Enter Your Access Code in the App 👇",
@ -58,7 +64,34 @@
"get-my-reading-in-app": "GET MY READING IN THE APP", "get-my-reading-in-app": "GET MY READING IN THE APP",
"why_love": "Why does everyone <color> ?", "why_love": "Why does everyone <color> ?",
"why_love_color": "love AURA", "why_love_color": "love AURA",
"as_seen_in": "<color> As Seen in " "as_seen_in": "<color> As Seen in ",
"your-reading": {
"title": "Your Reading",
"subtitle": "Что ты получишь",
"points": {
"v1": {
"point1": "Разбор вашего притяжения и скрытых сил, управляющих вашими чувствами.",
"point2": "Индивидуальный прогноз на развитие отношений: что ждет вас впереди.",
"point3": "Вопросы и персональные рекомендации от эксперта.",
"point4": "Уникальная информация, которую нельзя найти в стандартных гороскопах.",
"point5": "Полная картина вашей совместимости: уровни в процентах — без иллюзий и догадок.",
"point6": "Подробный астрологический разбор: что сближает и что вызывает напряжение.",
"point7": "Глубокое понимание партнера: как он любит и что для него важно.",
"point8": "Графики, схемы и объяснения для наглядной динамики ваших отношений."
},
"v2": {
"point1": "Полная картина вашей совместимости: уровни в процентах — без иллюзий и догадок.",
"point2": "Подробный астрологический разбор: что сближает и что вызывает напряжение.",
"point3": "Глубокое понимание партнера: как он любит и что для него важно.",
"point4": "Графики, схемы и объяснения для наглядной динамики ваших отношений.",
"point5": "Разбор вашего притяжения и скрытых сил, управляющих вашими чувствами.",
"point6": "Индивидуальный прогноз на развитие отношений: что ждет вас впереди.",
"point7": "Вопросы и персональные рекомендации от эксперта.",
"point8": "Уникальная информация, которую нельзя найти в стандартных гороскопах."
}
},
"description": "To read the full reading, you need to get access through the app for your iPhone"
}
}, },
"/find-your-happiness": { "/find-your-happiness": {
"title": "Gain Clarity and Confidence in Life", "title": "Gain Clarity and Confidence in Life",
@ -82,69 +115,201 @@
} }
}, },
"v2": { "v2": {
"title": "Compatibility Test", "title": "Тест на Совместимость",
"subtitle": "It all starts with you! Choose your gender.", "subtitle": "Все начинается с тебя!<br>Выбери свой пол.",
"points": { "points": {
"point1": "The test takes less than a minute.", "point1": "Тест займет не более 1 мин.",
"point2": "You'll receive a compatibility analysis through palmistry based on the lines on your hand.", "point2": "Ты получишь полный разбор совместимости.",
"point3": "Resolve relationship issues in a month.", "point3": "Решишь проблемы в отношениях за месяц.",
"point4": "Save hundreds of dollars on unreliable forecasts.", "point4": "Сэкономите сотни долларов на ненадёжных прогнозах.",
"point5": "Get a personalized analysis." "point5": "Получите персональный анализ."
} }
} }
}, },
"/birthdate": { "/birthdate": {
"title": "When Were You Born?", "title": "Когда вы родились?",
"text": "Your birth date can reveal strengths and values that may help you move forward" "text": "Глубинные закономерности, заложенны в момент вашего рождения."
},
"/birthtime": {
"title": "Время твоего рождения",
"text": "Чтобы расчеты были точными, важно знать время рождения. Чем точнее, тем детальнее разбор",
"checkbox": "Не знаю точное время (рассчитайте по дню моего рождения)"
},
"/birthplace": {
"title": "Твое место рождения",
"text": "Город рождения позволяет учесть точное расположение планет в момент вашего появления на свет",
"placeholder": "Страна, Город"
},
"/calculate-in-advance": {
"title": "Как ты считаешь можно ли просчитать совместимость заранее?",
"answer1": "Да, по определённым закономерностям",
"answer2": "Нет нельзя",
"answer3": "Можно, но это не гарантия успеха",
"answer4": "Можно вычислить, но не судьбу"
},
"/calculate-in-advance-result": {
"yes": {
"title": "Наш уникальный разбор помог тысячам людей понять, подходят ли они друг другу",
"text": "Совместимость — это не просто “любовь” или “не любовь”, это набор четких паттернов, которые можно просчитать. Наша методика основана на астрологии, но объяснена понятным языком и применима в реальной жизни"
},
"no": {
"title": "Так считают многие. Но Совместимость — это предсказуемая вещь, на самом деле в отношениях действуют определённые закономерности",
"text": "Наш разбор показывает, как разные аспекты взаимодействия складываются в единую картину, помогая заранее увидеть сильные стороны и возможные точки роста. Это не догма, а полезный инструмент, который даёт понимание, почему с одними людьми всё складывается легко, а с другими возникают сложности."
},
"yes_may_be": {
"title": "Верно, совместимость — это не гарантия успеха, но это мощный инструмент, который помогает лучше понять партнёра и избежать многих ошибок",
"text": "Наш разбор показывает, какие закономерности влияют на динамику отношений и как их использовать, чтобы укрепить связь. Это не приговор, а руководство, которое помогает осознанно строить гармонию, а не полагаться только на удачу."
},
"yes_but_not_fate": {
"title": "Судьба — это не жёстко заданный сценарий, на нее влияют закономерности в отношениях которые можно просчитать и мы это делаем с вероятностью 95%",
"text": "Наш разбор показывает, какие паттерны влияют на динамику пары, где скрыты точки притяжения и возможные препятсвия. Это не просто теория — это сильный инструмент, который помогает осознанно управлять своей судьбой, а не плыть по течению."
}
},
"/your-analysis": {
"title": "Твой анализ",
"point1": "Анализирую ваши природные склонности в отношениях...",
"point2": "Точки риска, которые могут привести к сложным отношениям...",
"point3": "Формируем динамику отношений заложеную в тебе...",
"point4": "Разбираем, где вы сами создаете преграды в любви.",
"point5": "Обработка твоих сильных и слабых сторон в любви...",
"point6": "Анализ повторяющихся сценариев в твоих отношениях...",
"point7": "Расшифровываем твою силу в любви и на что нужно обратить внимание...",
"point8": "Раскрываем ваш потенциал в отношениях...",
"point9": "Обрабатываем твой эмоциональный ритм и стиль привязанности...",
"point10": "Разбираем, что ведет вас к счастью в отношениях...",
"point11": "Анализируем, какие качества должны быть у вашего идеального партнера",
"point12": "Разбираем, где вы сами создаете преграды в любви.",
"point13": "Разбираем ваш личный потенциал, чтобы не тратить время на несовместимость."
},
"/partner-analysis": {
"title": "Анализ твоего партнера",
"point1": "Подбор паттернов совпадения и не совпадения...",
"point2": "Выявляем сильные и уязвимые стороны связи...",
"point3": "Выявляем, как он влияет на вашу совместимость...",
"point4": "Анализируем что движет им на самом деле...",
"point5": "Составляем, какие аспекты требуют внимания...",
"point6": "Анализируем ключевые зоны ваших отношений...",
"point7": "Разбираем его сильные и слабые стороны в отношениях...",
"point8": "Рассчитываем насколько он совместим с вами на глубинном уровне",
"point9": "Проверяем его сильные стороны в паре насколько они подходят вам...",
"point10": "Ищем его черты которые могут создавать сложности...",
"point11": "Подбор паттернов совпадения и не совпадения..."
},
"/result-analysis": {
"title": "Какой результат разбора отношений вам важнее всего?",
"answer1": "Найти ответы на беспокоящие вопросы",
"answer2": "Понять, как улучшить отношения",
"answer3": "Узнать, что нас ждет впереди",
"answer4": "Разобраться, стоит ли продолжать отношения",
"answer5": "Просто интересно, без конкретного запроса"
},
"/result-analysis-result": {
"find_answers": {
"title": "<bold> Знание первый шаг к уверенности.",
"title_bold": "Ответы есть всегда, главное искать их правильно.",
"text": "Мы проанализировали более 10 000 пар, и в 82% случаев наши прогнозы подтверждаются. В основе методики астрология, психология и реальные истории."
},
"improve_relationships": {
"title": "Лучшие отношения это те, в которых оба чувствуют себя услышанными.",
"text": "Мы проанализировали более 10 000 пар, и в 82% случаев наши прогнозы подтверждаются. В основе методики астрология, психология и реальные истории."
},
"what_awaits": {
"title": "<bold> Зная, что ждет впереди, вы можете сделать отношения лучше.",
"title_bold": "Будущее это не приговор, а возможность.",
"text": "Мы проанализировали более 10 000 пар, и в 82% случаев наши прогнозы подтверждаются. В основе методики астрология, психология и реальные истории."
},
"continue_relationship": {
"title": "Будущее важно, но не забывайте про настоящее. Если отношения не делают вас счастливыми сейчас, что изменится дальше?",
"text": "Мы проанализировали более 10 000 пар, и в 82% случаев наши прогнозы подтверждаются. В основе методики астрология, психология и реальные истории."
},
"just_interested": {
"title": "<bold> Изучая себя и отношения, можно открыть неожиданные инсайты.",
"title_bold": "Иногда ответы приходят, когда их не ищешь.",
"text": "Мы проанализировали более 10 000 пар, и в 82% случаев наши прогнозы подтверждаются. В основе методики астрология, психология и реальные истории."
}
},
"/review": {
"single": {
"username": "Sofia Cruz",
"date": "April 8",
"text": "I kept ending up in failed relationships — things started well, then fell apart. It was exhausting, and I began to wonder if the problem was me. Your service felt different from the start. Instead of generic tips, I got clear, personal insights into my patterns and real compatibility. That first compatibility analysis clicked — it was so accurate, I finally stopped second-guessing myself. Now Im in a relationship that feels natural and real. No more forcing — just choosing right."
},
"username": "Emma Johnson",
"date": "Jan 25",
"text": "I used to keep running into failed relationships—things would start off well, but then everything would fall apart. It was exhausting, and I started wondering if the problem was me. Your service immediately felt different: instead of generic advice and random matches, I got precise insights about who I actually have a real chance of building something strong with. I still remember seeing my first compatibility analysis—it aligned so perfectly that I just knew: this was it. Now, I waste less time on pointless connections and am finally building a relationship that feels effortless and right."
}, },
"/palms-information": { "/palms-information": {
"aries": { "aries": {
"title": "♈ Aries", "title": "♈ Овен",
"description": "Your palms are a map of destiny, reflecting your strength and character. According to our data, 76 million Aries have a clear and straight head line, indicating decisiveness and independence. In 81% of successful relationships, their partners share their passion for action. Your palms will reveal how well your ambitions and energy align with your partner." "description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> разделяют их страсть к действию и независимость. Решительность и амбиции определяют, насколько легко вы находите общий ритм с партнером.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Что укрепляет отношения, а что создаёт сложности?",
"description_percent": "81%"
}, },
"taurus": { "taurus": {
"title": "♉ Taurus", "title": "♉ Телец",
"description": "The patterns on your palms reveal your true self. Our research shows that 83 million Taurus have a long and smooth life line, a sign of resilience and desire for stability. In 86% of harmonious relationships, partners value reliability and comfort. Take a look at your palm—it will indicate how strong your union is." "description": "Согласно нашим исследованиям, в <percent> гармоничных союзов партнеры <zodiac> разделяют их стремление к комфорту и верности. Спокойствие, преданность и терпение вот что делает тебя идеальным спутником для долгосрочных отношений.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "86%"
}, },
"gemini": { "gemini": {
"title": "♊ Gemini", "title": "♊ Близнецы",
"description": "Your palms are a mirror of your mind and character. According to our data, 72 million Gemini have a bifurcated or branched head line, suggesting flexibility and multitasking ability. In 78% of successful relationships, partners are ready for change and spontaneity. Check your lines—they will show how well your partner matches your pace." "description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> готовы к переменам, легкости и спонтанности. Остроумие, коммуникабельность и жажда новизны делают тебя увлекательным спутником, но требуют от партнера гибкости и умения идти в ногу с твоими идеями.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "78%"
}, },
"cancer": { "cancer": {
"title": "♋ Cancer", "title": "♋ Рак",
"description": "Your palm lines hide secrets of emotions and attachments. Our data shows that 78 million Cancers have a clear and expressive heart line, indicating deep sensitivity. In 82% of strong relationships, Cancer partners have well-developed empathy. Your palm will help reveal how well you and your loved one understand each other." "description": "Согласно нашим данным, в <percent> крепких отношений партнеры <zodiac> обладают развитой эмпатией и интуитивным пониманием друг друга. Ты ценишь заботу, стабильность и душевную близость, а значит, гармония возможна только с тем, кто умеет разделять твои чувства.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "82%"
}, },
"leo": { "leo": {
"title": "♌ Leo", "title": "♌ Лев",
"description": "Palms store the history of your personality. Our data indicates that 80 million Leos have a noticeable Sun line, which suggests innate charisma and a drive for recognition. In 84% of happy relationships, Leo partners are admired and share their ambitions. Your palm will show how ready your partner is to be part of your shine." "description": "Согласно нашим данным, в <percent> счастливых союзов партнеры <zodiac> искренне восхищаются ими и поддерживают их амбиции. Ты стремишься к ярким, вдохновляющим отношениям, где тебя ценят, уважают и не пытаются затмить.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "84%"
}, },
"virgo": { "virgo": {
"title": "♍ Virgo", "title": "♍ Дева",
"description": "Every line on your palm is a trace of your character. Our observations show that 75 million Virgos have fine but distinct lines, reflecting attention to detail and rationality. In 79% of successful Virgo relationships, trust and predictability are key. Your palms will reveal how well your values align with your partner." "description": "Согласно нашим наблюдениям, в <percent> успешных союзов партнеры <zodiac> ценят предсказуемость, заботу и рациональный подход к отношениям. Ты стремишься к порядку и гармонии, а значит, идеальный партнер тот, кто разделяет твои ценности и уважает твой перфекционизм.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "79%"
}, },
"libra": { "libra": {
"title": "♎ Libra", "title": "♎ Весы",
"description": "The harmony of your palms reflects the harmony of your soul. Our data shows that 77 million Libras have a curved and smooth heart line, indicating diplomacy and balanced relationships. In 80% of successful partnerships, their partners appreciate honesty and compromise. Look at your palm—it will indicate how much your relationship is based on mutual understanding." "description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> разделяют их стремление к гармонии и открытому диалогу. Ты ценишь красивые, легкие и справедливые отношения, где оба партнера готовы работать над взаимопониманием.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "80%"
}, },
"scorpio": { "scorpio": {
"title": "♏ Scorpio", "title": "♏ Скорпион",
"description": "The depth of your palm lines reflects the depth of your feelings. Our research shows that 81 million Scorpios have a deep and straight heart line, indicating passion and devotion. In 83% of successful relationships, their partners share the same emotional intensity. Your palm will show how genuine the connection between you and your partner is." "description": "Согласно нашим исследованиям, в <percent> успешных союзов партнеры <zodiac> обладают такой же эмоциональной интенсивностью, не боясь глубокой связи и полной вовлеченности. Ты не приемлешь поверхностности в отношениях тебе нужен партнер, который готов разделить с тобой и взлеты, и падения.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "83%"
}, },
"sagittarius": { "sagittarius": {
"title": "♐ Sagittarius", "title": "♐ Стрелец",
"description": "The lines on your palm are a map of your life journey. Our data shows that 74 million Sagittarians have a long and prominent head line, symbolizing curiosity and a quest for freedom. In 77% of long-term relationships, their partners share an adventurous spirit. Your palm will tell you how ready your partner is for your ambitious dreams." "description": "Согласно нашим данным, в <percent> долгосрочных союзов партнеры <zodiac> разделяют их жажду новых впечатлений и открытий. Ты ценишь легкость, оптимизм и независимость, а значит, тебе нужен человек, который не станет ограничивать тебя, а будет готов идти рядом в новом путешествии.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "77%"
}, },
"capricorn": { "capricorn": {
"title": "♑ Capricorn", "title": "♑ Козерог",
"description": "Your palms are a reflection of your strength and determination. Our data shows that 79 million Capricorns have a pronounced fate line, indicating their ambitions and discipline. In 85% of happy marriages, their partners support their pursuit of success. Look at your palm—it will reveal how much your union is based on shared goals." "description": "Согласно нашим данным, в <percent> счастливых браков партнеры <zodiac> поддерживают их стремление к успеху и стабильности. Ты ценишь верность, ответственность и долгосрочные перспективы, а значит, тебе нужен партнер, который разделит твои амбиции и будет готов идти к общему будущему.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "85%"
}, },
"aquarius": { "aquarius": {
"title": "♒ Aquarius", "title": "♒ Водолей",
"description": "The lines on your palm are the mark of your uniqueness. Our research shows that 71 million Aquarians have an unconventional head line, indicating originality and non-standard thinking. In 75% of harmonious relationships, their partners respect their freedom and independence. Your palm will reveal how ready your partner is for your innovative ideas." "description": "Согласно нашим исследованиям, в <percent> гармоничных союзов партнеры <zodiac> разделяют их стремление к независимости и нестандартный взгляд на жизнь. Ты ценишь оригинальность, свободу мысли и дружеские отношения в паре, а значит, тебе нужен партнер, который не будет тебя ограничивать, а будет вдохновлять.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "75%"
}, },
"pisces": { "pisces": {
"title": "♓ Pisces", "title": "♓ Рыбы",
"description": "Palms conceal the secrets of your soul. Our data shows that 82 million Pisces have a long and smooth heart line, indicating their intuition and deep sense of love. In 84% of strong relationships, their partners share a similar sensitivity. Your palm will tell you how well your partner can feel you on an emotional level." "description": "Согласно нашим данным, в <percent> крепких отношений партнеры <zodiac> обладают схожей чувствительностью и умением понимать друг друга без слов. Ты ценишь романтику, душевную близость и тонкие невидимые нити, связывающие сердца.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "84%"
} }
}, },
"/what-aspects": { "/what-aspects": {
@ -155,11 +320,34 @@
"answer4": "Life Transitions" "answer4": "Life Transitions"
}, },
"/relationship-status": { "/relationship-status": {
"title": "To Better Understand You, Indicate Your Current Relationship Status", "title": "Чтобы яснее разобрать вашу суть, укажите какие у вас отношения сейчас?",
"answer1": "Single", "answer1": "Начало",
"answer2": "In a Relationship", "answer2": "Долгие отношения",
"answer3": "Married", "answer3": "Развиваются непонятно куда",
"answer4": "Divorced" "answer4": "Разрыв/кризис",
"answer5": "Single"
},
"/relationship-status-result": {
"single": {
"title": "Вам кажется, что достойных партнёров нет или вы сталкиваетесь «не с теми»? Дело в несовместимых выборах.",
"text": "Наша система прогнозирует совместимость с точностью до 98%, помогая найти подходящего партнёра и открывая новые перспективы в личной жизни."
},
"start": {
"title": "Как понять, что ваш партнер — действительно “ваш человек” и не ошибиться?",
"text": "Понимание эмоций партнёра — ключ к гармоничным отношениям. Наша система прогнозирует совместимость с точностью до 98% и мы дадим тебе точные отчеты."
},
"in_relationship": {
"title": "Любовь это важно, но почему только любви недостаточно для долгих счастливых отношений?",
"text": "Быт либо укрепляет отношения, либо провоцирует конфликты, если взгляды на комфорт и заботу различаются. Наша система предсказывает совместимость с точностью до 98%, мы поможем тебе понять, насколько долгими и гармоничными могут быть ваши отношения."
},
"developing": {
"title": "Чувствуете, что партнёр вас не понимает? Это не случайность, а различие в натальных паттернах.",
"text": "Близость — это не только влечение, но и понимание желаний и эмоций. Наши данные с 2 млн человек подтверждают это, и мы знаем, как помочь."
},
"crisis": {
"title": "Почему одни пары быстро расходятся, а другие остаются вместе на годы?",
"text": "Совместное будущее — это не мечты, а общие планы, ценности и ежедневный труд. Наша система с точностью до 98% предсказывает совместимость. Мы расскажем, как шаг за шагом построить крепкие отношения."
}
}, },
"/element-resonates": { "/element-resonates": {
"title": "Which Element Empowers You the Most?", "title": "Which Element Empowers You the Most?",
@ -181,11 +369,11 @@
"answer7": "Turquoise" "answer7": "Turquoise"
}, },
"/head-or-heart": { "/head-or-heart": {
"title": "What Guides You in Life: The Call of the Heart or the Voice of Reason?", "title": "Чем вы руководствуетесь в жизни: зовом сердца или голосом разума?",
"answer1": "Follow My Heart", "answer1": "Следую за сердцем",
"answer2": "Rely on Reason", "answer2": "Опираюсь на разум",
"answer3": "Combine Both Approaches", "answer3": "Сочетаю сердце и разум",
"answer4": "Depends on the Situation" "answer4": "Зависит от ситуации"
}, },
"/relate-following": { "/relate-following": {
"title": "How Important Is It for You to Meet Your Partner's Expectations?", "title": "How Important Is It for You to Meet Your Partner's Expectations?",
@ -198,13 +386,29 @@
"strongly_disagree": "Not Important at All" "strongly_disagree": "Not Important at All"
}, },
"/gender-partner": { "/gender-partner": {
"title": "Your Partner's Gender", "title": "Теперь добавим твоего партнера",
"description": "Select your partner's gender for a personalized astrological analysis. The stars consider every detail! ✨", "description": "Чтобы определить ключевые точки вашей совместимости.",
"already_have_account": "" "already_have_account": ""
}, },
"/partner-similarity": {
"title": "Насколько ваш партнер похож на вас по характеру?",
"answer1": "Очень похож",
"answer2": "Есть сходства, но и различия тоже",
"answer3": "Мы совершенно разные"
},
"/birthdate-partner": { "/birthdate-partner": {
"title": "Your Partner's Birth Date?", "title": "Дата рождения партнера",
"text": "We'll incorporate zodiac influences for a more precise compatibility assessment" "text": "Это основа для понимания динамики вашей пары."
},
"/birthplace-partner": {
"title": "Место рождения партнера",
"text": "Чтобы рассчитать взаимодействие ваших планет, нам нужно место рождения партнера.",
"placeholder": "Страна, Город",
"checkbox": "Я не знаю место его рождения"
},
"/almost-there": {
"title": "Мы почти у цели",
"description": "Еще несколько вопросов чтобы расчеты стали максимально точными."
}, },
"/date-event": { "/date-event": {
"single": { "single": {
@ -212,58 +416,70 @@
"text": "💫 A significant date can help reveal planetary influences on your life and provide a more personalized analysis." "text": "💫 A significant date can help reveal planetary influences on your life and provide a more personalized analysis."
}, },
"relationship": { "relationship": {
"title": "Enter a Significant Date Important to You or Your Partner.", "title": "Значимая дата важная для вас или вашей пары",
"text": "💫 A significant date can help reveal planetary influences on your relationship and provide a more personalized analysis." "text": "Она поможет раскрыть ваши отношения и дать более персонализированный анализ."
} }
}, },
"/palms-information-partner": { "/palms-information-partner": {
"aries": { "aries": {
"title": "♈ Aries", "title": "♈ Овен",
"description": "Passionate, straightforward, and initiative-driven. Falls in love quickly, acts decisively, but can be impatient and jealous. Loves leadership and craves intense emotions, hates routine. If the feelings fade, they leave without regret. You form an intriguing combination, and we have a lot to share with you!" "description": "Страстный, прямолинейный и инициативный. Влюбляется быстро, действует решительно, но может быть нетерпеливым и ревнивым. Любит лидерство, стремится к ярким эмоциям, не терпит рутины. Если чувства угасают уходит без сожалений.<br><bold>",
"description_bold": "С тобой получится интересное сочетание, давай разберем детали..."
}, },
"taurus": { "taurus": {
"title": "♉ Taurus", "title": "♉ Телец",
"description": "Reliable, sensual, and loyal. Moves slowly but thoroughly in love. Values stability, comfort, and physical connection. Can be possessive and reluctant to let go of the past. Doesn't tolerate betrayal or sudden changes. How will your relationship fare? We have the answers!" "description": "Надежный, чувственный и преданный. В любви движется медленно, но основательно. Ценит стабильность, комфорт и телесный контакт. Может быть собственником и долго не отпускать прошлое. Не терпит предательства и резких перемен.<br><bold>",
"description_bold": "Как сложится ваша пара? У нас есть ответы..."
}, },
"gemini": { "gemini": {
"title": "♊ Gemini", "title": "♊ Близнецы",
"description": "Light-hearted, sociable, and unpredictable. Gets interested quickly but might lose interest just as fast. Enjoys intellectual interaction, flirting, and freedom. Sometimes inconsistent, but with someone who can keep their interest, they're committed. What are your chances for harmony? Let's find out together!" "description": "Легкий, общительный и непредсказуемый. Быстро увлекается, но может охладеть так же стремительно. Любит интеллектуальное взаимодействие, флирт и свободу. Иногда непостоянен, но с тем, кто сможет удерживать его интерес, готов на многое.<br><bold>",
"description_bold": "Какие у вас шансы на гармонию? Давай разберемся вместе..."
}, },
"cancer": { "cancer": {
"title": "♋ Cancer", "title": "♋ Рак",
"description": "Sensitive, caring, and loyal. Emotions are their main element. Falls deeply in love but opens up slowly. Can be vulnerable and needs emotional safety. If disappointed, they leave but linger long over it. What's your emotional compatibility? We have the answer!" "description": "Чувствительный, заботливый и верный. Эмоции его главная стихия. Влюбляется глубоко, но медленно раскрывается. Может быть ранимым и нуждается в эмоциональной безопасности. Если разочарован уходит, но долго переживает.<br><bold>",
"description_bold": "Какая у вас эмоциональная совместимость? Мы знаем ответ..."
}, },
"leo": { "leo": {
"title": "♌ Leo", "title": "♌ Лев",
"description": "Bright, charismatic, and generous. Loves attention, compliments, and drama. Acts like a conqueror in love but desires admiration and loyalty. Can be self-centered but loyal to those who appreciate them. Can you become a true power couple? Lets check!" "description": "Яркий, харизматичный и великодушный. Любит внимание, комплименты и драму. В любви ведет себя как завоеватель, но хочет восхищения и преданности. Может быть эгоцентричным, но при этом предан тем, кто ценит его по достоинству.<br><bold>",
"description_bold": "Сможете ли вы стать настоящей королевской парой? Давай проверим..."
}, },
"virgo": { "virgo": {
"title": "♍ Virgo", "title": "♍ Дева",
"description": "Rational, reliable, and demanding. Cautious in love, doesn't rush to open up. Values order, honesty, and depth. Can be critical and reserved, but becomes a loyal partner once they trust. Your combination might be incredibly strong—let's discover the details!" "description": "Рациональный, надежный и требовательный. В любви осторожен, не спешит открываться. Ценит порядок, честность и глубину. Может быть критичным и сдержанным, но если доверится становится преданным партнером.<br><bold>",
"description_bold": "Ваше сочетание может быть неожиданно сильным давай узнаем детали..."
}, },
"libra": { "libra": {
"title": "♎ Libra", "title": "♎ Весы",
"description": "Charming, diplomatic, and romantic. Loves harmony, elegant courtship, and intellectual conversation. May hesitate before making decisions but strives for equality and understanding in relationships. What role will you play in this connection? We'll tell you!" "description": "Очаровательный, дипломатичный и романтичный. Любит гармонию, красивое ухаживание и интеллектуальное общение. Может долго колебаться перед выбором, но стремится к равноправию и взаимопониманию в отношениях.<br><bold>",
"description_bold": "Какую роль ты займешь в этой связи? Мы расскажем..."
}, },
"scorpio": { "scorpio": {
"title": "♏ Scorpio", "title": "♏ Скорпион",
"description": "Deep, passionate, and magnetic. Loves seriously and long, but can be jealous and possessive. Trust and emotional connection are vital, and betrayal is unforgivable. Loves deeply. What secrets lie in your union? We know!" "description": "Глубокий, страстный и притягательный. Влюбляется всерьез и надолго, но может быть ревнивым и собственником. Ему важно доверие и эмоциональная связь, а предательства не прощает. Если любит то до конца.<br><bold>",
"description_bold": "Какие тайны скрываются в вашем союзе? Мы знаем..."
}, },
"sagittarius": { "sagittarius": {
"title": "♐ Sagittarius", "title": "♐ Стрелец",
"description": "Free-spirited, energetic, and optimistic. Loves adventure, novelty, and independence. Falls in love quickly but doesn't tolerate pressure. May avoid commitments but becomes a loyal partner with someone who shares their spirit of freedom. Will your union be full of passion or freedom? Let's find out!" "description": "Свободолюбивый, энергичный и оптимистичный. Любит приключения, новизну и независимость. Влюбляется быстро, но не терпит давления. Может избегать обязательств, но с тем, кто разделяет его дух свободы, становится верным партнером.<br><bold>",
"description_bold": "Будет ли ваш союз полон страсти или свободы? Давай выясним.."
}, },
"capricorn": { "capricorn": {
"title": "♑ Capricorn", "title": "♑ Козерог",
"description": "Reserved, goal-oriented, and reliable. Serious and practical in love, dislikes games and frivolity. Observes for a long time but builds strong, lasting relationships once decided. Faithful, but can be too strict. Compatibility with them can be life-changing—want to know more?" "description": "Сдержанный, целеустремленный и надежный. В любви серьезен и практичен, не терпит игр и легкомысленности. Долго присматривается, но если решится строит крепкие, долговременные отношения. Верный, но иногда слишком строгий.<br><bold>",
"description_bold": "Совместимость с ним может быть судьбоносной хочешь узнать больше?"
}, },
"aquarius": { "aquarius": {
"title": "♒ Aquarius", "title": "♒ Водолей",
"description": "Original, independent, and intellectual. Loves freedom, experimentation, and friendship in relationships. Can be emotionally detached but with someone who shares their views, becomes a loyal ally. Your bond with them might be unconventional—lets explore the details!" "description": "Оригинальный, независимый и интеллектуальный. Любит свободу, эксперименты и дружбу в отношениях. Может быть эмоционально отстраненным, но с тем, кто разделяет его взгляды, становится преданным союзником.<br><bold>",
"description_bold": "Твоя связь с ним может быть нестандартной давай разберем детали..."
}, },
"pisces": { "pisces": {
"title": "♓ Pisces", "title": "♓ Рыбы",
"description": "Romantic, intuitive, and dreamy. Falls deeply in love but can be prone to illusions. Seeks spiritual connection, tenderness, and care. May lose themselves in a partner, but if disillusioned, leaves behind a mystery. Will your union be magical or illusory? We know the answer!\n🔮 You form a unique combination, and we can tell you so much more! Let's dive into the details!" "description": "Романтичный, интуитивный и мечтательный. Влюбляется глубоко, но может быть склонен к иллюзиям. Ищет духовную связь, нежность и заботу. Может растворяться в партнере, но если разочаруется уходит, оставляя за собой загадку.<br><bold>",
"description_bold": "Будет ли ваш союз волшебным или иллюзорным? Мы знаем ответ..."
} }
}, },
"/let-scan": { "/let-scan": {
@ -285,13 +501,13 @@
"color": "#1 Astrology" "color": "#1 Astrology"
}, },
"/trial-payment": { "/trial-payment": {
"information-title": "We're Ready to Give You All the Answers, Don't Spend Years in Doubt!", "information-title": "Мы готовы дать тебе все ответы, не трать годы на сомнения!",
"information-description-single": "Ever wondered why some relationships flow smoothly while others feel tense like a tightrope? Coincidence or a sign of destiny? Hands tell more than you think. The lines on your palm are a map of your relationships. <color> there are hidden signs in your life that you haven't noticed yet. <eventDescription> <br><br> Receive a detailed palmistry compatibility analysis and discover the answers that are already written in your destiny.", "information-description-single": "Ты когда-нибудь задумывался, почему одни отношения развиваются легко, а другие будто натянуты, как струна? Совпадение или знак судьбы? <color> в вашей жизни есть скрытые знаки, которые вы ещё не заметили. <eventDescription><br>Получите детальный анализ совместимости и откройте ответы, которые уже написаны в вашей судьбе.",
"information-description-single-color": "<zodiacSign> (<birthdate>)", "information-description-single-color": "<zodiacSign> (<birthdate>)",
"information-description-single-event-description": "Your date <dateEvent> may have been a turning point or a hidden signal.", "information-description-single-event-description": "Ваша дата <dateEvent> могла стать поворотной точкой или скрытым сигналом.",
"information-description-with-partner": "Hands tell more than you think. The lines on your palm are a map of your relationships. <color> — two signs created for depth, but what secrets does your union hold? <eventDescription> Receive a detailed palmistry compatibility analysis and discover the answers that are already written in your destiny.", "information-description-with-partner": "Ты когда-нибудь задумывался, почему одни отношения развиваются легко, а другие будто натянуты, как струна? Совпадение или знак судьбы? <color> — два знака, созданные для глубины, но какие тайны скрывает ваш союз? <eventDescription> Получите детальный анализ совместимости и откройте ответы, которые уже написаны в вашей судьбе.",
"information-description-with-partner-color": "<zodiacSign> (<birthdate>) + <partnerZodiacSign> (<partnerBirthdate>)", "information-description-with-partner-color": "<zodiacSign> (<birthdate>) + <partnerZodiacSign> (<partnerBirthdate>)",
"information-description-with-partner-event-description": "Your date <dateEvent> may have been a turning point or a hidden signal.", "information-description-with-partner-event-description": "Ваша дата <dateEvent> могла стать поворотной точкой или скрытым сигналом.",
"palm_is_ready": { "palm_is_ready": {
"title": "Your Palm Reading <color>", "title": "Your Palm Reading <color>",
"title_color": "Is Ready", "title_color": "Is Ready",
@ -307,10 +523,10 @@
"get_personal_prediction": "Get personal prediction", "get_personal_prediction": "Get personal prediction",
"how_work": { "how_work": {
"title": "How does AURA work?", "title": "How does AURA work?",
"point1_title": "Send us your palm scan", "point1_title": "Подготовка данных о тебе и твоем партнере",
"point1_text": "We analyze your palm lines to get hints about your future", "point1_text": "Мы анализируем все данные чтобы получить максимально точный прогноз",
"point2_title": "Your palm reading is generated", "point2_title": "Твой разбор совместимости генерируется",
"point2_text": "One of our professional palm readers puts together a report filled with hints about your future", "point2_text": "Наша команда профессиональных астрологов уже проверила ваш прогноз и внесла дополнительные заделы указанные вами ранее",
"point3_title": "Start your trial to receive your prediction", "point3_title": "Start your trial to receive your prediction",
"point3_text": "Once youre an AURA member, well send over your prediction report so you can begin living a better life.", "point3_text": "Once youre an AURA member, well send over your prediction report so you can begin living a better life.",
"point4_title": "Talk with a palm reading specialist anytime", "point4_title": "Talk with a palm reading specialist anytime",
@ -370,6 +586,41 @@
"text1": "Questions? Were here to help", "text1": "Questions? Were here to help",
"text2": "Customer Support", "text2": "Customer Support",
"text3": "Help Center" "text3": "Help Center"
},
"reading_ready": {
"title": "Your Compatibility Reading is READY"
},
"get_my_reading": "Get my Reading",
"your-reading": {
"title": "Your Reading",
"subtitle": "Что ты получишь",
"points": {
"v1": {
"point1": "Индивидуальный прогноз на развитие отношений: что ждет вас впереди.",
"point2": "Вопросы и персональные рекомендации от эксперта.",
"point3": "Уникальная информация, которую нельзя найти в стандартных гороскопах.",
"point4": "Полная картина вашей совместимости: уровни в процентах — без иллюзий и догадок.",
"point5": "Подробный астрологический разбор: что сближает и что вызывает напряжение.",
"point6": "Глубокое понимание партнера: как он любит и что для него важно.",
"point7": "Графики, схемы и объяснения для наглядной динамики ваших отношений."
},
"v2": {
"point1": "Полная картина вашей совместимости: уровни в процентах — без иллюзий и догадок.",
"point2": "Подробный астрологический разбор: что сближает и что вызывает напряжение.",
"point3": "Глубокое понимание партнера: как он любит и что для него важно.",
"point4": "Графики, схемы и объяснения для наглядной динамики ваших отношений.",
"point5": "Разбор вашего притяжения и скрытых сил, управляющих вашими чувствами.",
"point6": "Индивидуальный прогноз на развитие отношений: что ждет вас впереди.",
"point7": "Вопросы и персональные рекомендации от эксперта.",
"point8": "Уникальная информация, которую нельзя найти в стандартных гороскопах."
}
},
"description": "To read the full reading, you need to get access through the app for your iPhone"
},
"zodiac-images-container-title": "Personal Compatibility Reading",
"offer_reserved": {
"title": "Offer reserved",
"button": "Get my Reading"
} }
}, },
"/payment": { "/payment": {
@ -503,15 +754,15 @@
}, },
"/depends": { "/depends": {
"with-partner": { "with-partner": {
"title": "Based on our data, only 9% of <gender> born under the <zodiacSign> have a distinct logical clarity—a rare gift. We'll definitely take this trait into account in your palm lines when preparing your Compatibility analysis with <partnerZodiacSign>." "title": "Редкий дар. Только <percent> <gender> знака <zodiacSign> принимают решения с полной логической ясностью. В Разборе ты узнаешь, как твой баланс влияет на <partnerZodiacSign> и что с этим делать."
}, },
"single": { "single": {
"title": "Based on our data, only 9% of <gender> born under the <zodiacSign> sign possess a clear logical clarity—a rare gift. We'll certainly take this trait into account in your lines." "title": "На основонии наших данных лишь 9% <gender> рожденных под знаком <zodiacSign> имеют четкую логическую ясность — редкий дар. Мы обязательно это учтем в вашем анализе на совместимость."
} }
}, },
"/with-heart": { "/with-heart": {
"with-partner": { "with-partner": {
"title": "Your choice is natural. According to our data, 52% of <gender> <zodiacSign> follow their heart. We will consider this in your palm analysis for Compatibility with <partnerZodiacSign>!" "title": "Ваш выбор естественен — <percent> <gender> с знаком <zodiacSign> следуют зову сердца, но гармония в отношениях — это не только чувства. В вашем разборе мы покажем, какие ошибки можно избежать и как использовать совместимость, чтобы укрепить связь с <partnerZodiacSign>."
}, },
"single": { "single": {
"title": "Your choice is natural—based on our data, 51% of <gender> with the <zodiacSign> sign follow their heart. We'll take this into account in your lines!" "title": "Your choice is natural—based on our data, 51% of <gender> with the <zodiacSign> sign follow their heart. We'll take this into account in your lines!"
@ -519,7 +770,7 @@
}, },
"/with-head": { "/with-head": {
"with-partner": { "with-partner": {
"title": "Even among <zodiacSign>, not everything is decided by the heart Based on our data, 35% of <gender> in your sign make decisions based on reason. We will take this aspect into account in your compatibility analysis with <partnerZodiacSign>." "title": "Даже среди <zodiacSign> не всё решает сердце — <percent> <gender> этого знака опираются на разум. В разборе мы покажем, как сбалансировать логику и тревожность, чтобы избежать ошибок и построить гармоничные отношения с <partnerZodiacSign>!"
}, },
"single": { "single": {
"title": "Even among <zodiacSign>, not everything is decided by the heart—based on our data, 35% of <gender> of your sign make decisions guided by reason. We'll factor this into your analysis." "title": "Even among <zodiacSign>, not everything is decided by the heart—based on our data, 35% of <gender> of your sign make decisions guided by reason. We'll factor this into your analysis."
@ -527,10 +778,10 @@
}, },
"/both": { "/both": {
"with-partner": { "with-partner": {
"title": "The facts speak for themselves! According to our data, only 15% of <gender> born under the <zodiacSign> equally follow both their mind and heart. This is the secret to harmonious relationships with <partnerZodiacSign>, and we'll consider this in your readings." "title": "Факты говорят сами за себя! По нашим данным, только <percent> <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений с <partnerZodiacSign>. В Разборе ты узнаешь, как твой баланс влияет на него и что с этим делать."
}, },
"single": { "single": {
"title": "The facts speak for themselves! According to our data, only 15% of <gender> born under the <zodiacSign> sign follow both heart and mind equally. That's the secret to harmonious relationships, and we'll reflect this in your lines." "title": "Факты говорят сами за себя! По нашим данным, только 15% <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений и мы учтем эту особенность в вашем анализе на совместимость."
} }
}, },
"/romantic-gestures": { "/romantic-gestures": {
@ -540,10 +791,81 @@
"answer3": "Don't see the point" "answer3": "Don't see the point"
}, },
"/checking-phone": { "/checking-phone": {
"title": "What Are Your Thoughts on Checking a Partner's Phone or Messages?", "title": "Как вы относитесь к проверке телефона или переписок партнёра?",
"answer1": "Strongly against", "answer1": "Против",
"answer2": "Only in extreme cases", "answer2": "Допускаю иногда",
"answer3": "Fine with it" "answer3": "Нормально"
},
"/complex-relationship-aspect": {
"title": "Какой аспект ваших отношений вам кажется самым сложным?",
"answer1": "Нам трудно понимать друг друга",
"answer2": "Чувствую, что мои эмоции не всегда учитываются",
"answer3": "У нас разные взгляды на будущее",
"answer4": "Часто конфликтуем, не зная почему"
},
"/stress-response": {
"title": "Как вы реагируете на стресс в отношениях?",
"answer1": "Ищем компромисс",
"answer2": "Берем паузу",
"answer3": "Злимся",
"answer4": "Отдаляемся"
},
"/stress-response-result": {
"compromise": {
"title": "Значит, готовы слушать друг друга и находить решения, которые подходят обоим. <bold>",
"title_bold": "Главное — не жертвовать собой, а искать баланс.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
},
"pause": {
"title": "<bold> Это помогает избежать конфликтов и найти верное решение.",
"title_bold": "Лучше сделать шаг назад, чтобы остыть и разобраться в ситуации без лишних эмоций.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
},
"anger": {
"title": "Эмоции берут верх, и это нормально. <bold>",
"title_bold": "Главное — не застревать в гневе, а находить способ выплеснуть его без вреда для отношений.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
},
"distance": {
"title": "Нужно пространство, чтобы переварить ситуацию и разобраться в своих чувствах. <bold>",
"title_bold": "Важно, чтобы дистанция не превращалась в стену.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
}
},
"/what-add-to-analysis": {
"title": "Что нам еще добавить в твой персональный Разбор",
"5_compatibility_zones": "5 ключевых зон совместимости",
"analyzing_mistakes_relationships": "Разбор ошибок в отношениях",
"astrological_triggers": "Астрологические триггеры",
"relationship_timing": "Тайминг отношений",
"practices_strengthen_communication": "Практики для усиления связи",
"scenarios_times_crisis": "Сценарии в кризисные периоды",
"astrological_view_conflicts": "Астрологический взгляд на конфликты",
"energy_compatibility": "Энергетическая совместимость"
},
"/loading": {
"loaders": {
"title-1-1": "Analyzing both your and your partner's key traits...",
"title-1-2": "Reading astrological parameters...",
"title-2-1": "Calculating your unique compatibility chart...",
"title-2-2": "Checking key intersections of your destinies...",
"title-3-1": "Comparing your match across 1,120,000 potential astrological combinations...",
"title-3-2": "Inputting data: evaluating the depth of your connection—almost there..."
},
"modals": {
"title-1": "Clarifying question.",
"description-1": "Have you noticed recurring cycles in your life?",
"answer-1-left": "NO",
"answer-1-right": "YES",
"title-2": "Clarifying question.",
"description-2": "What's more important to you: fate or choice?",
"answer-2-left": "CHOICE",
"answer-2-right": "FATE",
"title-3": "Clarifying question.",
"description-3": "Do you believe there's more to love than chance?",
"answer-3-left": "NO",
"answer-3-right": "YES"
}
}, },
"zodiac_signs": { "zodiac_signs": {
"aries": "Aries", "aries": "Aries",
@ -558,5 +880,19 @@
"capricorn": "Capricorn", "capricorn": "Capricorn",
"aquarius": "Aquarius", "aquarius": "Aquarius",
"pisces": "Pisces" "pisces": "Pisces"
},
"/choose-place": {
"title": "Your Compatibility<br>Reading is READY",
"subtitle": "<count><br>страниц подробного<br>разбора.",
"subtitle_count": "57",
"description1": "Ты можешь купить его на всегда или БЕСПЛАТНО прочитать у нас в приложении.",
"description2": "Ваш разбор по Совместимости действителен 10 минут, после чего он будет удален.",
"button1": "Buy",
"button2": "Read for FREE<br>in the App"
},
"/code-instruction": {
"instruction_point_1": "1. Copy Your Access Code",
"instruction_point_2": "2. Download the App and Enter Your Access Code",
"not_share_description": "Enter your access code in the app to access Your Personalized Reading. Do not share your code with anyone."
} }
} }

View File

@ -167,33 +167,17 @@
}, },
"/your-analysis": { "/your-analysis": {
"title": "Твой анализ", "title": "Твой анализ",
"point1": "Анализирую ваши природные склонности в отношениях...", "point1": "Разбираем, где вы сами создаете преграды в любви...",
"point2": "Точки риска, которые могут привести к сложным отношениям...", "point2": "Анализ повторяющихся сценариев в твоих отношениях...",
"point3": "Формируем динамику отношений заложеную в тебе...", "point3": "Расшифровываем твою силу в любви и на что нужно обратить внимание...",
"point4": "Разбираем, где вы сами создаете преграды в любви.", "point4": "Разбираем ваш личный потенциал, чтобы не тратить время на несовместимость..."
"point5": "Обработка твоих сильных и слабых сторон в любви...",
"point6": "Анализ повторяющихся сценариев в твоих отношениях...",
"point7": "Расшифровываем твою силу в любви и на что нужно обратить внимание...",
"point8": "Раскрываем ваш потенциал в отношениях...",
"point9": "Обрабатываем твой эмоциональный ритм и стиль привязанности...",
"point10": "Разбираем, что ведет вас к счастью в отношениях...",
"point11": "Анализируем, какие качества должны быть у вашего идеального партнера",
"point12": "Разбираем, где вы сами создаете преграды в любви.",
"point13": "Разбираем ваш личный потенциал, чтобы не тратить время на несовместимость."
}, },
"/partner-analysis": { "/partner-analysis": {
"title": "Анализ твоего партнера", "title": "Анализ твоего партнера",
"point1": "Подбор паттернов совпадения и не совпадения...", "point1": "Анализируем что движет им на самом деле...",
"point2": "Выявляем сильные и уязвимые стороны связи...", "point2": "Рассчитываем, насколько он совместим с вами на глубинном уровне...",
"point3": "Выявляем, как он влияет на вашу совместимость...", "point3": "Ищем его черты, которые могут создавать сложности...",
"point4": "Анализируем что движет им на самом деле...", "point4": "Выявляем, как он влияет на вашу совместимость..."
"point5": "Составляем, какие аспекты требуют внимания...",
"point6": "Анализируем ключевые зоны ваших отношений...",
"point7": "Разбираем его сильные и слабые стороны в отношениях...",
"point8": "Рассчитываем насколько он совместим с вами на глубинном уровне",
"point9": "Проверяем его сильные стороны в паре насколько они подходят вам...",
"point10": "Ищем его черты которые могут создавать сложности...",
"point11": "Подбор паттернов совпадения и не совпадения..."
}, },
"/result-analysis": { "/result-analysis": {
"title": "Какой результат разбора отношений вам важнее всего?", "title": "Какой результат разбора отношений вам важнее всего?",
@ -229,7 +213,12 @@
} }
}, },
"/review": { "/review": {
"username": "Emma Johnson", "single": {
"username": "Alex Reyes",
"date": "April 8",
"text": "I kept ending up in failed relationships — things started well, then fell apart. It was exhausting, and I began to wonder if the problem was me. Your service felt different from the start. Instead of generic tips, I got clear, personal insights into my patterns and real compatibility. That first compatibility analysis clicked — it was so accurate, I finally stopped second-guessing myself. Now Im in a relationship that feels natural and real. No more forcing — just choosing right."
},
"username": "Adam Torres",
"date": "Jan 25", "date": "Jan 25",
"text": "I used to keep running into failed relationships—things would start off well, but then everything would fall apart. It was exhausting, and I started wondering if the problem was me. Your service immediately felt different: instead of generic advice and random matches, I got precise insights about who I actually have a real chance of building something strong with. I still remember seeing my first compatibility analysis—it aligned so perfectly that I just knew: this was it. Now, I waste less time on pointless connections and am finally building a relationship that feels effortless and right." "text": "I used to keep running into failed relationships—things would start off well, but then everything would fall apart. It was exhausting, and I started wondering if the problem was me. Your service immediately felt different: instead of generic advice and random matches, I got precise insights about who I actually have a real chance of building something strong with. I still remember seeing my first compatibility analysis—it aligned so perfectly that I just knew: this was it. Now, I waste less time on pointless connections and am finally building a relationship that feels effortless and right."
}, },
@ -319,9 +308,14 @@
"answer1": "Начало", "answer1": "Начало",
"answer2": "Долгие отношения", "answer2": "Долгие отношения",
"answer3": "Развиваются непонятно куда", "answer3": "Развиваются непонятно куда",
"answer4": "Разрыв/кризис" "answer4": "Разрыв/кризис",
"answer5": "Single"
}, },
"/relationship-status-result": { "/relationship-status-result": {
"single": {
"title": "Вам кажется, что достойных партнёров нет или вы сталкиваетесь «не с теми»? Дело в несовместимых выборах.",
"text": "Наша система прогнозирует совместимость с точностью до 98%, помогая найти подходящего партнёра и открывая новые перспективы в личной жизни."
},
"start": { "start": {
"title": "Как понять, что ваш партнер — действительно “ваш человек” и не ошибиться?", "title": "Как понять, что ваш партнер — действительно “ваш человек” и не ошибиться?",
"text": "Понимание эмоций партнёра — ключ к гармоничным отношениям. Наша система прогнозирует совместимость с точностью до 98% и мы дадим тебе точные отчеты." "text": "Понимание эмоций партнёра — ключ к гармоничным отношениям. Наша система прогнозирует совместимость с точностью до 98% и мы дадим тебе точные отчеты."
@ -402,8 +396,8 @@
}, },
"/date-event": { "/date-event": {
"single": { "single": {
"title": "Enter a Significant Date Important to You.", "title": "Значимая дата важная для вас",
"text": "💫 A significant date can help reveal planetary influences on your life and provide a more personalized analysis." "text": "Она поможет лучше понять, чего вы ищете в любви, и даст более персонализированный анализ."
}, },
"relationship": { "relationship": {
"title": "Значимая дата важная для вас или вашей пары", "title": "Значимая дата важная для вас или вашей пары",
@ -747,7 +741,7 @@
"title": "Редкий дар. Только <percent> <gender> знака <zodiacSign> принимают решения с полной логической ясностью. В Разборе ты узнаешь, как твой баланс влияет на <partnerZodiacSign> и что с этим делать." "title": "Редкий дар. Только <percent> <gender> знака <zodiacSign> принимают решения с полной логической ясностью. В Разборе ты узнаешь, как твой баланс влияет на <partnerZodiacSign> и что с этим делать."
}, },
"single": { "single": {
"title": "На основонии наших данных лишь 9% <gender> рожденных под знаком <zodiacSign> имеют четкую логическую ясность — редкий дар. Мы обязательно это учтем в вашем анализе на совместимость." "title": "Лишь <percent> <gender> <zodiacSign> решают с полной ясностью — это влияет и на выбор, и на будущее отношений. В разборе ты узнаешь, как работает твой внутренний выбор и что изменить, чтобы вовремя впустить любовь."
} }
}, },
"/with-heart": { "/with-heart": {
@ -755,7 +749,7 @@
"title": "Ваш выбор естественен — <percent> <gender> с знаком <zodiacSign> следуют зову сердца, но гармония в отношениях — это не только чувства. В вашем разборе мы покажем, какие ошибки можно избежать и как использовать совместимость, чтобы укрепить связь с <partnerZodiacSign>." "title": "Ваш выбор естественен — <percent> <gender> с знаком <zodiacSign> следуют зову сердца, но гармония в отношениях — это не только чувства. В вашем разборе мы покажем, какие ошибки можно избежать и как использовать совместимость, чтобы укрепить связь с <partnerZodiacSign>."
}, },
"single": { "single": {
"title": "Your choice is natural—based on our data, 51% of <gender> with the <zodiacSign> sign follow their heart. We'll take this into account in your lines!" "title": "Вы не одни — <percent> <gender> <zodiacSign> следуют сердцу, даже без партнёра. Это не слабость, а особенность вашей души. В разборе мы покажем, что мешает любви войти в вашу жизнь и как совместимость с другими поможет создать искреннюю и глубокую связь."
} }
}, },
"/with-head": { "/with-head": {
@ -763,7 +757,7 @@
"title": "Даже среди <zodiacSign> не всё решает сердце — <percent> <gender> этого знака опираются на разум. В разборе мы покажем, как сбалансировать логику и тревожность, чтобы избежать ошибок и построить гармоничные отношения с <partnerZodiacSign>!" "title": "Даже среди <zodiacSign> не всё решает сердце — <percent> <gender> этого знака опираются на разум. В разборе мы покажем, как сбалансировать логику и тревожность, чтобы избежать ошибок и построить гармоничные отношения с <partnerZodiacSign>!"
}, },
"single": { "single": {
"title": "Even among <zodiacSign>, not everything is decided by the heart—based on our data, 35% of <gender> of your sign make decisions guided by reason. We'll factor this into your analysis." "title": "Даже среди <zodiacSign> <percent> <gender> выбирают разум. Но что делать, если душа жаждет любви, а ум мешает? В разборе мы покажем, как сбалансировать логику и тревожность, чтобы избежать ошибок и построить глубокую связь"
} }
}, },
"/both": { "/both": {
@ -771,7 +765,7 @@
"title": "Факты говорят сами за себя! По нашим данным, только <percent> <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений с <partnerZodiacSign>. В Разборе ты узнаешь, как твой баланс влияет на него и что с этим делать." "title": "Факты говорят сами за себя! По нашим данным, только <percent> <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений с <partnerZodiacSign>. В Разборе ты узнаешь, как твой баланс влияет на него и что с этим делать."
}, },
"single": { "single": {
"title": "Факты говорят сами за себя! По нашим данным, только 15% <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений и мы учтем эту особенность в вашем анализе на совместимость." "title": "Лишь <percent> <gender> <zodiacSign> одинаково полагаются на разум и сердце — а в этом часто и кроется секрет гармонии. В разборе ты узнаешь, как внутренний баланс влияет на притяжение и что мешает построить настоящую связь."
} }
}, },
"/romantic-gestures": { "/romantic-gestures": {
@ -786,6 +780,24 @@
"answer2": "Допускаю иногда", "answer2": "Допускаю иногда",
"answer3": "Нормально" "answer3": "Нормально"
}, },
"/partner-password": {
"title": "Когда вы начнёте встречаться, как будете относиться к паролю на телефоне партнёра?",
"answer1": "Нормально",
"answer2": "Не нравится",
"answer3": "Обменяемся паролями"
},
"/your-fear": {
"title": "Вы больше боитесь…",
"answer1": "Быть в одиночестве",
"answer2": "Потерять себя в отношениях",
"answer3": "Снова сделать неправильный выбор"
},
"/your-inclination": {
"title": "Когда человек вам нравится, вы склонны…",
"answer1": "Сразу влюбляться",
"answer2": "Осторожничать",
"answer3": "Долго присматриваться"
},
"/complex-relationship-aspect": { "/complex-relationship-aspect": {
"title": "Какой аспект ваших отношений вам кажется самым сложным?", "title": "Какой аспект ваших отношений вам кажется самым сложным?",
"answer1": "Нам трудно понимать друг друга", "answer1": "Нам трудно понимать друг друга",
@ -798,7 +810,14 @@
"answer1": "Ищем компромисс", "answer1": "Ищем компромисс",
"answer2": "Берем паузу", "answer2": "Берем паузу",
"answer3": "Злимся", "answer3": "Злимся",
"answer4": "Отдаляемся" "answer4": "Отдаляемся",
"single": {
"title": "Как вы ведетя себя в соре… будь вы в отношениях?",
"answer1": "Ищу компромис",
"answer2": "Беру паузу",
"answer3": "Вспыхиваю",
"answer4": "Закрываюсь"
}
}, },
"/stress-response-result": { "/stress-response-result": {
"compromise": { "compromise": {
@ -820,18 +839,73 @@
"title": "Нужно пространство, чтобы переварить ситуацию и разобраться в своих чувствах. <bold>", "title": "Нужно пространство, чтобы переварить ситуацию и разобраться в своих чувствах. <bold>",
"title_bold": "Важно, чтобы дистанция не превращалась в стену.", "title_bold": "Важно, чтобы дистанция не превращалась в стену.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать." "text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
},
"single": {
"compromise": {
"title": "Это означает полную готовность к диалогу и поиску решений, <bold>",
"title_bold": "где важно слышать и себя, и будущего партнёра.",
"text": "Наши разборы уже помогли 500+ одиноким людям впустить любовь. Ты увидишь свои сильные стороны и то, что нужно для зрелых будующих отношений."
},
"pause": {
"title": "<bold>",
"title_bold": "Иногда шаг назад — способ остыть, избежать конфликта и выбрать зрелое решение.",
"text": "Этот гайд уже помог 500+ парам посмотреть на свои отношения по-новому. Вы узнаете, что у вас работает хорошо, а над чем стоит поработать."
},
"anger": {
"title": "Эмоции — это нормально. <bold>",
"title_bold": "Главное — проживать их без вреда себе и будущим отношениям.",
"text": "Наши разборы помогли 500+ одиноким людям переосмыслить их чувства. Ты поймёшь, что уже ведёт тебя к любви, а что стоит доработать."
},
"distance": {
"title": "Закрытость нужна, чтобы понять себя — это не слабость. Главное — позже открыться любви.",
"title_bold": "",
"text": "Наши разборы помогли 500+ одиноким людям переосмыслить себя и установки. Ты поймёшь, что для тебя уже работает — и что стоит изменить для настоящих отношений."
}
} }
}, },
"/what-add-to-analysis": { "/what-add-to-analysis": {
"title": "Что нам еще добавить в твой персональный Разбор", "title": "Что нам еще добавить в твой персональный Разбор",
"5_compatibility_zones": "5 ключевых зон совместимости", "former_partner": "Бывшего партнера",
"potential_partner": "Потенциального партнера",
"analyzing_mistakes_relationships": "Разбор ошибок в отношениях", "analyzing_mistakes_relationships": "Разбор ошибок в отношениях",
"astrological_triggers": "Астрологические триггеры", "astrological_triggers": "Астрологические триггеры",
"relationship_timing": "Тайминг отношений", "relationship_timing": "Тайминг отношений",
"practices_strengthen_communication": "Практики для усиления связи", "practices_strengthen_communication": "Практики для усиления связи",
"scenarios_times_crisis": "Сценарии в кризисные периоды", "scenarios_times_crisis": "Сценарии в кризисные периоды",
"astrological_view_conflicts": "Астрологический взгляд на конфликты", "astrological_view_conflicts": "Астрологический взгляд на конфликты",
"energy_compatibility": "Энергетическая совместимость" "energy_compatibility": "Энергетическая совместимость",
"key_compatibility_areas": "Добавить 2 ключевые зоны совместимости",
"talking_about_future": "Разговоры о будущем",
"single": {
"potential_partner": "Потенциального партнера",
"family_love_model": "Чью модель любви вы повторяете из вашей семьи",
"strong_connection_date": "Дата когда тебя ждёт сильная связь",
"dangerous_partner_type": "Предсказание “Опасного” типа партнера",
"relationship_karma": "Карма отношений",
"when_not_start_relationship": "Когда лучше не начинать отношения",
"scenario_next_love": "Сценарий следующей любви",
"what_radiate_in_relationship": "Что ты излучаешь в отношениях",
"how_partners_see_you": "Как тебя видят партнеры в отношениях",
"key_compatibility_areas": "Добавить 2 ключевые зоны совместимости"
}
},
"/potential-partner-name": {
"title": "Имя потенциального партнера",
"text": "Каждое имя несёт вибрацию вашей будущей связи. Укажите его, чтобы понять, куда ведёт это притяжение.",
"placeholder": "Имя"
},
"/potential-partner-birthdate": {
"title": "Дата рождения потенциального партнера",
"text": "Дата рождения расскажет о типе привязанности и к чему может привести ваша связь."
},
"/former-partner-name": {
"title": "Имя бывшего партнера",
"text": "Каждое имя несёт в себе код. По нему мы сможем проанализировать насколько он подходил тебе.",
"placeholder": "Имя"
},
"/former-partner-birthdate": {
"title": "Дата рождения бывшего партнера",
"text": "Она нужна, чтобы понять, почему между вами именно так всё и происходило."
}, },
"/loading": { "/loading": {
"loaders": { "loaders": {
@ -840,7 +914,15 @@
"title-2-1": "Calculating your unique compatibility chart...", "title-2-1": "Calculating your unique compatibility chart...",
"title-2-2": "Checking key intersections of your destinies...", "title-2-2": "Checking key intersections of your destinies...",
"title-3-1": "Comparing your match across 1,120,000 potential astrological combinations...", "title-3-1": "Comparing your match across 1,120,000 potential astrological combinations...",
"title-3-2": "Inputting data: evaluating the depth of your connection—almost there..." "title-3-2": "Inputting data: evaluating the depth of your connection—almost there...",
"single": {
"title-1-1": "Анализируем твои ключевые качества...",
"title-1-2": "Считываем астрологические параметры...",
"title-2-1": "Рассчет твоей уникальной карты совместимости...",
"title-2-2": "Формируем персональную стратегию любви…",
"title-3-1": "Анализ тебя среди 1 120 000 возможных астрологических комбинаций...",
"title-3-2": "Проверяем точность прогноза — почти готово…"
}
}, },
"modals": { "modals": {
"title-1": "Clarifying question.", "title-1": "Clarifying question.",

View File

@ -74,7 +74,7 @@ export interface IAnswersSessionCompatibilityV3 {
export interface IAnswersSessionCompatibilityV4 { export interface IAnswersSessionCompatibilityV4 {
what_aspects: 'love_relationships' | 'health_vitality' | 'career_destiny' | 'life_transitions', // Type: string, optional - 'love_relationships' | 'health_vitality' | 'career_destiny'; what_aspects: 'love_relationships' | 'health_vitality' | 'career_destiny' | 'life_transitions', // Type: string, optional - 'love_relationships' | 'health_vitality' | 'career_destiny';
relationship_status: 'start' | 'in_relationship' | 'developing' | 'crisis', // Type: string, optional - 'single' | 'in_relationship'; relationship_status: 'single' | 'start' | 'in_relationship' | 'developing' | 'crisis', // Type: string, optional - 'single' | 'in_relationship';
result_analysis: 'find_answers' | 'improve_relationships' | 'what_awaits' | 'continue_relationship' | 'just_interested'; result_analysis: 'find_answers' | 'improve_relationships' | 'what_awaits' | 'continue_relationship' | 'just_interested';
calculate_in_advance: 'yes' | 'no' | 'yes_may_be' | 'yes_but_not_fate', // Type: string, optional - 'single' | 'in_relationship'; calculate_in_advance: 'yes' | 'no' | 'yes_may_be' | 'yes_but_not_fate', // Type: string, optional - 'single' | 'in_relationship';
element_resonates: 'water' | 'fire' | 'air' | 'earth' | 'light' | 'darkness', // Type: string, optional - 'water' | 'fire' | 'air' | 'earth'; element_resonates: 'water' | 'fire' | 'air' | 'earth' | 'light' | 'darkness', // Type: string, optional - 'water' | 'fire' | 'air' | 'earth';
@ -95,6 +95,9 @@ export interface IAnswersSessionCompatibilityV4 {
what_add_to_analysis: string // '5_compatibility_zones' | 'analyzing_mistakes_relationships' | what_add_to_analysis: string // '5_compatibility_zones' | 'analyzing_mistakes_relationships' |
// 'astrological_triggers' | 'relationship_timing' | 'practices_strengthen_communication' | // 'astrological_triggers' | 'relationship_timing' | 'practices_strengthen_communication' |
// 'scenarios_times_crisis' | 'astrological_view_conflicts' | 'energy_compatibility' // 'scenarios_times_crisis' | 'astrological_view_conflicts' | 'energy_compatibility'
partner_password: "normal" | "not_like" | "exchange_passwords";
your_fear: "loneliness" | "lose_yourself" | "make_mistake";
your_inclination: "fall_in_love" | "be_careful" | "look_closely";
} }
export interface IAnswersSessionChats { export interface IAnswersSessionChats {

View File

@ -1,9 +1,9 @@
import { import {
useCallback, // useCallback,
useEffect, useEffect,
useLayoutEffect, useLayoutEffect,
useMemo, // useMemo,
useRef, // useRef,
useState, useState,
} from "react"; } from "react";
import { import {
@ -12,21 +12,21 @@ import {
Navigate, Navigate,
Outlet, Outlet,
useLocation, useLocation,
useNavigate, // useNavigate,
useSearchParams, useSearchParams,
} from "react-router-dom"; } from "react-router-dom";
import { useAuth } from "@/auth"; import { useAuth } from "@/auth";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import routes, { import routes, {
hasNavigation, // hasNavigation,
getRouteBy, getRouteBy,
hasNoFooter, // hasNoFooter,
hasNoHeader, // hasNoHeader,
hasNavbarFooter, // hasNavbarFooter,
hasFullDataModal, // hasFullDataModal,
palmistryV1Prefix, palmistryV1Prefix,
chatsPrefix, // chatsPrefix,
palmistryV2Prefix, palmistryV2Prefix,
emailMarketingV1Prefix, emailMarketingV1Prefix,
compatibilityV2Prefix, compatibilityV2Prefix,
@ -40,10 +40,10 @@ import CreateProfilePage from "../CreateProfilePage";
import EmailEnterPage from "../EmailEnterPage"; import EmailEnterPage from "../EmailEnterPage";
import PaymentPage from "../PaymentPage"; import PaymentPage from "../PaymentPage";
import WallpaperPage from "../WallpaperPage"; import WallpaperPage from "../WallpaperPage";
import NotFoundPage from "../NotFoundPage"; // import NotFoundPage from "../NotFoundPage";
import Header from "../Header"; // import Header from "../Header";
import Navbar from "../Navbar"; // import Navbar from "../Navbar";
import Footer from "../Footer"; // import Footer from "../Footer";
import "./styles.css"; import "./styles.css";
import DidYouKnowPage from "../DidYouKnowPage"; import DidYouKnowPage from "../DidYouKnowPage";
import FreePeriodInfoPage from "../FreePeriodInfoPage"; import FreePeriodInfoPage from "../FreePeriodInfoPage";
@ -54,10 +54,10 @@ import PriceListPage from "../PriceListPage";
import CompatResultPage from "../CompatResultPage"; import CompatResultPage from "../CompatResultPage";
import HomePage from "../HomePage"; import HomePage from "../HomePage";
import UserCallbacksPage from "../UserCallbacksPage"; import UserCallbacksPage from "../UserCallbacksPage";
import NavbarFooter, { INavbarHomeItems } from "../NavbarFooter"; // import NavbarFooter, { INavbarHomeItems } from "../NavbarFooter";
import { EPathsFromHome } from "@/store/siteConfig"; // import { EPathsFromHome } from "@/store/siteConfig";
import { APNG } from "apng-js"; import { APNG } from "apng-js";
import { useApi, useApiCall } from "@/api"; // import { useApi, useApiCall } from "@/api";
// import { Asset } from "@/api/resources/Assets"; // import { Asset } from "@/api/resources/Assets";
import PaymentResultPage from "../PaymentPage/results"; import PaymentResultPage from "../PaymentPage/results";
import PaymentSuccessPage from "../PaymentPage/results/SuccessPage"; import PaymentSuccessPage from "../PaymentPage/results/SuccessPage";
@ -72,60 +72,60 @@ import ThermalResult from "../pages/ThermalResult";
import MoonPhaseTrackerResult from "../pages/MoonPhaseTrackerResult"; import MoonPhaseTrackerResult from "../pages/MoonPhaseTrackerResult";
import EnergyVampirismResult from "../pages/EnergyVampirismResult"; import EnergyVampirismResult from "../pages/EnergyVampirismResult";
import NameHoroscopeResult from "../pages/NameHoroscopeResult"; import NameHoroscopeResult from "../pages/NameHoroscopeResult";
import GenderPage from "../pages/Gender"; // import GenderPage from "../pages/Gender";
import QuestionnairePage from "../pages/Questionnaire"; // import QuestionnairePage from "../pages/Questionnaire";
import GoalSetupPage from "../pages/GoalSetup"; // import GoalSetupPage from "../pages/GoalSetup";
import HyperPersonalizedAstrologyPage from "../pages/HyperPersonalizedAstrologyPage"; // import HyperPersonalizedAstrologyPage from "../pages/HyperPersonalizedAstrologyPage";
import NoBirthtimePage from "../pages/NoBirthtime"; // import NoBirthtimePage from "../pages/NoBirthtime";
import LoadingInRelationshipPage from "../pages/LoadingInRelationship"; // import LoadingInRelationshipPage from "../pages/LoadingInRelationship";
import QuestionnaireIntermediatePage from "../pages/QuestionnaireIntermediate"; // import QuestionnaireIntermediatePage from "../pages/QuestionnaireIntermediate";
import RelationshipAlmostTherePage from "../pages/RelationshipAlmostThere"; // import RelationshipAlmostTherePage from "../pages/RelationshipAlmostThere";
import Modal from "../Modal"; // import Modal from "../Modal";
import FullDataModal from "../FullDataModal"; // import FullDataModal from "../FullDataModal";
import SingleZodiacInfoPage from "../pages/SingleZodiacInfo"; // import SingleZodiacInfoPage from "../pages/SingleZodiacInfo";
import ProblemsPage from "../pages/Problems"; // import ProblemsPage from "../pages/Problems";
import WorksRouterPage from "../pages/WorksRouter"; // import WorksRouterPage from "../pages/WorksRouter";
import NotAlonePage from "../pages/NotAlone"; // import NotAlonePage from "../pages/NotAlone";
import AlmostTherePage from "../pages/AlmostThere"; // import AlmostTherePage from "../pages/AlmostThere";
import AllRightPage from "../pages/AllRight"; // import AllRightPage from "../pages/AllRight";
import PartnerRightPlacePage from "../pages/PartnerRightPlace"; // import PartnerRightPlacePage from "../pages/PartnerRightPlace";
import PartnerThingPage from "../pages/PartnerThing"; // import PartnerThingPage from "../pages/PartnerThing";
import PartnerTotallyNormalPage from "../pages/PartnerTotallyNormal"; // import PartnerTotallyNormalPage from "../pages/PartnerTotallyNormal";
import WithHeartPage from "../pages/WithHeart"; // import WithHeartPage from "../pages/WithHeart";
import WithHeadPage from "../pages/WithHead"; // import WithHeadPage from "../pages/WithHead";
import BothPage from "../pages/Both"; // import BothPage from "../pages/Both";
import RelationshipZodiacInfoPage from "../pages/RelationshipZodiacInfo"; // import RelationshipZodiacInfoPage from "../pages/RelationshipZodiacInfo";
import Satisfied from "../pages/Satisfied"; // import Satisfied from "../pages/Satisfied";
import AboutUsPage from "../pages/AboutUs"; // import AboutUsPage from "../pages/AboutUs";
import LoadingProfilePage from "../pages/LoadingProfile"; // import LoadingProfilePage from "../pages/LoadingProfile";
import EmailConfirmPage from "../pages/EmailConfirm"; // import EmailConfirmPage from "../pages/EmailConfirm";
import OnboardingPage from "../pages/Onboarding"; // import OnboardingPage from "../pages/Onboarding";
import TrialChoicePage from "../pages/TrialChoice"; // import TrialChoicePage from "../pages/TrialChoice";
import TrialPaymentPage from "../pages/TrialPayment"; // import TrialPaymentPage from "../pages/TrialPayment";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import AdditionalDiscount from "../pages/AdditionalDiscount"; // import AdditionalDiscount from "../pages/AdditionalDiscount";
import TrialPaymentWithDiscount from "../pages/TrialPaymentWithDiscount"; // import TrialPaymentWithDiscount from "../pages/TrialPaymentWithDiscount";
import MarketingLanding from "../pages/EmailLetters/MarketingLanding"; // import MarketingLanding from "../pages/EmailLetters/MarketingLanding";
import MarketingTrialPayment from "../pages/EmailLetters/MarketingTrialPayment"; // import MarketingTrialPayment from "../pages/EmailLetters/MarketingTrialPayment";
import { EUserDeviceType } from "@/store/userConfig"; import { EUserDeviceType } from "@/store/userConfig";
import TryAppPage from "../pages/TryApp"; // import TryAppPage from "../pages/TryApp";
import AdditionalPurchases from "../pages/AdditionalPurchases"; // import AdditionalPurchases from "../pages/AdditionalPurchases";
import AddReportPage from "../pages/AdditionalPurchases/pages/AddReport"; // import AddReportPage from "../pages/AdditionalPurchases/pages/AddReport";
import UnlimitedReadingsPage from "../pages/AdditionalPurchases/pages/UnlimitedReadings"; // import UnlimitedReadingsPage from "../pages/AdditionalPurchases/pages/UnlimitedReadings";
import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultation"; // import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultation";
import StepsManager from "@/components/palmistry/steps-manager/steps-manager"; // import StepsManager from "@/components/palmistry/steps-manager/steps-manager";
import Advisors from "../pages/Advisors"; import Advisors from "../pages/Advisors";
import AdvisorChatPage from "../pages/AdvisorChat"; import AdvisorChatPage from "../pages/AdvisorChat";
import SuccessPaymentPage from "../pages/SinglePaymentPage/ResultPayment/SuccessPaymentPage"; // import SuccessPaymentPage from "../pages/SinglePaymentPage/ResultPayment/SuccessPaymentPage";
import FailPaymentPage from "../pages/SinglePaymentPage/ResultPayment/FailPaymentPage"; // import FailPaymentPage from "../pages/SinglePaymentPage/ResultPayment/FailPaymentPage";
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement"; // import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
import GetInformationPartnerPage from "../pages/GetInformationPartner"; import GetInformationPartnerPage from "../pages/GetInformationPartner";
import BirthPlacePage from "../pages/BirthPlacePage"; // import BirthPlacePage from "../pages/BirthPlacePage";
import LoadingPage from "../pages/LoadingPage"; // import LoadingPage from "../pages/LoadingPage";
import { EProductKeys, productUrls } from "@/data/products"; // import { EProductKeys, productUrls } from "@/data/products";
import SinglePaymentPage from "../pages/SinglePaymentPage"; // import SinglePaymentPage from "../pages/SinglePaymentPage";
import ABDesignV1Routes from "@/routerComponents/ABDesign/v1"; // import ABDesignV1Routes from "@/routerComponents/ABDesign/v1";
import MikeV1Routes from "@/routerComponents/Mike/v1"; // import MikeV1Routes from "@/routerComponents/Mike/v1";
import metricService from "@/services/metric/metricService"; import metricService from "@/services/metric/metricService";
import PalmistryV1Routes from "@/routerComponents/Palmistry/v1"; import PalmistryV1Routes from "@/routerComponents/Palmistry/v1";
import AdditionalPurchasesPalmistry from "../palmistry/AdditionalPurchases"; import AdditionalPurchasesPalmistry from "../palmistry/AdditionalPurchases";
@ -134,7 +134,7 @@ import AddGuides from "../palmistry/AdditionalPurchases/pages/AddGuides";
import SkipTrial from "../palmistry/AdditionalPurchases/pages/SkipTrial"; import SkipTrial from "../palmistry/AdditionalPurchases/pages/SkipTrial";
import { parseQueryParams } from "@/services/url"; import { parseQueryParams } from "@/services/url";
import Auth from "../pages/Auth"; import Auth from "../pages/Auth";
import ChatsRoutes from "@/routerComponents/Chats"; // import ChatsRoutes from "@/routerComponents/Chats";
import CookieYesController from "@/routerComponents/CookieYesController"; import CookieYesController from "@/routerComponents/CookieYesController";
import PalmistryV2Routes from "@/routerComponents/Palmistry/v2"; import PalmistryV2Routes from "@/routerComponents/Palmistry/v2";
import MarketingLandingV1Routes from "@/routerComponents/MarketingLanding/v1"; import MarketingLandingV1Routes from "@/routerComponents/MarketingLanding/v1";
@ -147,6 +147,8 @@ import { useUnleashClient } from "@unleash/proxy-client-react";
import { useSession } from "@/hooks/session/useSession"; import { useSession } from "@/hooks/session/useSession";
import { getSourceByPathname } from "@/utils/source.utils"; import { getSourceByPathname } from "@/utils/source.utils";
import "../palmistry/palmistry-container/palmistry-container.css"
const isProduction = import.meta.env.MODE === "production"; const isProduction = import.meta.env.MODE === "production";
const gaMeasurementId = import.meta.env.AURA_GA_MEASUREMENT_ID; const gaMeasurementId = import.meta.env.AURA_GA_MEASUREMENT_ID;
@ -226,17 +228,17 @@ function App(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const isForceShortPath = useSelector(selectors.selectIsForceShortPath); // const isForceShortPath = useSelector(selectors.selectIsForceShortPath);
const { gender: genderFromStore } = useSelector( // const { gender: genderFromStore } = useSelector(
selectors.selectQuestionnaire // selectors.selectQuestionnaire
); // );
const birthdateFromStore = useSelector(selectors.selectBirthdate); // const birthdateFromStore = useSelector(selectors.selectBirthdate);
const { birthPlace: birthPlaceFromStore } = useSelector( // const { birthPlace: birthPlaceFromStore } = useSelector(
selectors.selectQuestionnaire // selectors.selectQuestionnaire
); // );
const gender = user?.profile?.gender || genderFromStore; // const gender = user?.profile?.gender || genderFromStore;
const birthdate = user?.profile?.birthday || birthdateFromStore; // const birthdate = user?.profile?.birthday || birthdateFromStore;
const birthPlace = user?.profile?.birthplace || birthPlaceFromStore; // const birthPlace = user?.profile?.birthplace || birthPlaceFromStore;
useEffect(() => { useEffect(() => {
// metricService.initMetricAB() // metricService.initMetricAB()
@ -495,14 +497,14 @@ function App(): JSX.Element {
</Route> </Route>
</Route> </Route>
<Route path="*" element={<ABDesignV1Routes />} /> {/* <Route path="*" element={<ABDesignV1Routes />} /> */}
<Route path="*" element={<Navigate to={getRouteBy(subscriptionStatus)} />} /> <Route path="*" element={<Navigate to={getRouteBy(subscriptionStatus)} />} />
{/* ROUTES OFF */} {/* ROUTES OFF */}
{/* <Route path="*" element={<ABDesignV1Routes />} /> */} {/* <Route path="*" element={<ABDesignV1Routes />} /> */}
<Route path={`${chatsPrefix}/*`} element={<ChatsRoutes />} /> {/* <Route path={`${chatsPrefix}/*`} element={<ChatsRoutes />} />
<Route <Route
path={`${routes.client.mikeV1()}/*`} path={`${routes.client.mikeV1()}/*`}
element={<MikeV1Routes />} element={<MikeV1Routes />}
@ -511,7 +513,6 @@ function App(): JSX.Element {
element={<Layout />} element={<Layout />}
> >
<Route path={routes.client.loadingPage()} element={<LoadingPage />} /> <Route path={routes.client.loadingPage()} element={<LoadingPage />} />
{/* Email - Pay - Email */}
<Route <Route
element={ element={
<ShortPathOutlet <ShortPathOutlet
@ -640,9 +641,7 @@ function App(): JSX.Element {
path={routes.client.epeFailPayment()} path={routes.client.epeFailPayment()}
element={<FailPaymentPage />} element={<FailPaymentPage />}
/> />
{/* Email - Pay - Email */}
{/* Advisor short path */}
<Route <Route
element={ element={
<ShortPathOutlet <ShortPathOutlet
@ -833,9 +832,7 @@ function App(): JSX.Element {
<Route path=":id" element={<AdvisorChatPage />} /> <Route path=":id" element={<AdvisorChatPage />} />
</Route> </Route>
</Route> </Route>
{/* Advisor short path */}
{/* Test Routes Start */}
<Route path={routes.client.notFound()} element={<NotFoundPage />} /> <Route path={routes.client.notFound()} element={<NotFoundPage />} />
<Route path={routes.client.gender()} element={<GenderPage />}> <Route path={routes.client.gender()} element={<GenderPage />}>
<Route path=":targetId" element={<GenderPage />} /> <Route path=":targetId" element={<GenderPage />} />
@ -940,9 +937,7 @@ function App(): JSX.Element {
> >
<Route path=":subPlan" element={<TrialPaymentPage />} /> <Route path=":subPlan" element={<TrialPaymentPage />} />
</Route> </Route>
{/* Test Routes End */}
{/* Email Letters */}
<Route <Route
path={routes.client.email("marketing-landing")} path={routes.client.email("marketing-landing")}
element={<MarketingLanding />} element={<MarketingLanding />}
@ -951,9 +946,7 @@ function App(): JSX.Element {
path={routes.client.email("marketing-trial-payment")} path={routes.client.email("marketing-trial-payment")}
element={<MarketingTrialPayment />} element={<MarketingTrialPayment />}
/> />
{/* Email Letters End */}
{/* Additional Purchases */}
<Route element={<PrivateOutlet />}> <Route element={<PrivateOutlet />}>
<Route element={<AdditionalPurchases />}> <Route element={<AdditionalPurchases />}>
<Route <Route
@ -970,141 +963,138 @@ function App(): JSX.Element {
/> />
</Route> </Route>
</Route> </Route>
{/* Additional Purchases End */}
<Route path="/palmistry" element={<StepsManager />} /> <Route path="/palmistry" element={<StepsManager />} />
<Route path="/palmistry/:step" element={<StepsManager />} /> <Route path="/palmistry/:step" element={<StepsManager />} />
<Route path="*" element={<NotFoundPage />} /> <Route path="*" element={<NotFoundPage />} />
</Route> </Route> */}
</Route> </Route>
</Routes> </Routes>
); );
} }
function Layout(): JSX.Element { // function Layout(): JSX.Element {
const location = useLocation(); // const location = useLocation();
const navigate = useNavigate(); // const navigate = useNavigate();
const dispatch = useDispatch(); // const dispatch = useDispatch();
const showNavbar = hasNavigation(location.pathname); // const showNavbar = hasNavigation(location.pathname);
const showFooter = hasNoFooter(location.pathname); // const showFooter = hasNoFooter(location.pathname);
const showHeader = hasNoHeader(location.pathname); // const showHeader = hasNoHeader(location.pathname);
const isRouteFullDataModal = hasFullDataModal(location.pathname); // const isRouteFullDataModal = hasFullDataModal(location.pathname);
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false); // const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const homeConfig = useSelector(selectors.selectHome); // const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar; // const showNavbarFooter = homeConfig.isShowNavbar;
const mainRef = useRef<HTMLDivElement>(null); // const mainRef = useRef<HTMLDivElement>(null);
useSchemeColorByElement(mainRef.current, "section.page, .page, section", [ // useSchemeColorByElement(mainRef.current, "section.page, .page, section", [
location, // location,
]); // ]);
const birthdate = useSelector(selectors.selectBirthdate); // const birthdate = useSelector(selectors.selectBirthdate);
const dataItems = useMemo(() => [birthdate], [birthdate]); // const dataItems = useMemo(() => [birthdate], [birthdate]);
const [isShowFullDataModal, setIsShowFullDataModal] = // const [isShowFullDataModal, setIsShowFullDataModal] =
useState<boolean>(false); // useState<boolean>(false);
useEffect(() => { // useEffect(() => {
setIsShowFullDataModal(getIsShowFullDataModal(dataItems)); // setIsShowFullDataModal(getIsShowFullDataModal(dataItems));
}, [dataItems]); // }, [dataItems]);
const onCloseFullDataModal = (_birthDate: string) => { // const onCloseFullDataModal = (_birthDate: string) => {
dispatch(actions.form.addDate(_birthDate)); // dispatch(actions.form.addDate(_birthDate));
setIsShowFullDataModal(getIsShowFullDataModal(dataItems)); // setIsShowFullDataModal(getIsShowFullDataModal(dataItems));
}; // };
const handleCompatibility = () => { // const handleCompatibility = () => {
dispatch( // dispatch(
actions.siteConfig.update({ // actions.siteConfig.update({
home: { // home: {
pathFromHome: EPathsFromHome.navbar, // pathFromHome: EPathsFromHome.navbar,
isShowNavbar: showNavbarFooter, // isShowNavbar: showNavbarFooter,
}, // },
}) // })
); // );
navigate(routes.client.compatibility()); // navigate(routes.client.compatibility());
}; // };
const handleBreath = () => { // const handleBreath = () => {
dispatch( // dispatch(
actions.siteConfig.update({ // actions.siteConfig.update({
home: { // home: {
pathFromHome: EPathsFromHome.navbar, // pathFromHome: EPathsFromHome.navbar,
isShowNavbar: showNavbarFooter, // isShowNavbar: showNavbarFooter,
}, // },
}) // })
); // );
navigate(routes.client.breath()); // navigate(routes.client.breath());
}; // };
const navbarItems: INavbarHomeItems[] = [ // const navbarItems: INavbarHomeItems[] = [
{ // {
title: "Breathing", // title: "Breathing",
path: routes.client.breath(), // path: routes.client.breath(),
paths: [routes.client.breath(), routes.client.breathResult()], // paths: [routes.client.breath(), routes.client.breathResult()],
image: "Breath.svg", // image: "Breath.svg",
onClick: handleBreath, // onClick: handleBreath,
}, // },
{ // {
title: "Aura", // title: "Aura",
path: routes.client.home(), // path: routes.client.home(),
paths: [routes.client.home()], // paths: [routes.client.home()],
image: "Aura.svg", // image: "Aura.svg",
active: true, // active: true,
onClick: () => null, // onClick: () => null,
}, // },
{ // {
title: "Compatibility", // title: "Compatibility",
path: routes.client.compatibility(), // path: routes.client.compatibility(),
paths: [ // paths: [
routes.client.compatibility(), // routes.client.compatibility(),
routes.client.compatibilityResult(), // routes.client.compatibilityResult(),
], // ],
image: "Compatibility.svg", // image: "Compatibility.svg",
onClick: handleCompatibility, // onClick: handleCompatibility,
}, // },
{ // {
title: "Advisors", // title: "Advisors",
path: routes.client.advisors(), // path: routes.client.advisors(),
paths: [routes.client.advisors()], // paths: [routes.client.advisors()],
image: "moon.svg", // image: "moon.svg",
onClick: () => null, // onClick: () => null,
}, // },
{ // {
title: "My Moon", // title: "My Moon",
path: routes.client.wallpaper(), // path: routes.client.wallpaper(),
paths: [routes.client.wallpaper()], // paths: [routes.client.wallpaper()],
image: "moon.svg", // image: "moon.svg",
onClick: () => null, // onClick: () => null,
}, // },
]; // ];
return ( // return (
<div className="container"> // <div className="container">
{showHeader ? ( // {showHeader ? (
<Header // <Header
openMenu={() => setIsMenuOpen(true)} // openMenu={() => setIsMenuOpen(true)}
/> // />
) : null} // ) : null}
{isRouteFullDataModal && ( // {isRouteFullDataModal && (
<Modal open={isShowFullDataModal} isCloseButtonVisible={false} onClose={() => { }}> // <Modal open={isShowFullDataModal} isCloseButtonVisible={false} onClose={() => { }}>
<FullDataModal onClose={onCloseFullDataModal} /> // <FullDataModal onClose={onCloseFullDataModal} />
</Modal> // </Modal>
)} // )}
<main className="content" ref={mainRef}> // <main className="content" ref={mainRef}>
<Outlet /> // <Outlet />
</main> // </main>
{showFooter ? <Footer color={showNavbar ? "black" : "white"} /> : null} // {showFooter ? <Footer color={showNavbar ? "black" : "white"} /> : null}
{showNavbar ? ( // {showNavbar ? (
<Navbar isOpen={isMenuOpen} closeMenu={() => setIsMenuOpen(false)} /> // <Navbar isOpen={isMenuOpen} closeMenu={() => setIsMenuOpen(false)} />
) : null} // ) : null}
{showNavbarFooter && hasNavbarFooter(location.pathname) ? ( // {showNavbarFooter && hasNavbarFooter(location.pathname) ? (
<NavbarFooter items={navbarItems} /> // <NavbarFooter items={navbarItems} />
) : null} // ) : null}
</div> // </div>
); // );
} // }
// enum EIsAuthPageType { // enum EIsAuthPageType {
// private, // private,
@ -1130,131 +1120,131 @@ function Layout(): JSX.Element {
// return <Outlet />; // return <Outlet />;
// } // }
interface IShortPathOutletProps { // interface IShortPathOutletProps {
productKey: EProductKeys; // productKey: EProductKeys;
requiredParameters: unknown[]; // requiredParameters: unknown[];
isProductPage?: boolean; // isProductPage?: boolean;
redirectUrls: { // redirectUrls: {
user?: { // user?: {
yes?: string; // yes?: string;
no?: string; // no?: string;
force?: string; // force?: string;
}; // };
data?: { // data?: {
yes?: string; // yes?: string;
no?: string; // no?: string;
force?: string; // force?: string;
}; // };
purchasedProduct?: { // purchasedProduct?: {
yes?: string; // yes?: string;
no?: string; // no?: string;
force?: string; // force?: string;
}; // };
force?: { // force?: {
yes?: string; // yes?: string;
no?: string; // no?: string;
force?: string; // force?: string;
}; // };
}; // };
} // }
function ShortPathOutlet(props: IShortPathOutletProps): JSX.Element { // function ShortPathOutlet(props: IShortPathOutletProps): JSX.Element {
const dispatch = useDispatch(); // const dispatch = useDispatch();
const { productKey, requiredParameters, redirectUrls, isProductPage } = props; // const { productKey, requiredParameters, redirectUrls, isProductPage } = props;
const { user, token } = useAuth(); // const { user, token } = useAuth();
const dateOfPaymentChatMike = useSelector( // const dateOfPaymentChatMike = useSelector(
selectors.selectDateOfPaymentChatMike // selectors.selectDateOfPaymentChatMike
); // );
const queryParameters = new URLSearchParams(window.location.search); // const queryParameters = new URLSearchParams(window.location.search);
const paymentMadeChatMike = queryParameters.get("paymentMadeChatMike"); // const paymentMadeChatMike = queryParameters.get("paymentMadeChatMike");
if (paymentMadeChatMike && !dateOfPaymentChatMike) { // if (paymentMadeChatMike && !dateOfPaymentChatMike) {
dispatch(actions.userConfig.setDateOfPaymentChatMike(new Date())); // dispatch(actions.userConfig.setDateOfPaymentChatMike(new Date()));
} // }
const isForcePaymentStatus = useMemo(() => { // const isForcePaymentStatus = useMemo(() => {
if ((Date.now() - new Date(dateOfPaymentChatMike).getTime()) / 1000 < 180) { // if ((Date.now() - new Date(dateOfPaymentChatMike).getTime()) / 1000 < 180) {
return true; // return true;
} // }
if (paymentMadeChatMike && !dateOfPaymentChatMike) { // if (paymentMadeChatMike && !dateOfPaymentChatMike) {
return true; // return true;
} // }
return false; // return false;
}, [dateOfPaymentChatMike, paymentMadeChatMike]); // }, [dateOfPaymentChatMike, paymentMadeChatMike]);
const api = useApi(); // const api = useApi();
const isForce = useSelector(selectors.selectIsForceShortPath); // const isForce = useSelector(selectors.selectIsForceShortPath);
const loadData = useCallback(async () => { // const loadData = useCallback(async () => {
if (!token?.length || !user?.email || !productKey?.length) // if (!token?.length || !user?.email || !productKey?.length)
return { // return {
status: "error", // status: "error",
error: "Missing params", // error: "Missing params",
}; // };
try { // try {
const purchased = await api.checkProductPurchased({ // const purchased = await api.checkProductPurchased({
productKey, // productKey,
token, // token,
}); // });
return purchased; // return purchased;
} catch (error) { // } catch (error) {
console.error(error); // console.error(error);
return { // return {
status: "error", // status: "error",
error: "Something went wrong", // error: "Something went wrong",
}; // };
} // }
// eslint-disable-next-line react-hooks/exhaustive-deps // // eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]); // }, [token]);
const { data, isPending } = useApiCall(loadData, "pending"); // const { data, isPending } = useApiCall(loadData, "pending");
if (isPending) { // if (isPending) {
return <LoadingPage />; // return <LoadingPage />;
} // }
const isPurchasedProduct = !!(data && "active" in data && data.active); // const isPurchasedProduct = !!(data && "active" in data && data.active);
const isUser = !!user && !!token.length; // const isUser = !!user && !!token.length;
const isFullData = requiredParameters.every((item) => !!item); // const isFullData = requiredParameters.every((item) => !!item);
if (!isFullData) { // if (!isFullData) {
if (isForce && redirectUrls.data?.force) { // if (isForce && redirectUrls.data?.force) {
return <Navigate to={redirectUrls.data.force} replace={true} />; // return <Navigate to={redirectUrls.data.force} replace={true} />;
} // }
if (redirectUrls.data?.no && !isForce) { // if (redirectUrls.data?.no && !isForce) {
return <Navigate to={redirectUrls.data.no} replace={true} />; // return <Navigate to={redirectUrls.data.no} replace={true} />;
} // }
return <Outlet />; // return <Outlet />;
} // }
if (!isUser) { // if (!isUser) {
if (isForce && redirectUrls.user?.force) { // if (isForce && redirectUrls.user?.force) {
return <Navigate to={redirectUrls.user.force} replace={true} />; // return <Navigate to={redirectUrls.user.force} replace={true} />;
} // }
if (redirectUrls.user?.no && !isForce) { // if (redirectUrls.user?.no && !isForce) {
return <Navigate to={redirectUrls.user.no} replace={true} />; // return <Navigate to={redirectUrls.user.no} replace={true} />;
} // }
return <Outlet />; // return <Outlet />;
} // }
if (!isPurchasedProduct && !isForcePaymentStatus) { // if (!isPurchasedProduct && !isForcePaymentStatus) {
if (isForce && redirectUrls.purchasedProduct?.force) { // if (isForce && redirectUrls.purchasedProduct?.force) {
return ( // return (
<Navigate to={redirectUrls.purchasedProduct.force} replace={true} /> // <Navigate to={redirectUrls.purchasedProduct.force} replace={true} />
); // );
} // }
if (redirectUrls.purchasedProduct?.no && !isForce) { // if (redirectUrls.purchasedProduct?.no && !isForce) {
return <Navigate to={redirectUrls.purchasedProduct.no} replace={true} />; // return <Navigate to={redirectUrls.purchasedProduct.no} replace={true} />;
} // }
return <Outlet />; // return <Outlet />;
} // }
if (isProductPage) { // if (isProductPage) {
return <Outlet />; // return <Outlet />;
} // }
return <Navigate to={productUrls[productKey]} replace={true} />; // return <Navigate to={productUrls[productKey]} replace={true} />;
} // }
export function AuthorizedUserOutlet(): JSX.Element { export function AuthorizedUserOutlet(): JSX.Element {
const status = useSelector(selectors.selectStatus); const status = useSelector(selectors.selectStatus);
@ -1285,18 +1275,18 @@ function PrivateSubscriptionOutlet(): JSX.Element {
); );
} }
function getIsShowFullDataModal(dataItems: Array<unknown> = []): boolean { // function getIsShowFullDataModal(dataItems: Array<unknown> = []): boolean {
let hasNoDataItem = false; // let hasNoDataItem = false;
for (const item of dataItems) { // for (const item of dataItems) {
if (!item) { // if (!item) {
hasNoDataItem = true; // hasNoDataItem = true;
break; // break;
} // }
} // }
return hasNoDataItem; // return hasNoDataItem;
} // }
function SkipStep(): JSX.Element { function SkipStep(): JSX.Element {
const { user } = useAuth(); const { user } = useAuth();

View File

@ -40,7 +40,6 @@ function ExpertChat() {
const userId = useSelector(selectors.selectUserId); const userId = useSelector(selectors.selectUserId);
const [messageText, setMessageText] = useState(""); const [messageText, setMessageText] = useState("");
const [textareaRows, setTextareaRows] = useState(1); const [textareaRows, setTextareaRows] = useState(1);
// const [isLoadingLatestMessages, setIsLoadingLatestMessages] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const [isShowRefillCreditsModal, setIsShowRefillCreditsModal] = const [isShowRefillCreditsModal, setIsShowRefillCreditsModal] =
useState(false); useState(false);
@ -65,14 +64,10 @@ function ExpertChat() {
const tokenFromStore = useSelector(selectors.selectToken); const tokenFromStore = useSelector(selectors.selectToken);
const [isLoadingPayment, setIsLoadingPayment] = useState(false); const [isLoadingPayment, setIsLoadingPayment] = useState(false);
const [isError, setIsError] = useState(false); const [isError, setIsError] = useState(false);
// const [currentProduct, setCurrentProduct] = useState<IPaywallProduct | null>(
// null
// );
const currentProduct = useSelector(selectors.selectActiveProduct); const currentProduct = useSelector(selectors.selectActiveProduct);
const setCurrentProduct = (product: IPaywallProduct) => { const setCurrentProduct = (product: IPaywallProduct) => {
dispatch(actions.payment.update({ activeProduct: product })); dispatch(actions.payment.update({ activeProduct: product }));
}; };
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
const { const {
@ -196,12 +191,8 @@ function ExpertChat() {
setIsLoadingPayment(true); setIsLoadingPayment(true);
const isPaymentMethodExist = await api.getPaymentMethods({ token: tokenFromStore }); const isPaymentMethodExist = await api.getPaymentMethods({ token: tokenFromStore });
if (isPaymentMethodExist.status === "error") { if (isPaymentMethodExist.status === "error") {
// return setIsPaymentModalOpen(true);
return showCreditCardForm(); return showCreditCardForm();
} }
// if (!isPayedFirstPurchase) {
// return setIsPaymentModalOpen(true);
// }
const { _id, key } = currentProduct; const { _id, key } = currentProduct;
const paymentInfo = { const paymentInfo = {
productId: _id, productId: _id,
@ -270,12 +261,10 @@ function ExpertChat() {
}; };
const onPaymentError = () => { const onPaymentError = () => {
// setIsPaymentModalOpen(false);
return setIsError(true); return setIsError(true);
} }
const onPaymentSuccess = () => { const onPaymentSuccess = () => {
// setIsPaymentModalOpen(false);
setIsLoadingPayment(false); setIsLoadingPayment(false);
return closeModals(); return closeModals();
} }
@ -294,27 +283,6 @@ function ExpertChat() {
return ( return (
<section className={`${styles.page} page`}> <section className={`${styles.page} page`}>
{/* {!isLoading &&
!!tokenFromStore.length &&
currentProduct && (
<>
<Modal
open={isPaymentModalOpen}
onClose={() => setIsPaymentModalOpen(false)}
containerClassName={styles.modal}
>
<Title variant="h1" className={styles["modal-title"]}>
{getPriceCentsToDollars(currentProduct.price || 0)}$
</Title>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
isSinglePayment={true}
/>
</Modal>
</>
)} */}
{isLoading && ( {isLoading && (
<Loader color={LoaderColor.Red} className={styles.loader} /> <Loader color={LoaderColor.Red} className={styles.loader} />
)} )}
@ -358,9 +326,6 @@ function ExpertChat() {
textColor={"#000"} textColor={"#000"}
/> />
)} )}
{/* <div className={styles["loader-container"]}>
{isLoadingLatestMessages && <Loader color={LoaderColor.Red} />}
</div> */}
</div> </div>
)} )}
@ -378,7 +343,6 @@ function ExpertChat() {
handleChangeMessageText={handleChangeMessageText} handleChangeMessageText={handleChangeMessageText}
isLoading={isLoadingSelfMessage} isLoading={isLoadingSelfMessage}
submitForm={handleSendMessage} submitForm={handleSendMessage}
// description={`The cost of chat is ${assistant?.price} credits/min`}
description={translate("/expert.cost", { description={translate("/expert.cost", {
price: assistant?.price || 0, price: assistant?.price || 0,
})} })}

View File

@ -24,6 +24,7 @@ interface CameraModalProps {
onVideoReady?: () => void; onVideoReady?: () => void;
reinitializeKey?: number; // for reinitializing the camera (change the key to reinitialize the camera) reinitializeKey?: number; // for reinitializing the camera (change the key to reinitialize the camera)
isCameraVisible?: boolean; isCameraVisible?: boolean;
className?: string;
} }
function CameraModal({ function CameraModal({
@ -32,7 +33,8 @@ function CameraModal({
onError, onError,
onVideoReady, onVideoReady,
reinitializeKey = 0, reinitializeKey = 0,
isCameraVisible = true isCameraVisible = true,
className = ""
}: CameraModalProps) { }: CameraModalProps) {
const [isVideoReady, setIsVideoReady] = useState(false); const [isVideoReady, setIsVideoReady] = useState(false);
const [isTorchOn, setIsTorchOn] = useState(false); const [isTorchOn, setIsTorchOn] = useState(false);
@ -115,7 +117,7 @@ function CameraModal({
return <ModalOverlay return <ModalOverlay
type={ModalOverlayType.Dark} type={ModalOverlayType.Dark}
className={styles.overlay} className={`${styles.overlay} ${className}`}
onClick={onClickOverlay} onClick={onClickOverlay}
> >
<Modal <Modal

View File

@ -1,77 +0,0 @@
import { useSearchParams } from "react-router-dom";
import styles from "./styles.module.scss";
import { HTMLAttributes, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import PaymentModalNew from "@/components/PaymentModalNew";
import { EPlacementKeys } from "@/api/resources/Paywall";
import routes from "@/routes";
function PaymentModal(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
const [height, setHeight] = useState(
subscriptionStatus === "subscribed" ? 246 : 146
);
const returnUrl = window.location.origin + routes.client.compatibilityV2Payment();
return (
<>
{activeProductFromStore && (
<div
{...props}
className={styles.container}
style={{ minHeight: `${height}px`, ...props.style }}
>
<div
className={`${styles.widget} ${
subscriptionStatus === "subscribed"
? styles["widget_success"]
: ""
} ${props.className}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModalNew
setHeight={setHeight}
activeProduct={activeProductFromStore}
returnUrl={returnUrl}
placementKey={
EPlacementKeys["aura.placement.compatibility.v2"]
}
noProductRedirect={{
pagesFrom: [routes.client.compatibilityV2TrialPayment()],
url: routes.client.compatibilityV2TrialChoice(),
}}
/>
)}
{subscriptionStatus === "subscribed" && (
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
)}
</div>
</div>
)}
</>
);
}
export default PaymentModal;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,174 +0,0 @@
import { useTranslations } from '@/hooks/translations';
import { addCurrency, ELocalesPlacement } from '@/locales';
import styles from "./styles.module.scss";
import { LegacyRef, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { selectors } from '@/store';
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
import cn from "classnames";
import { getFormattedPrice } from '@/utils/price.utils';
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
import Title from '@/components/Title';
import { useNavigate } from 'react-router-dom';
import routes from '@/routes';
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
import NMIPaymentForm from '@/components/Payment/nmi/PaymentForm';
import PaymentModal from '@/components/Payment/PaymentModal';
const placementKey = EPlacementKeys['aura.placement.compatibility.v2'];
interface IPaymentFormProps {
activeProduct: IPaywallProduct;
}
function PaymentForm({
activeProduct,
}: IPaymentFormProps) {
const navigate = useNavigate();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
const ref = useRef<HTMLDivElement>();
const currency = useSelector(selectors.selectCurrency);
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
const [isPaymentError, setIsPaymentError] = useState(false);
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
const onPaymentError = (error?: string) => {
setIsPaymentError(true);
if (error === "Product not found") {
navigate(routes.client.compatibilityV2TrialChoice());
}
}
const onPaymentSuccess = () => {
setIsPaymentSuccess(true);
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// if (activeProduct) {
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
// });
// }
setTimeout(() => {
navigate(routes.client.compatibilityV2SkipTrial());
}, 1500);
}
const onModalClosed = () => {
setIsPaymentModalOpen(false);
navigate(routes.client.compatibilityV2SaveOff());
}
if (isPaymentError) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
if (isPaymentSuccess) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
</div>
);
}
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={cn(
styles.paymentModalContainer
)}
>
<PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal>
<div className={styles.paymentModalPrice}>
{translate(
"/payment.total_due",
{
trialPrice: addCurrency(
getFormattedPrice(activeProduct.trialPrice || 0),
currency
),
},
ELocalesPlacement.CompatibilityV2
)}
</div>
<div
className={styles.paymentCreditCard}
onClick={() => setIsPaymentModalOpen(true)}
// onClick={() => showCreditCardForm()}
>
<CreditCardIcon />
<div>Credit / Debit Card</div>
</div>
{/* <GooglePayButton />
<ApplePayButton /> */}
<div className={styles.infoContainer}>
<SecurityPayments />
<p className={styles.address}>
{translate(
"payment_modal.address",
undefined,
ELocalesPlacement.V1
)}
</p>
</div>
</div>
);
}
export default PaymentForm

View File

@ -1,94 +0,0 @@
.paymentModalContainer {
display: flex;
flex-direction: column;
position: relative;
margin: -12px -20px;
padding: 12px 20px;
gap: 6px;
transition: height 1s ease-out;
.address {
color: gray;
font-size: 10px;
margin-bottom: 16px;
text-transform: uppercase;
}
.infoContainer>* {
padding-top: 16px;
}
.paymentCreditCard {
background: #066fde;
color: #fff !important;
gap: 6px;
display: flex;
font-size: 14px;
line-height: 18px;
align-items: center;
font-weight: 400;
min-height: 48px;
border-radius: 5px;
justify-content: center;
cursor: pointer;
}
&Loading {
background: rgba(215, 213, 213, .5);
}
.paymentModalLoader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
color: #2f2e37;
position: absolute;
width: 100%;
margin-left: -20px;
}
.paymentModalPrice {
color: #066fde;
font-size: 16px;
font-weight: 700;
line-height: 25px;
text-align: center;
margin-bottom: 12px;
}
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
&>.icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
&>.text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
.modal-content {
overflow-x: hidden;
}

View File

@ -1,32 +0,0 @@
import { selectors } from '@/store';
import { HTMLAttributes } from 'react';
import { useSelector } from 'react-redux';
import styles from "./styles.module.scss";
import PaymentForm from './PaymentForm';
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
return (
<>
{
activeProductFromStore && (
<div
{...props}
className={styles.container}
>
<div
className={`${styles.widget} ${props.className}`}
>
<PaymentForm
activeProduct={activeProductFromStore}
/>
</div>
</div>
)
}
</>
);
}
export default PaymentModalV1;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import AndroidCamera from "./android"; import AndroidCamera from "./android";
import IphoneCamera from "./iphone"; import IphoneCamera from "./iphone";
import { isWebView } from "@/services/hacks/webviewToSystemBrowser";
function Camera() { function Camera() {
const isIphoneSafari = useMemo((): boolean => { const isIphoneSafari = useMemo((): boolean => {
@ -13,7 +14,7 @@ function Camera() {
!/EdgiOS/i.test(userAgent) && // не Edge !/EdgiOS/i.test(userAgent) && // не Edge
!/OPiOS/i.test(userAgent); // не Opera !/OPiOS/i.test(userAgent); // не Opera
return isIOS && isSafari; return isIOS && (isSafari || isWebView());
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -95,6 +95,11 @@ function IphoneCamera() {
setReinitializeCameraCount(prev => prev + 1) setReinitializeCameraCount(prev => prev + 1)
} }
const handleTryAgain = () => {
setToastVisible(null)
// setReinitializeCameraCount(prev => prev + 1)
}
useEffect(() => { useEffect(() => {
if (!isShowCameraRequestModal) { if (!isShowCameraRequestModal) {
setIsCameraModalOpen(true) setIsCameraModalOpen(true)
@ -240,15 +245,16 @@ function IphoneCamera() {
</Modal> </Modal>
{/* Модальное окно камеры */} {/* Модальное окно камеры */}
{!isLoading && ( {/* {!isLoading && ( */}
<CameraModal <CameraModal
onClose={() => console.log("close")} onClose={() => console.log("close")}
onTakePhoto={handleCameraSuccess} onTakePhoto={handleCameraSuccess}
onError={handleCameraError} onError={handleCameraError}
isCameraVisible={isCameraModalOpen} isCameraVisible={isCameraModalOpen}
reinitializeKey={reinitializeCameraCount} reinitializeKey={reinitializeCameraCount}
/> className={isLoading ? styles.hideCameraModal : ""}
)} />
{/* )} */}
{/* Лоадер */} {/* Лоадер */}
{isLoading && ( {isLoading && (
@ -261,7 +267,7 @@ function IphoneCamera() {
<div className={styles["toast-content"]}> <div className={styles["toast-content"]}>
<span>{translate("/camera.bad_photo")}</span> <span>{translate("/camera.bad_photo")}</span>
<div className={styles["toast-buttons-container"]}> <div className={styles["toast-buttons-container"]}>
<button onClick={() => setToastVisible(null)}> <button onClick={handleTryAgain}>
{translate("/camera.try_again")} {translate("/camera.try_again")}
</button> </button>
<button className={styles.buttonUpload}> <button className={styles.buttonUpload}>
@ -316,7 +322,7 @@ function IphoneCamera() {
> >
<span>{translate("/camera.do_better")}</span> <span>{translate("/camera.do_better")}</span>
<div className={styles["buttons-container"]}> <div className={styles["buttons-container"]}>
<button onClick={() => setToastVisible(null)}> <button onClick={handleTryAgain}>
{translate("/camera.try_again")} {translate("/camera.try_again")}
</button> </button>
<button onClick={handleToScannedPhoto}> <button onClick={handleToScannedPhoto}>

View File

@ -108,4 +108,12 @@
:global(.dark-theme) .modal-container { :global(.dark-theme) .modal-container {
background-color: #343639; background-color: #343639;
&>.modal-answers>.modal-answer {
color: #1e7dff;
}
}
.hideCameraModal {
opacity: 0;
} }

View File

@ -13,6 +13,8 @@ import { images } from "../../data";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash"; import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import Loader, { LoaderColor } from "@/components/Loader"; import Loader, { LoaderColor } from "@/components/Loader";
import { useEffect } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
function PalmsInformation() { function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -35,6 +37,16 @@ function PalmsInformation() {
return <Loader color={LoaderColor.Black} />; return <Loader color={LoaderColor.Black} />;
} }
useEffect(() => {
const ua = window.navigator.userAgent;
if (ua.includes("FBAN") || ua.includes("FBAV") || ua.includes("FBIOS")) {
metricService.reachGoal(EGoals.STAYED_IN_FB, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
if (ua.includes("Instagram")) {
metricService.reachGoal(EGoals.STAYED_IN_INSTAGRAM, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
}, [])
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
{zodiacImages !== "new" && ( {zodiacImages !== "new" && (

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import routes from "@/routes"; import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.v2'];
function Payment() { function Payment() {
const navigate = useNavigate(); const navigate = useNavigate();
const { products, currency, getText } = usePaywall({ const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 0; const trialPrice = activeProductFromStore?.trialPrice || 0;
const fullPrice = activeProductFromStore?.price || 0; const fullPrice = activeProductFromStore?.price || 0;
// const isShowPaymentModal = useSelector(
// selectors.selectCompatibilityV2IsShowPaymentModalV1
// );
// const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
// const [isPaymentError, setIsPaymentError] = useState(false);
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const onPaymentError = (error?: string) => {
// setIsPaymentError(true);
// if (error === "Product not found") {
// return navigate(routes.client.compatibilityV2TrialChoice());
// }
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// }
// const onPaymentSuccess = () => {
// setIsPaymentSuccess(true);
// // metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// // if (activeProductFromStore) {
// // metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// // currency: "USD",
// // value: ((activeProductFromStore.trialPrice || 100) / 100).toFixed(2),
// // });
// // }
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProductFromStore?.trialPrice || 100) / 100).toFixed(2),
// });
// setTimeout(() => {
// navigate(routes.client.compatibilityV2SkipTrial());
// }, 1500);
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// navigate(routes.client.compatibilityV2SaveOff());
// }
const handlePayment = () => { const handlePayment = () => {
// dispatch(actions.compatibilityV2.setIsShowPaymentModalV1(true));
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
navigate(routes.client.compatibilityV2PaymentModal()); navigate(routes.client.compatibilityV2PaymentModal());
}; };
// useEffect(() => {
// window.onpopstate = function (_event) {
// if (document.location.toString() === `${window.location.origin}${routes.client.compatibilityV2TrialPayment()}`) {
// return navigate(routes.client.compatibilityV2SaveOff());
// }
// };
// return () => {
// setTimeout(() => {
// window.onpopstate = null;
// }, 0);
// };
// }, [])
useEffect(() => { useEffect(() => {
if (!products.length) return; if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id); const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return ( return (
<> <>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}> <div className={styles["app-number-one"]}>
<p className={styles.text}> <p className={styles.text}>
{translate("/payment.app_number_one", { {translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation /> <PaymentInformation />
<div className={styles["prices-description"]}> <div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", { {translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice( splitPrice: addCurrency(getFormattedPrice(
( (
fullPrice / ( fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})} })}
</div> </div>
<Guarantees /> <Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}> <Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")} {translate("/payment.get_personal_prediction")}
</Button> </Button>
{/* )} */}
{/* {isPaymentError && (
<Toast
variant="error"
classNameContainer={styles.toast}
>
<p>Something went wrong</p>
</Toast>
)}
{isPaymentSuccess && (
<Toast
variant="success"
classNameContainer={styles.toast}
>
<p>Payment successful</p>
</Toast>
)} */}
</> </>
); );

View File

@ -287,6 +287,7 @@ function ScannedPhoto() {
drawElements={drawElements} drawElements={drawElements}
className={classNameScannedPhoto} className={classNameScannedPhoto}
isDecorationShown={isDecorationShown} isDecorationShown={isDecorationShown}
isShowLineScale={true}
/> />
<h2 <h2
className={`palmistry-container__waiting-title ${styles.waitingTitle} ${!isDecorationShown ? styles.hidden : ""}`} className={`palmistry-container__waiting-title ${styles.waitingTitle} ${!isDecorationShown ? styles.hidden : ""}`}

View File

@ -96,6 +96,7 @@
0% { 0% {
transform: scale(3); transform: scale(3);
} }
100% { 100% {
transform: scale(1); transform: scale(1);
} }
@ -348,6 +349,10 @@
:global(.dark-theme) .modal-container { :global(.dark-theme) .modal-container {
background-color: #343639; background-color: #343639;
&>.modal-answers>.modal-answer {
color: #1e7dff;
}
} }
:global(.dark-theme) .modal-title { :global(.dark-theme) .modal-title {

View File

@ -30,10 +30,7 @@ function SecretDiscount() {
}); });
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV2); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV2);
// const activeProductFromStore = useSelector(selectors.selectActiveProduct);
// const activeProduct = products.find(product => product?.trialPrice === activeProductFromStore?.trialPrice) || products[0]
const activeProduct = products[0] const activeProduct = products[0]
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +42,19 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// const onPaymentSuccess = () => {
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct?.trialPrice || 100) / 100).toFixed(2),
// });
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// return navigate(routes.client.paymentFail())
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.compatibilityV2SecretDiscountPaymentModal()); navigate(routes.client.compatibilityV2SecretDiscountPaymentModal());
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return ( return (
<section className={styles.container} ref={elementRef} style={{ <section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px` paddingBottom: `${height + 42}px`
}}> }}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header <Header
className={styles.header} className={styles.header}
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -1,77 +0,0 @@
import { useSearchParams } from "react-router-dom";
import styles from "./styles.module.scss";
import { HTMLAttributes, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import PaymentModalNew from "@/components/PaymentModalNew";
import { EPlacementKeys } from "@/api/resources/Paywall";
import routes from "@/routes";
function PaymentModal(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
const [height, setHeight] = useState(
subscriptionStatus === "subscribed" ? 246 : 146
);
const returnUrl = window.location.origin + routes.client.compatibilityV3Payment();
return (
<>
{activeProductFromStore && (
<div
{...props}
className={styles.container}
style={{ minHeight: `${height}px`, ...props.style }}
>
<div
className={`${styles.widget} ${
subscriptionStatus === "subscribed"
? styles["widget_success"]
: ""
} ${props.className}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModalNew
setHeight={setHeight}
activeProduct={activeProductFromStore}
returnUrl={returnUrl}
placementKey={
EPlacementKeys["aura.placement.compatibility.v3"]
}
noProductRedirect={{
pagesFrom: [routes.client.compatibilityV3TrialPayment()],
url: routes.client.compatibilityV3TrialChoice(),
}}
/>
)}
{subscriptionStatus === "subscribed" && (
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
)}
</div>
</div>
)}
</>
);
}
export default PaymentModal;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,174 +0,0 @@
import { useTranslations } from '@/hooks/translations';
import { addCurrency, ELocalesPlacement } from '@/locales';
import styles from "./styles.module.scss";
import { LegacyRef, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { selectors } from '@/store';
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
import cn from "classnames";
import { getFormattedPrice } from '@/utils/price.utils';
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
import Title from '@/components/Title';
import { useNavigate } from 'react-router-dom';
import routes from '@/routes';
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
import NMIPaymentForm from '@/components/Payment/nmi/PaymentForm';
import PaymentModal from '@/components/Payment/PaymentModal';
const placementKey = EPlacementKeys['aura.placement.compatibility.v3'];
interface IPaymentFormProps {
activeProduct: IPaywallProduct;
}
function PaymentForm({
activeProduct,
}: IPaymentFormProps) {
const navigate = useNavigate();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV3);
const ref = useRef<HTMLDivElement>();
const currency = useSelector(selectors.selectCurrency);
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
const [isPaymentError, setIsPaymentError] = useState(false);
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
const onPaymentError = (error?: string) => {
setIsPaymentError(true);
if (error === "Product not found") {
navigate(routes.client.compatibilityV3TrialChoice());
}
}
const onPaymentSuccess = () => {
setIsPaymentSuccess(true);
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// if (activeProduct) {
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
// });
// }
setTimeout(() => {
navigate(routes.client.compatibilityV3SkipTrial());
}, 1500);
}
const onModalClosed = () => {
setIsPaymentModalOpen(false);
navigate(routes.client.compatibilityV3SaveOff());
}
if (isPaymentError) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
if (isPaymentSuccess) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
</div>
);
}
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={cn(
styles.paymentModalContainer
)}
>
<PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal>
<div className={styles.paymentModalPrice}>
{translate(
"/payment.total_due",
{
trialPrice: addCurrency(
getFormattedPrice(activeProduct.trialPrice || 0),
currency
),
},
ELocalesPlacement.CompatibilityV3
)}
</div>
<div
className={styles.paymentCreditCard}
onClick={() => setIsPaymentModalOpen(true)}
// onClick={() => showCreditCardForm()}
>
<CreditCardIcon />
<div>Credit / Debit Card</div>
</div>
{/* <GooglePayButton />
<ApplePayButton /> */}
<div className={styles.infoContainer}>
<SecurityPayments />
<p className={styles.address}>
{translate(
"payment_modal.address",
undefined,
ELocalesPlacement.V1
)}
</p>
</div>
</div>
);
}
export default PaymentForm

View File

@ -1,94 +0,0 @@
.paymentModalContainer {
display: flex;
flex-direction: column;
position: relative;
margin: -12px -20px;
padding: 12px 20px;
gap: 6px;
transition: height 1s ease-out;
.address {
color: gray;
font-size: 10px;
margin-bottom: 16px;
text-transform: uppercase;
}
.infoContainer>* {
padding-top: 16px;
}
.paymentCreditCard {
background: #066fde;
color: #fff !important;
gap: 6px;
display: flex;
font-size: 14px;
line-height: 18px;
align-items: center;
font-weight: 400;
min-height: 48px;
border-radius: 5px;
justify-content: center;
cursor: pointer;
}
&Loading {
background: rgba(215, 213, 213, .5);
}
.paymentModalLoader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
color: #2f2e37;
position: absolute;
width: 100%;
margin-left: -20px;
}
.paymentModalPrice {
color: #066fde;
font-size: 16px;
font-weight: 700;
line-height: 25px;
text-align: center;
margin-bottom: 12px;
}
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
&>.icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
&>.text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
.modal-content {
overflow-x: hidden;
}

View File

@ -1,32 +0,0 @@
import { selectors } from '@/store';
import { HTMLAttributes } from 'react';
import { useSelector } from 'react-redux';
import styles from "./styles.module.scss";
import PaymentForm from './PaymentForm';
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
return (
<>
{
activeProductFromStore && (
<div
{...props}
className={styles.container}
>
<div
className={`${styles.widget} ${props.className}`}
>
<PaymentForm
activeProduct={activeProductFromStore}
/>
</div>
</div>
)
}
</>
);
}
export default PaymentModalV1;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -10,6 +10,8 @@ import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { selectors } from "@/store"; import { selectors } from "@/store";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useEffect } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
function PalmsInformation() { function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV3); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV3);
@ -24,6 +26,16 @@ function PalmsInformation() {
navigate(routes.client.compatibilityV3RelationshipStatus()); navigate(routes.client.compatibilityV3RelationshipStatus());
}; };
useEffect(() => {
const ua = window.navigator.userAgent;
if (ua.includes("FBAN") || ua.includes("FBAV") || ua.includes("FBIOS")) {
metricService.reachGoal(EGoals.STAYED_IN_FB, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
if (ua.includes("Instagram")) {
metricService.reachGoal(EGoals.STAYED_IN_INSTAGRAM, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
}, [])
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
<div className={styles.zodiac}> <div className={styles.zodiac}>

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import routes from "@/routes"; import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.v3'];
function Payment() { function Payment() {
const navigate = useNavigate(); const navigate = useNavigate();
const { products, currency, getText } = usePaywall({ const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 0; const trialPrice = activeProductFromStore?.trialPrice || 0;
const fullPrice = activeProductFromStore?.price || 0; const fullPrice = activeProductFromStore?.price || 0;
// const isShowPaymentModal = useSelector(
// selectors.selectCompatibilityV3IsShowPaymentModalV1
// );
// const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
// const [isPaymentError, setIsPaymentError] = useState(false);
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const onPaymentError = (error?: string) => {
// setIsPaymentError(true);
// if (error === "Product not found") {
// return navigate(routes.client.compatibilityV3TrialChoice());
// }
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// }
// const onPaymentSuccess = () => {
// setIsPaymentSuccess(true);
// // metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// // if (activeProductFromStore) {
// // metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// // currency: "USD",
// // value: ((activeProductFromStore.trialPrice || 100) / 100).toFixed(2),
// // });
// // }
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProductFromStore?.trialPrice || 100) / 100).toFixed(2),
// });
// setTimeout(() => {
// navigate(routes.client.compatibilityV3SkipTrial());
// }, 1500);
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// navigate(routes.client.compatibilityV3SaveOff());
// }
const handlePayment = () => { const handlePayment = () => {
// dispatch(actions.compatibilityV3.setIsShowPaymentModalV1(true));
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
navigate(routes.client.compatibilityV3PaymentModal()); navigate(routes.client.compatibilityV3PaymentModal());
}; };
// useEffect(() => {
// window.onpopstate = function (_event) {
// if (document.location.toString() === `${window.location.origin}${routes.client.compatibilityV3TrialPayment()}`) {
// return navigate(routes.client.compatibilityV3SaveOff());
// }
// };
// return () => {
// setTimeout(() => {
// window.onpopstate = null;
// }, 0);
// };
// }, [])
useEffect(() => { useEffect(() => {
if (!products.length) return; if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id); const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return ( return (
<> <>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}> <div className={styles["app-number-one"]}>
<p className={styles.text}> <p className={styles.text}>
{translate("/payment.app_number_one", { {translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation /> <PaymentInformation />
<div className={styles["prices-description"]}> <div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", { {translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice( splitPrice: addCurrency(getFormattedPrice(
( (
fullPrice / ( fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})} })}
</div> </div>
<Guarantees /> <Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}> <Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")} {translate("/payment.get_personal_prediction")}
</Button> </Button>
{/* )} */}
{/* {isPaymentError && (
<Toast
variant="error"
classNameContainer={styles.toast}
>
<p>Something went wrong</p>
</Toast>
)}
{isPaymentSuccess && (
<Toast
variant="success"
classNameContainer={styles.toast}
>
<p>Payment successful</p>
</Toast>
)} */}
</> </>
); );

View File

@ -29,11 +29,7 @@ function SecretDiscount() {
localesPlacement: ELocalesPlacement.EmailMarketingCompatibilityV3, localesPlacement: ELocalesPlacement.EmailMarketingCompatibilityV3,
}); });
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV3); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV3);
// const activeProductFromStore = useSelector(selectors.selectActiveProduct);
// const activeProduct = products.find(product => product?.trialPrice === activeProductFromStore?.trialPrice) || products[0]
const activeProduct = products[0] const activeProduct = products[0]
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +41,19 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// const onPaymentSuccess = () => {
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct?.trialPrice || 100) / 100).toFixed(2),
// });
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// return navigate(routes.client.paymentFail())
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.compatibilityV3SecretDiscountPaymentModal()); navigate(routes.client.compatibilityV3SecretDiscountPaymentModal());
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return ( return (
<section className={styles.container} ref={elementRef} style={{ <section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px` paddingBottom: `${height + 42}px`
}}> }}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header <Header
className={styles.header} className={styles.header}
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -1,77 +0,0 @@
import { useSearchParams } from "react-router-dom";
import styles from "./styles.module.scss";
import { HTMLAttributes, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import PaymentModalNew from "@/components/PaymentModalNew";
import { EPlacementKeys } from "@/api/resources/Paywall";
import routes from "@/routes";
function PaymentModal(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
const [height, setHeight] = useState(
subscriptionStatus === "subscribed" ? 246 : 146
);
const returnUrl = window.location.origin + routes.client.compatibilityV4Payment();
return (
<>
{activeProductFromStore && (
<div
{...props}
className={styles.container}
style={{ minHeight: `${height}px`, ...props.style }}
>
<div
className={`${styles.widget} ${
subscriptionStatus === "subscribed"
? styles["widget_success"]
: ""
} ${props.className}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModalNew
setHeight={setHeight}
activeProduct={activeProductFromStore}
returnUrl={returnUrl}
placementKey={
EPlacementKeys["aura.placement.compatibility.v4"]
}
noProductRedirect={{
pagesFrom: [routes.client.compatibilityV4TrialPayment()],
url: routes.client.compatibilityV4TrialChoice(),
}}
/>
)}
{subscriptionStatus === "subscribed" && (
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
)}
</div>
</div>
)}
</>
);
}
export default PaymentModal;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,174 +0,0 @@
import { useTranslations } from '@/hooks/translations';
import { addCurrency, ELocalesPlacement } from '@/locales';
import styles from "./styles.module.scss";
import { LegacyRef, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { selectors } from '@/store';
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
import cn from "classnames";
import { getFormattedPrice } from '@/utils/price.utils';
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
import Title from '@/components/Title';
import { useNavigate } from 'react-router-dom';
import routes from '@/routes';
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
import NMIPaymentForm from '@/components/Payment/nmi/PaymentForm';
import PaymentModal from '@/components/Payment/PaymentModal';
const placementKey = EPlacementKeys['aura.placement.compatibility.v4'];
interface IPaymentFormProps {
activeProduct: IPaywallProduct;
}
function PaymentForm({
activeProduct,
}: IPaymentFormProps) {
const navigate = useNavigate();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const ref = useRef<HTMLDivElement>();
const currency = useSelector(selectors.selectCurrency);
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
const [isPaymentError, setIsPaymentError] = useState(false);
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
const onPaymentError = (error?: string) => {
setIsPaymentError(true);
if (error === "Product not found") {
navigate(routes.client.compatibilityV4TrialChoice());
}
}
const onPaymentSuccess = () => {
setIsPaymentSuccess(true);
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// if (activeProduct) {
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
// });
// }
setTimeout(() => {
navigate(routes.client.compatibilityV4SkipTrial());
}, 1500);
}
const onModalClosed = () => {
setIsPaymentModalOpen(false);
navigate(routes.client.compatibilityV4SaveOff());
}
if (isPaymentError) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
if (isPaymentSuccess) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
</div>
);
}
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={cn(
styles.paymentModalContainer
)}
>
<PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal>
<div className={styles.paymentModalPrice}>
{translate(
"/payment.total_due",
{
trialPrice: addCurrency(
getFormattedPrice(activeProduct.trialPrice || 0),
currency
),
},
ELocalesPlacement.CompatibilityV4
)}
</div>
<div
className={styles.paymentCreditCard}
onClick={() => setIsPaymentModalOpen(true)}
// onClick={() => showCreditCardForm()}
>
<CreditCardIcon />
<div>Credit / Debit Card</div>
</div>
{/* <GooglePayButton />
<ApplePayButton /> */}
<div className={styles.infoContainer}>
<SecurityPayments />
<p className={styles.address}>
{translate(
"payment_modal.address",
undefined,
ELocalesPlacement.V1
)}
</p>
</div>
</div>
);
}
export default PaymentForm

View File

@ -1,94 +0,0 @@
.paymentModalContainer {
display: flex;
flex-direction: column;
position: relative;
margin: -12px -20px;
padding: 12px 20px;
gap: 6px;
transition: height 1s ease-out;
.address {
color: gray;
font-size: 10px;
margin-bottom: 16px;
text-transform: uppercase;
}
.infoContainer>* {
padding-top: 16px;
}
.paymentCreditCard {
background: #066fde;
color: #fff !important;
gap: 6px;
display: flex;
font-size: 14px;
line-height: 18px;
align-items: center;
font-weight: 400;
min-height: 48px;
border-radius: 5px;
justify-content: center;
cursor: pointer;
}
&Loading {
background: rgba(215, 213, 213, .5);
}
.paymentModalLoader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
color: #2f2e37;
position: absolute;
width: 100%;
margin-left: -20px;
}
.paymentModalPrice {
color: #066fde;
font-size: 16px;
font-weight: 700;
line-height: 25px;
text-align: center;
margin-bottom: 12px;
}
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
&>.icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
&>.text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
.modal-content {
overflow-x: hidden;
}

View File

@ -1,32 +0,0 @@
import { selectors } from '@/store';
import { HTMLAttributes } from 'react';
import { useSelector } from 'react-redux';
import styles from "./styles.module.scss";
import PaymentForm from './PaymentForm';
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
return (
<>
{
activeProductFromStore && (
<div
{...props}
className={styles.container}
>
<div
className={`${styles.widget} ${props.className}`}
>
<PaymentForm
activeProduct={activeProductFromStore}
/>
</div>
</div>
)
}
</>
);
}
export default PaymentModalV1;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,11 +1,11 @@
import Title from "@/components/Title"; import Title from "@/components/Title";
import styles from "./styles.module.scss"; import styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker"; import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react"; import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import Button from "../../components/Button"; import Button from "../../components/Button";
import metricService from "@/services/metric/metricService"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import routes from "@/routes"; import routes from "@/routes";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
@ -56,6 +56,17 @@ function Birthdate() {
navigate(routes.client.compatibilityV4CalculateInAdvance()); navigate(routes.client.compatibilityV4CalculateInAdvance());
}; };
useEffect(() => {
const ua = window.navigator.userAgent;
if (ua.includes("FBAN") || ua.includes("FBAV") || ua.includes("FBIOS")) {
metricService.reachGoal(EGoals.STAYED_IN_FB, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
if (ua.includes("Instagram")) {
metricService.reachGoal(EGoals.STAYED_IN_INSTAGRAM, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
}, [])
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}> <Title variant="h2" className={styles.title}>

View File

@ -18,7 +18,8 @@ function DateEvent() {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { dateEvent: dateEventFromStore, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers); const { dateEvent: dateEventFromStore, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const translateSeparator = relationshipStatus === "single" ? "single" : "relationship"; const isSingle = relationshipStatus === "single";
const translateSeparator = isSingle ? "single" : "relationship";
const [dateEvent, setDateEvent] = useState(dateEventFromStore); const [dateEvent, setDateEvent] = useState(dateEventFromStore);
const [isDisabled, setIsDisabled] = useState(true); const [isDisabled, setIsDisabled] = useState(true);
@ -30,7 +31,11 @@ function DateEvent() {
const handleNext = () => { const handleNext = () => {
dispatch(actions.compatibilityV4Answers.update({ dateEvent })); dispatch(actions.compatibilityV4Answers.update({ dateEvent }));
navigate(routes.client.compatibilityV4PartnerAnalysis()); navigate(
isSingle
? routes.client.compatibilityV4HeadOrHeart()
: routes.client.compatibilityV4PartnerAnalysis()
);
updateSession( updateSession(
{ {
answers: { answers: {

View File

@ -0,0 +1,55 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import Button from "../../components/Button";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
function FormerPartnerBirthdate() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const navigate = useNavigate();
const dispatch = useDispatch();
const { formerPartnerBirthdate: birthdateFromStore } = useSelector(selectors.selectCompatibilityV4Answers);
const [birthdate, setBirthdate] = useState(birthdateFromStore || "");
const [isDisabled, setIsDisabled] = useState(true);
const handleValid = (_birthdate: string) => {
setBirthdate(_birthdate);
setIsDisabled(_birthdate === "");
};
const handleNext = () => {
dispatch(actions.compatibilityV4Answers.update({ formerPartnerBirthdate: birthdate }));
navigate(routes.client.compatibilityV4Loading());
};
return (
<div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}>
{translate("/former-partner-birthdate.title")}
</Title>
<p className={styles.description}>{translate("/former-partner-birthdate.text")}</p>
<DatePicker
name="birthdate"
value={birthdate}
onValid={handleValid}
onInvalid={() => setIsDisabled(true)}
inputClassName="date-picker-input"
/>
<Button
className={styles.button}
onClick={handleNext}
disabled={isDisabled}
>
{translate("next")}
</Button>
</div>
);
}
export default FormerPartnerBirthdate;

View File

@ -0,0 +1,22 @@
.page-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
margin-bottom: 0;
font-size: 27px;
}
.description {
margin: 16px 0 25px;
text-align: center;
font-size: 20px;
}
.image {
margin-top: 56px;
}

View File

@ -0,0 +1,69 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import Button from "../../components/Button";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectors, actions } from "@/store";
import { useDispatch } from "react-redux";
import { useEffect, useState } from "react";
import BirthplaceInput from "@/components/pages/ABDesign/v1/pages/EmailEnterPage/BirthplaceInput";
function FormerPartnerName() {
const dispatch = useDispatch();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { formerPartnerName } = useSelector(selectors.selectCompatibilityV4Answers);
const [isValidFormerPartnerName, setIsValidFormerPartnerName] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (formerPartnerName) {
setIsValidFormerPartnerName(true);
}
}, [formerPartnerName])
const handleValidFormerPartnerName = (formerPartnerName: string) => {
if (formerPartnerName) {
dispatch(
actions.compatibilityV4Answers.update({
formerPartnerName,
})
);
}
setIsValidFormerPartnerName(true);
};
const handleClick = () => {
navigate(routes.client.compatibilityV4FormerPartnerBirthdate());
}
return (
<div className={styles.container}>
<Title variant="h2" className={styles.title}>
{translate("/former-partner-name.title")}
</Title>
<p className={styles.text}>
{translate("/former-partner-name.text")}
</p>
<BirthplaceInput
value={formerPartnerName}
placeholder={translate("/former-partner-name.placeholder")}
inputClassName={styles.input}
placeholderClassName={styles.placeholder}
onValid={handleValidFormerPartnerName}
onInvalid={() => setIsValidFormerPartnerName(false)}
/>
<Button
className={styles.button}
disabled={!isValidFormerPartnerName}
onClick={handleClick}
>
{translate("next")}
</Button>
</div>
)
}
export default FormerPartnerName

View File

@ -0,0 +1,40 @@
.container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 27px;
font-weight: 600;
color: #121620;
line-height: 40px;
}
.text {
font-size: 20px;
font-weight: 400;
color: #121620;
line-height: 25px;
margin-top: 33px;
margin-bottom: 57px;
text-align: center;
}
.button {
margin-top: 37px;
}
.input {
border: 2px solid #DEE5F9;
border-radius: 8px !important;
&:focus {
border-color: #6B7BAA !important;
}
}
.placeholder {
color: #6B7BAA;
}

View File

@ -40,7 +40,7 @@ function Loading() {
const [isPause, setIsPause] = useState(false); const [isPause, setIsPause] = useState(false);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const interval = useRef<NodeJS.Timeout>(); const interval = useRef<NodeJS.Timeout>();
const { whatAddToAnalysis } = useSelector(selectors.selectCompatibilityV4Answers); const { whatAddToAnalysis, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const feature = useSelector(selectors.selectFeature); const feature = useSelector(selectors.selectFeature);
const isIOSPath = useMemo(() => feature?.toLowerCase()?.includes("ios"), [feature]); const isIOSPath = useMemo(() => feature?.toLowerCase()?.includes("ios"), [feature]);
const authCode = useSelector(selectors.selectAuthCode); const authCode = useSelector(selectors.selectAuthCode);
@ -48,6 +48,7 @@ function Loading() {
const { gender, partnerGender, birthdate, partnerBirthdate } = useSelector(selectors.selectQuestionnaire); const { gender, partnerGender, birthdate, partnerBirthdate } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate); const zodiacSign = getZodiacSignByDate(birthdate);
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const isSingle = relationshipStatus === "single";
const preloadImages = useMemo(() => { const preloadImages = useMemo(() => {
return [ return [
@ -71,8 +72,8 @@ function Loading() {
const loadingProfilePoints: IPoint[] = useMemo(() => { const loadingProfilePoints: IPoint[] = useMemo(() => {
const titles: IPoint[] = [ const titles: IPoint[] = [
{ {
title1: `/loading.loaders.title-1-1`, title1: `/loading.loaders${isSingle ? ".single" : ""}.title-1-1`,
title2: `/loading.loaders.title-1-2`, title2: `/loading.loaders${isSingle ? ".single" : ""}.title-1-2`,
modal: { modal: {
title: `/loading.modals.title-1`, title: `/loading.modals.title-1`,
description: `/loading.modals.description-1`, description: `/loading.modals.description-1`,
@ -81,8 +82,8 @@ function Loading() {
} }
}, },
{ {
title1: `/loading.loaders.title-2-1`, title1: `/loading.loaders${isSingle ? ".single" : ""}.title-2-1`,
title2: `/loading.loaders.title-2-2`, title2: `/loading.loaders${isSingle ? ".single" : ""}.title-2-2`,
modal: { modal: {
title: `/loading.modals.title-2`, title: `/loading.modals.title-2`,
description: `/loading.modals.description-2`, description: `/loading.modals.description-2`,
@ -91,8 +92,8 @@ function Loading() {
} }
}, },
{ {
title1: `/loading.loaders.title-3-1`, title1: `/loading.loaders${isSingle ? ".single" : ""}.title-3-1`,
title2: `/loading.loaders.title-3-2`, title2: `/loading.loaders${isSingle ? ".single" : ""}.title-3-2`,
modal: { modal: {
title: `/loading.modals.title-3`, title: `/loading.modals.title-3`,
description: `/loading.modals.description-3`, description: `/loading.modals.description-3`,
@ -104,11 +105,11 @@ function Loading() {
const whatAddToAnalysisAnswers = whatAddToAnalysis.split(",").slice(0, 3); const whatAddToAnalysisAnswers = whatAddToAnalysis.split(",").slice(0, 3);
whatAddToAnalysisAnswers.forEach((answer, index) => { whatAddToAnalysisAnswers.forEach((answer, index) => {
if (!!answer?.length) { if (!!answer?.length) {
titles[index].title3 = `/what-add-to-analysis.${answer}` titles[index].title3 = `/what-add-to-analysis${isSingle ? ".single" : ""}.${answer}`
} }
}) })
return titles; return titles;
}, [whatAddToAnalysis]); }, [whatAddToAnalysis, isSingle]);
useEffect(() => { useEffect(() => {
if (isIOSPath) { if (isIOSPath) {

View File

@ -12,11 +12,14 @@ import { getZodiacSignByDate } from "@/services/zodiac-sign";
function PalmsInformation() { function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const { gender, birthdate } = useSelector(selectors.selectQuestionnaire); const { gender, birthdate } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate); const zodiacSign = getZodiacSignByDate(birthdate);
const navigate = useNavigate(); const navigate = useNavigate();
const isSingle = relationshipStatus === "single";
const handleNext = () => { const handleNext = () => {
if (isSingle) return navigate(routes.client.compatibilityV4Review());
navigate(routes.client.compatibilityV4ResultAnalysis()); navigate(routes.client.compatibilityV4ResultAnalysis());
}; };

View File

@ -12,7 +12,7 @@ import { selectors } from "@/store";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { usePreloadImages } from "@/hooks/preload/images"; import { usePreloadImages } from "@/hooks/preload/images";
const POINTS_COUNT = 11; const POINTS_COUNT = 4;
const TOTAL_ANIMATION_TIME = 24600; const TOTAL_ANIMATION_TIME = 24600;
function PartnerAnalysis() { function PartnerAnalysis() {
@ -39,8 +39,6 @@ function PartnerAnalysis() {
setListHeight(maxHeight); setListHeight(maxHeight);
}, [listRef]); }, [listRef]);
// const [currentPoint, setCurrentPoint] = useState(0);
const handleNext = useCallback(() => { const handleNext = useCallback(() => {
navigate(routes.client.compatibilityV4PalmsInformationPartner()); navigate(routes.client.compatibilityV4PalmsInformationPartner());
}, [navigate]); }, [navigate]);
@ -62,24 +60,6 @@ function PartnerAnalysis() {
})(); })();
}, [handleNext, loadingProgress]); }, [handleNext, loadingProgress]);
// const getPointsHeight = useCallback(() => {
// return listRef.current.slice(currentPoint, currentPoint + 4).reduce((acc, curr) => {
// return acc + (curr?.offsetHeight || 0) + 32;
// }, 0) - 32;
// }, [currentPoint]);
// useEffect(() => {
// (async () => {
// await sleep(TOTAL_ANIMATION_TIME / (POINTS_COUNT - 1));
// setCurrentPoint((value) => {
// if (value >= POINTS_COUNT - 4) {
// return value;
// }
// return value + 1;
// });
// })()
// }, [currentPoint])
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles["progress-container"]}> <div className={styles["progress-container"]}>
@ -102,7 +82,6 @@ function PartnerAnalysis() {
{translate("/partner-analysis.title")} {translate("/partner-analysis.title")}
</Title> </Title>
<div className={styles.list} style={{ <div className={styles.list} style={{
// height: `${getPointsHeight()}px`
height: `${listHeight}px` height: `${listHeight}px`
}}> }}>
{Array.from(Array(POINTS_COUNT).keys()).map((index) => ( {Array.from(Array(POINTS_COUNT).keys()).map((index) => (
@ -111,12 +90,6 @@ function PartnerAnalysis() {
className={styles.item} className={styles.item}
ref={(el) => listRef.current[index] = el} ref={(el) => listRef.current[index] = el}
style={{ style={{
// marginTop: (() => {
// if (index < currentPoint) {
// return `${-(listRef.current[index]?.offsetHeight || 0) - 32}px`
// }
// return "0px"
// })(),,
animationDuration: `${TOTAL_ANIMATION_TIME / (POINTS_COUNT)}ms`, animationDuration: `${TOTAL_ANIMATION_TIME / (POINTS_COUNT)}ms`,
animationDelay: `${index * (TOTAL_ANIMATION_TIME / (POINTS_COUNT))}ms` animationDelay: `${index * (TOTAL_ANIMATION_TIME / (POINTS_COUNT))}ms`
}} }}

View File

@ -0,0 +1,75 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import Answer from "../../components/Answer";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { sleep } from "@/services/date";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { answerTimeOut } from "../../data";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import { useMemo } from "react";
import { useSession } from "@/hooks/session/useSession";
import { IAnswersSessionCompatibilityV4 } from "@/api/resources/Session";
import { ESourceAuthorization } from "@/api/resources/User";
function PartnerPassword() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { updateSession } = useSession();
const navigate = useNavigate();
const dispatch = useDispatch();
const { partnerPassword } = useSelector(
selectors.selectCompatibilityV4Answers
);
const answers: {
id: IAnswersSessionCompatibilityV4["partner_password"];
title: string;
}[] = useMemo(
() => [
{
id: "normal",
title: translate("/partner-password.answer1"),
},
{
id: "not_like",
title: translate("/partner-password.answer2"),
},
{
id: "exchange_passwords",
title: translate("/partner-password.answer3"),
},
],
[translate]
);
const handleClick = async (id: IAnswersSessionCompatibilityV4["partner_password"]) => {
dispatch(actions.compatibilityV4Answers.update({ partnerPassword: id }));
updateSession({
answers: {
partner_password: id,
},
}, ESourceAuthorization["aura.compatibility.v4"]);
if (id !== partnerPassword) await sleep(answerTimeOut);
return navigate(routes.client.compatibilityV4YourFear());
};
return (
<div className={styles.container}>
<Title variant="h2" className={styles.title}>
{translate("/partner-password.title")}
</Title>
{answers.map((answers, index) => (
<Answer
key={index}
answer={answers}
isSelected={partnerPassword === answers.id}
onClick={() => handleClick(answers.id)}
/>
))}
</div>
);
}
export default PartnerPassword;

View File

@ -0,0 +1,3 @@
.container {
width: 100%;
}

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import routes from "@/routes"; import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.4'];
function Payment() { function Payment() {
const navigate = useNavigate(); const navigate = useNavigate();
const { products, currency, getText } = usePaywall({ const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 0; const trialPrice = activeProductFromStore?.trialPrice || 0;
const fullPrice = activeProductFromStore?.price || 0; const fullPrice = activeProductFromStore?.price || 0;
// const isShowPaymentModal = useSelector(
// selectors.selectCompatibilityV4IsShowPaymentModalV1
// );
// const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
// const [isPaymentError, setIsPaymentError] = useState(false);
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const onPaymentError = (error?: string) => {
// setIsPaymentError(true);
// if (error === "Product not found") {
// return navigate(routes.client.compatibilityV4TrialChoice());
// }
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// }
// const onPaymentSuccess = () => {
// setIsPaymentSuccess(true);
// // metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// // if (activeProductFromStore) {
// // metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// // currency: "USD",
// // value: ((activeProductFromStore.trialPrice || 100) / 100).toFixed(2),
// // });
// // }
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProductFromStore?.trialPrice || 100) / 100).toFixed(2),
// });
// setTimeout(() => {
// navigate(routes.client.compatibilityV4SkipTrial());
// }, 1500);
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// navigate(routes.client.compatibilityV4SaveOff());
// }
const handlePayment = () => { const handlePayment = () => {
// dispatch(actions.compatibilityV4.setIsShowPaymentModalV1(true));
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
navigate(routes.client.compatibilityV4PaymentModal()); navigate(routes.client.compatibilityV4PaymentModal());
}; };
// useEffect(() => {
// window.onpopstate = function (_event) {
// if (document.location.toString() === `${window.location.origin}${routes.client.compatibilityV4TrialPayment()}`) {
// return navigate(routes.client.compatibilityV4SaveOff());
// }
// };
// return () => {
// setTimeout(() => {
// window.onpopstate = null;
// }, 0);
// };
// }, [])
useEffect(() => { useEffect(() => {
if (!products.length) return; if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id); const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return ( return (
<> <>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}> <div className={styles["app-number-one"]}>
<p className={styles.text}> <p className={styles.text}>
{translate("/payment.app_number_one", { {translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation /> <PaymentInformation />
<div className={styles["prices-description"]}> <div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", { {translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice( splitPrice: addCurrency(getFormattedPrice(
( (
fullPrice / ( fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})} })}
</div> </div>
<Guarantees /> <Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}> <Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")} {translate("/payment.get_personal_prediction")}
</Button> </Button>
{/* )} */}
{/* {isPaymentError && (
<Toast
variant="error"
classNameContainer={styles.toast}
>
<p>Something went wrong</p>
</Toast>
)}
{isPaymentSuccess && (
<Toast
variant="success"
classNameContainer={styles.toast}
>
<p>Payment successful</p>
</Toast>
)} */}
</> </>
); );

View File

@ -0,0 +1,59 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import Button from "../../components/Button";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
function PotentialPartnerBirthdate() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const navigate = useNavigate();
const dispatch = useDispatch();
const { potentialPartnerBirthdate: birthdateFromStore, whatAddToAnalysis, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const isSingle = relationshipStatus === "single";
const [birthdate, setBirthdate] = useState(birthdateFromStore || "");
const [isDisabled, setIsDisabled] = useState(true);
const handleValid = (_birthdate: string) => {
setBirthdate(_birthdate);
setIsDisabled(_birthdate === "");
};
const handleNext = () => {
dispatch(actions.compatibilityV4Answers.update({ potentialPartnerBirthdate: birthdate }));
if (whatAddToAnalysis.includes("former_partner") && !isSingle) {
return navigate(routes.client.compatibilityV4FormerPartnerName());
}
navigate(routes.client.compatibilityV4Loading());
};
return (
<div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}>
{translate("/potential-partner-birthdate.title")}
</Title>
<p className={styles.description}>{translate("/potential-partner-birthdate.text")}</p>
<DatePicker
name="birthdate"
value={birthdate}
onValid={handleValid}
onInvalid={() => setIsDisabled(true)}
inputClassName="date-picker-input"
/>
<Button
className={styles.button}
onClick={handleNext}
disabled={isDisabled}
>
{translate("next")}
</Button>
</div>
);
}
export default PotentialPartnerBirthdate;

View File

@ -0,0 +1,22 @@
.page-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
margin-bottom: 0;
font-size: 27px;
}
.description {
margin: 16px 0 25px;
text-align: center;
font-size: 20px;
}
.image {
margin-top: 56px;
}

View File

@ -0,0 +1,69 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import Button from "../../components/Button";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectors, actions } from "@/store";
import { useDispatch } from "react-redux";
import { useEffect, useState } from "react";
import BirthplaceInput from "@/components/pages/ABDesign/v1/pages/EmailEnterPage/BirthplaceInput";
function PotentialPartnerName() {
const dispatch = useDispatch();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { potentialPartnerName } = useSelector(selectors.selectCompatibilityV4Answers);
const [isValidPotentialPartnerName, setIsValidPotentialPartnerName] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (potentialPartnerName) {
setIsValidPotentialPartnerName(true);
}
}, [potentialPartnerName])
const handleValidPotentialPartnerName = (potentialPartnerName: string) => {
if (potentialPartnerName) {
dispatch(
actions.compatibilityV4Answers.update({
potentialPartnerName,
})
);
}
setIsValidPotentialPartnerName(true);
};
const handleClick = () => {
navigate(routes.client.compatibilityV4PotentialPartnerBirthdate());
}
return (
<div className={styles.container}>
<Title variant="h2" className={styles.title}>
{translate("/potential-partner-name.title")}
</Title>
<p className={styles.text}>
{translate("/potential-partner-name.text")}
</p>
<BirthplaceInput
value={potentialPartnerName}
placeholder={translate("/potential-partner-name.placeholder")}
inputClassName={styles.input}
placeholderClassName={styles.placeholder}
onValid={handleValidPotentialPartnerName}
onInvalid={() => setIsValidPotentialPartnerName(false)}
/>
<Button
className={styles.button}
disabled={!isValidPotentialPartnerName}
onClick={handleClick}
>
{translate("next")}
</Button>
</div>
)
}
export default PotentialPartnerName

View File

@ -0,0 +1,40 @@
.container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 27px;
font-weight: 600;
color: #121620;
line-height: 40px;
}
.text {
font-size: 20px;
font-weight: 400;
color: #121620;
line-height: 25px;
margin-top: 33px;
margin-bottom: 57px;
text-align: center;
}
.button {
margin-top: 37px;
}
.input {
border: 2px solid #DEE5F9;
border-radius: 8px !important;
&:focus {
border-color: #6B7BAA !important;
}
}
.placeholder {
color: #6B7BAA;
}

View File

@ -8,6 +8,8 @@ import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
import { useSession } from "@/hooks/session/useSession"; import { useSession } from "@/hooks/session/useSession";
import { ESourceAuthorization } from "@/api/resources/User"; import { ESourceAuthorization } from "@/api/resources/User";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
function RelateFollowing() { function RelateFollowing() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
@ -15,6 +17,8 @@ function RelateFollowing() {
const navigate = useNavigate(); const navigate = useNavigate();
const { questionId } = useParams(); const { questionId } = useParams();
const [activeButton, setActiveButton] = useState<number | null>(); const [activeButton, setActiveButton] = useState<number | null>();
const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const isSingle = relationshipStatus === "single";
const questions: { const questions: {
id: "partner_expectations"; id: "partner_expectations";
@ -60,6 +64,7 @@ function RelateFollowing() {
}` }`
); );
} }
if (isSingle) return navigate(routes.client.compatibilityV4PartnerPassword());
return navigate(routes.client.compatibilityV4CheckingPhone()); return navigate(routes.client.compatibilityV4CheckingPhone());
}; };

View File

@ -28,10 +28,10 @@ function RelationshipStatus() {
title: string; title: string;
}[] = useMemo( }[] = useMemo(
() => [ () => [
// { {
// id: "single", id: "single",
// title: translate("/relationship-status.answer1"), title: translate("/relationship-status.answer5"),
// }, },
{ {
id: "start", id: "start",
title: translate("/relationship-status.answer1"), title: translate("/relationship-status.answer1"),

View File

@ -5,23 +5,28 @@ import { ELocalesPlacement } from '@/locales'
import Button from '../../components/Button' import Button from '../../components/Button'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import routes from '@/routes' import routes from '@/routes'
import { useSelector } from 'react-redux'
import { selectors } from '@/store'
function ReviewPage() { function ReviewPage() {
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4) const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4)
const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const isSingle = relationshipStatus === "single";
const handleNext = () => { const handleNext = () => {
if (isSingle) return navigate(routes.client.compatibilityV4DateEvent());
navigate(routes.client.compatibilityV4GenderPartner()); navigate(routes.client.compatibilityV4GenderPartner());
}; };
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Review <Review
username={translate('/review.username')} username={translate(`/review${isSingle ? '.single' : ''}.username`)}
// avatar={translate('/review.avatar')} // avatar={translate('/review.avatar')}
text={translate('/review.text')} text={translate(`/review${isSingle ? '.single' : ''}.text`)}
date={translate('/review.date')} date={translate(`/review${isSingle ? '.single' : ''}.date`)}
/> />
<Button className={styles.button} onClick={handleNext}> <Button className={styles.button} onClick={handleNext}>
{translate("next")} {translate("next")}

View File

@ -30,10 +30,7 @@ function SecretDiscount() {
}); });
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV4); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV4);
// const activeProductFromStore = useSelector(selectors.selectActiveProduct);
// const activeProduct = products.find(product => product?.trialPrice === activeProductFromStore?.trialPrice) || products[0]
const activeProduct = products[0] const activeProduct = products[0]
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +42,19 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// const onPaymentSuccess = () => {
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct?.trialPrice || 100) / 100).toFixed(2),
// });
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// return navigate(routes.client.paymentFail())
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.compatibilityV4SecretDiscountPaymentModal()); navigate(routes.client.compatibilityV4SecretDiscountPaymentModal());
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return ( return (
<section className={styles.container} ref={elementRef} style={{ <section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px` paddingBottom: `${height + 42}px`
}}> }}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header <Header
className={styles.header} className={styles.header}
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -19,9 +19,10 @@ function StressResponse() {
const { updateSession } = useSession(); const { updateSession } = useSession();
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { stressResponse } = useSelector( const { stressResponse, relationshipStatus } = useSelector(
selectors.selectCompatibilityV4Answers selectors.selectCompatibilityV4Answers
); );
const isSingle = relationshipStatus === "single";
const answers: { const answers: {
id: IAnswersSessionCompatibilityV4["stress_response"]; id: IAnswersSessionCompatibilityV4["stress_response"];
@ -30,19 +31,19 @@ function StressResponse() {
() => [ () => [
{ {
id: "compromise", id: "compromise",
title: translate("/stress-response.answer1"), title: translate(`/stress-response${isSingle ? ".single" : ""}.answer1`),
}, },
{ {
id: "pause", id: "pause",
title: translate("/stress-response.answer2"), title: translate(`/stress-response${isSingle ? ".single" : ""}.answer2`),
}, },
{ {
id: "anger", id: "anger",
title: translate("/stress-response.answer3"), title: translate(`/stress-response${isSingle ? ".single" : ""}.answer3`),
}, },
{ {
id: "distance", id: "distance",
title: translate("/stress-response.answer4"), title: translate(`/stress-response${isSingle ? ".single" : ""}.answer4`),
}, },
], ],
[translate] [translate]
@ -63,7 +64,7 @@ function StressResponse() {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Title variant="h2" className={styles.title}> <Title variant="h2" className={styles.title}>
{translate("/stress-response.title")} {translate(`/stress-response${isSingle ? ".single" : ""}.title`)}
</Title> </Title>
{answers.map((answers, index) => ( {answers.map((answers, index) => (
<Answer <Answer

View File

@ -9,18 +9,19 @@ import { useNavigate } from "react-router-dom"
function StressResponseResult() { function StressResponseResult() {
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4) const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4)
const { stressResponse } = useSelector(selectors.selectCompatibilityV4Answers) const { stressResponse, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers)
const isSingle = relationshipStatus === "single"
return ( return (
<AnswerExplanation <AnswerExplanation
title={translate(`/stress-response-result.${stressResponse}.title`, { title={translate(`/stress-response-result${isSingle ? ".single" : ""}.${stressResponse}.title`, {
bold: <b> bold: <b>
{ {
translate(`/stress-response-result.${stressResponse}.title_bold`) translate(`/stress-response-result${isSingle ? ".single" : ""}.${stressResponse}.title_bold`)
} }
</b> </b>
})} })}
text={translate(`/stress-response-result.${stressResponse}.text`)} text={translate(`/stress-response-result${isSingle ? ".single" : ""}.${stressResponse}.text`)}
handleNext={() => { handleNext={() => {
navigate(routes.client.compatibilityV4WhatAddToAnalysis()) navigate(routes.client.compatibilityV4WhatAddToAnalysis())
}} }}

View File

@ -13,14 +13,14 @@ function ZodiacImagesWithBook() {
const { dateEvent, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers); const { dateEvent, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const zodiacSign = getZodiacSignByDate(birthdate); const zodiacSign = getZodiacSignByDate(birthdate);
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const isSingle = relationshipStatus === "single";
return ( return (
<div className={styles.container}> <div className={styles.container}>
<p className={styles.title}> <p className={styles.title}>
{translate("/trial-payment.zodiac-images-container-title")} {translate("/trial-payment.zodiac-images-container-title")}
</p> </p>
<ZodiacImages <ZodiacImages
classNameContainer={styles.zodiacImagesContainer} classNameContainer={`${styles.zodiacImagesContainer} ${isSingle ? styles.zodiacImagesContainerSingle : ""}`}
gender={gender} gender={gender}
zodiacSign={zodiacSign} zodiacSign={zodiacSign}
relationshipStatus={relationshipStatus} relationshipStatus={relationshipStatus}

View File

@ -50,4 +50,8 @@
top: calc(100% * 226 / 458 - 100% * 223 / 458 * 1229 / 2800); top: calc(100% * 226 / 458 - 100% * 223 / 458 * 1229 / 2800);
left: calc(100% * 4 / 350); left: calc(100% * 4 / 350);
margin-top: 0; margin-top: 0;
&.zodiacImagesContainerSingle>img {
width: 50%;
}
} }

View File

@ -30,7 +30,6 @@ function TrialPayment() {
const { dateEvent } = useSelector(selectors.selectCompatibilityV4Answers); const { dateEvent } = useSelector(selectors.selectCompatibilityV4Answers);
const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers); const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
// const relationshipStatus = "single";
const zodiacSign = getZodiacSignByDate(birthdate); const zodiacSign = getZodiacSignByDate(birthdate);
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const time = useTimer(); const time = useTimer();

View File

@ -20,9 +20,10 @@ function WhatAddToAnalysis() {
const { updateSession } = useSession(); const { updateSession } = useSession();
const navigate = useNavigate(); const navigate = useNavigate();
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { whatAddToAnalysis } = useSelector( const { whatAddToAnalysis, relationshipStatus } = useSelector(
selectors.selectCompatibilityV4Answers selectors.selectCompatibilityV4Answers
); );
const isSingle = relationshipStatus === "single";
useLottie({ useLottie({
preloadKey: ELottieKeys.scannedPhoto, preloadKey: ELottieKeys.scannedPhoto,
}); });
@ -31,41 +32,104 @@ function WhatAddToAnalysis() {
id: IAnswersSessionCompatibilityV4["what_add_to_analysis"]; id: IAnswersSessionCompatibilityV4["what_add_to_analysis"];
title: string; title: string;
}[] = useMemo( }[] = useMemo(
() => [ () => {
{ if (isSingle) {
id: "5_compatibility_zones", return [
title: translate("/what-add-to-analysis.5_compatibility_zones"), {
}, id: "potential_partner",
{ title: translate("/what-add-to-analysis.single.potential_partner"),
id: "analyzing_mistakes_relationships", },
title: translate("/what-add-to-analysis.analyzing_mistakes_relationships"), {
}, id: "family_love_model",
{ title: translate("/what-add-to-analysis.single.family_love_model"),
id: "astrological_triggers", },
title: translate("/what-add-to-analysis.astrological_triggers"), {
}, id: "strong_connection_date",
{ title: translate("/what-add-to-analysis.single.strong_connection_date"),
id: "relationship_timing", },
title: translate("/what-add-to-analysis.relationship_timing"), {
}, id: "dangerous_partner_type",
{ title: translate("/what-add-to-analysis.single.dangerous_partner_type"),
id: "practices_strengthen_communication", },
title: translate("/what-add-to-analysis.practices_strengthen_communication"), {
}, id: "relationship_karma",
{ title: translate("/what-add-to-analysis.single.relationship_karma"),
id: "scenarios_times_crisis", },
title: translate("/what-add-to-analysis.scenarios_times_crisis"), {
}, id: "when_not_start_relationship",
{ title: translate("/what-add-to-analysis.single.when_not_start_relationship"),
id: "astrological_view_conflicts", },
title: translate("/what-add-to-analysis.astrological_view_conflicts"), {
}, id: "scenario_next_love",
{ title: translate("/what-add-to-analysis.single.scenario_next_love"),
id: "energy_compatibility", },
title: translate("/what-add-to-analysis.energy_compatibility"), {
}, id: "what_radiate_in_relationship",
], title: translate("/what-add-to-analysis.single.what_radiate_in_relationship"),
[translate] },
{
id: "how_partners_see_you",
title: translate("/what-add-to-analysis.single.how_partners_see_you"),
},
{
id: "key_compatibility_areas",
title: translate("/what-add-to-analysis.single.key_compatibility_areas"),
},
]
}
return [
// {
// id: "5_compatibility_zones",
// title: translate("/what-add-to-analysis.5_compatibility_zones"),
// },
{
id: "former_partner",
title: translate("/what-add-to-analysis.former_partner"),
},
{
id: "potential_partner",
title: translate("/what-add-to-analysis.potential_partner"),
},
{
id: "analyzing_mistakes_relationships",
title: translate("/what-add-to-analysis.analyzing_mistakes_relationships"),
},
{
id: "astrological_triggers",
title: translate("/what-add-to-analysis.astrological_triggers"),
},
{
id: "relationship_timing",
title: translate("/what-add-to-analysis.relationship_timing"),
},
{
id: "practices_strengthen_communication",
title: translate("/what-add-to-analysis.practices_strengthen_communication"),
},
{
id: "scenarios_times_crisis",
title: translate("/what-add-to-analysis.scenarios_times_crisis"),
},
{
id: "astrological_view_conflicts",
title: translate("/what-add-to-analysis.astrological_view_conflicts"),
},
{
id: "energy_compatibility",
title: translate("/what-add-to-analysis.energy_compatibility"),
},
{
id: "key_compatibility_areas",
title: translate("/what-add-to-analysis.key_compatibility_areas"),
},
{
id: "talking_about_future",
title: translate("/what-add-to-analysis.talking_about_future"),
},
]
},
[translate, isSingle]
); );
const handleClick = async (id: IAnswersSessionCompatibilityV4["what_add_to_analysis"]) => { const handleClick = async (id: IAnswersSessionCompatibilityV4["what_add_to_analysis"]) => {
@ -99,6 +163,9 @@ function WhatAddToAnalysis() {
}, },
}, ESourceAuthorization["aura.compatibility.v4"]); }, ESourceAuthorization["aura.compatibility.v4"]);
if (whatAddToAnalysis.includes("potential_partner")) {
return navigate(routes.client.compatibilityV4PotentialPartnerName());
}
navigate(routes.client.compatibilityV4Loading()); navigate(routes.client.compatibilityV4Loading());
}; };

View File

@ -12,7 +12,7 @@ import { useSelector } from "react-redux";
import { selectors } from "@/store"; import { selectors } from "@/store";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
const POINTS_COUNT = 13; const POINTS_COUNT = 4;
const TOTAL_ANIMATION_TIME = 24600; const TOTAL_ANIMATION_TIME = 24600;
function YourAnalysis() { function YourAnalysis() {
@ -62,24 +62,6 @@ function YourAnalysis() {
})(); })();
}, [handleNext, loadingProgress]); }, [handleNext, loadingProgress]);
// const getPointsHeight = useCallback(() => {
// return listRef.current.slice(currentPoint, currentPoint + 4).reduce((acc, curr) => {
// return acc + (curr?.offsetHeight || 0) + 32;
// }, 0) - 32;
// }, [currentPoint]);
// useEffect(() => {
// (async () => {
// await sleep(TOTAL_ANIMATION_TIME / (POINTS_COUNT - 1));
// setCurrentPoint((value) => {
// if (value >= POINTS_COUNT - 4) {
// return value;
// }
// return value + 1;
// });
// })()
// }, [currentPoint])
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles["progress-container"]}> <div className={styles["progress-container"]}>
@ -102,7 +84,6 @@ function YourAnalysis() {
{translate("/your-analysis.title")} {translate("/your-analysis.title")}
</Title> </Title>
<div className={styles.list} style={{ <div className={styles.list} style={{
// height: `${getPointsHeight()}px`
height: `${listHeight}px` height: `${listHeight}px`
}}> }}>
{Array.from(Array(POINTS_COUNT).keys()).map((index) => ( {Array.from(Array(POINTS_COUNT).keys()).map((index) => (

View File

@ -0,0 +1,75 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import Answer from "../../components/Answer";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { sleep } from "@/services/date";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { answerTimeOut } from "../../data";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import { useMemo } from "react";
import { useSession } from "@/hooks/session/useSession";
import { IAnswersSessionCompatibilityV4 } from "@/api/resources/Session";
import { ESourceAuthorization } from "@/api/resources/User";
function YourFear() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { updateSession } = useSession();
const navigate = useNavigate();
const dispatch = useDispatch();
const { yourFear } = useSelector(
selectors.selectCompatibilityV4Answers
);
const answers: {
id: IAnswersSessionCompatibilityV4["your_fear"];
title: string;
}[] = useMemo(
() => [
{
id: "loneliness",
title: translate("/your-fear.answer1"),
},
{
id: "lose_yourself",
title: translate("/your-fear.answer2"),
},
{
id: "make_mistake",
title: translate("/your-fear.answer3"),
},
],
[translate]
);
const handleClick = async (id: IAnswersSessionCompatibilityV4["your_fear"]) => {
dispatch(actions.compatibilityV4Answers.update({ yourFear: id }));
updateSession({
answers: {
your_fear: id,
},
}, ESourceAuthorization["aura.compatibility.v4"]);
if (id !== yourFear) await sleep(answerTimeOut);
return navigate(routes.client.compatibilityV4YourInclination());
};
return (
<div className={styles.container}>
<Title variant="h2" className={styles.title}>
{translate("/your-fear.title")}
</Title>
{answers.map((answers, index) => (
<Answer
key={index}
answer={answers}
isSelected={yourFear === answers.id}
onClick={() => handleClick(answers.id)}
/>
))}
</div>
);
}
export default YourFear;

View File

@ -0,0 +1,3 @@
.container {
width: 100%;
}

View File

@ -0,0 +1,75 @@
import Title from "@/components/Title";
import styles from "./styles.module.scss";
import Answer from "../../components/Answer";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { sleep } from "@/services/date";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import { answerTimeOut } from "../../data";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import { useMemo } from "react";
import { useSession } from "@/hooks/session/useSession";
import { IAnswersSessionCompatibilityV4 } from "@/api/resources/Session";
import { ESourceAuthorization } from "@/api/resources/User";
function YourInclination() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
const { updateSession } = useSession();
const navigate = useNavigate();
const dispatch = useDispatch();
const { yourInclination } = useSelector(
selectors.selectCompatibilityV4Answers
);
const answers: {
id: IAnswersSessionCompatibilityV4["your_inclination"];
title: string;
}[] = useMemo(
() => [
{
id: "fall_in_love",
title: translate("/your-inclination.answer1"),
},
{
id: "be_careful",
title: translate("/your-inclination.answer2"),
},
{
id: "look_closely",
title: translate("/your-inclination.answer3"),
},
],
[translate]
);
const handleClick = async (id: IAnswersSessionCompatibilityV4["your_inclination"]) => {
dispatch(actions.compatibilityV4Answers.update({ yourInclination: id }));
updateSession({
answers: {
your_inclination: id,
},
}, ESourceAuthorization["aura.compatibility.v4"]);
if (id !== yourInclination) await sleep(answerTimeOut);
return navigate(routes.client.compatibilityV4StressResponse());
};
return (
<div className={styles.container}>
<Title variant="h2" className={styles.title}>
{translate("/your-inclination.title")}
</Title>
{answers.map((answers, index) => (
<Answer
key={index}
answer={answers}
isSelected={yourInclination === answers.id}
onClick={() => handleClick(answers.id)}
/>
))}
</div>
);
}
export default YourInclination;

View File

@ -0,0 +1,3 @@
.container {
width: 100%;
}

View File

@ -30,7 +30,6 @@ function SecretDiscount() {
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV1); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV1);
const activeProduct = products[0]; const activeProduct = products[0];
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
@ -41,48 +40,6 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
// const onPaymentSuccess = () => {
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// return navigate(routes.client.paymentFail())
// }
// const openPaymentModal = () => {
// setIsPaymentModalOpen(true);
// };
const handlePayment = () => { const handlePayment = () => {
navigate(routes.client.emailMarketingV1SecretDiscountPaymentModal()); navigate(routes.client.emailMarketingV1SecretDiscountPaymentModal());
} }
@ -96,20 +53,6 @@ function SecretDiscount() {
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -18,7 +18,6 @@ const placementKey = EPlacementKeys["aura.placement.email.marketing"];
function SpecialOffer() { function SpecialOffer() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
// const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
const activeProduct = useSelector(selectors.selectActiveProduct); const activeProduct = useSelector(selectors.selectActiveProduct);
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV1); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingCompatibilityV1);
@ -34,70 +33,12 @@ function SpecialOffer() {
dispatch(actions.payment.update({ activeProduct: products[0] })); dispatch(actions.payment.update({ activeProduct: products[0] }));
}, [dispatch, products]); }, [dispatch, products]);
// const {
// error,
// isPaymentSuccess,
// isModalClosed,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct: products[0],
// paymentFormType: "lightbox"
// });
const openPaymentModal = () => { const openPaymentModal = () => {
// setIsOpenPaymentModal(true);
// showCreditCardForm();
navigate(routes.client.emailMarketingV1PaymentModal()); navigate(routes.client.emailMarketingV1PaymentModal());
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
// useEffect(() => {
// if (isModalClosed) {
// handleCloseModal()
// }
// }, [isModalClosed])
// const handleCloseModal = () => {
// // setIsOpenPaymentModal(false);
// return navigate(routes.client.emailMarketingV1SaveOff())
// };
// const onPaymentError = () => {
// return navigate(routes.client.paymentFail())
// }
// const onPaymentSuccess = () => {
// return navigate(routes.client.paymentSuccess())
// }
return ( return (
<> <>
{/* {products[0] && (
<Modal
containerClassName={styles.modal}
open={isOpenPaymentModal}
onClose={handleCloseModal}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<div className={styles.container}> <div className={styles.container}>
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("special-offer.title")} {translate("special-offer.title")}

View File

@ -1,4 +1,6 @@
import { useTheme } from "@/hooks/theme/useTheme";
import "./styles.css"; import "./styles.css";
import { useMemo } from "react";
export enum LoaderColor { export enum LoaderColor {
White = "white", White = "white",
@ -11,13 +13,16 @@ type LoaderProps = {
className?: string; className?: string;
}; };
const colorClasses = {
[LoaderColor.White]: "loader__white",
[LoaderColor.Black]: "loader__black",
[LoaderColor.Red]: "loader__red",
};
function Loader({ color = LoaderColor.Black, className }: LoaderProps): JSX.Element { function Loader({ color = LoaderColor.Black, className }: LoaderProps): JSX.Element {
const { darkTheme } = useTheme();
const colorClasses = useMemo(() => ({
[LoaderColor.White]: "loader__white",
[LoaderColor.Black]: darkTheme ? "loader__white" : "loader__black",
[LoaderColor.Red]: "loader__red",
}), [darkTheme]);
return ( return (
<div className={`loader-container ${className}`}> <div className={`loader-container ${className}`}>
<div className={`loader ${colorClasses[color]}`}> <div className={`loader ${colorClasses[color]}`}>

View File

@ -1,77 +0,0 @@
import { useSearchParams } from "react-router-dom";
import styles from "./styles.module.scss";
import { HTMLAttributes, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import PaymentModalNew from "@/components/PaymentModalNew";
import { EPlacementKeys } from "@/api/resources/Paywall";
import routes from "@/routes";
function PaymentModal(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
const [height, setHeight] = useState(
subscriptionStatus === "subscribed" ? 246 : 146
);
const returnUrl = window.location.origin + routes.client.palmistryV1Payment();
return (
<>
{activeProductFromStore && (
<div
{...props}
className={styles.container}
style={{ minHeight: `${height}px`, ...props.style }}
>
<div
className={`${styles.widget} ${
subscriptionStatus === "subscribed"
? styles["widget_success"]
: ""
} ${props.className}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModalNew
setHeight={setHeight}
activeProduct={activeProductFromStore}
returnUrl={returnUrl}
placementKey={
EPlacementKeys["aura.placement.palmistry.redesign"]
}
noProductRedirect={{
pagesFrom: [routes.client.palmistryV1TrialPayment()],
url: routes.client.palmistryV1TrialChoice(),
}}
/>
)}
{subscriptionStatus === "subscribed" && (
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
)}
</div>
</div>
)}
</>
);
}
export default PaymentModal;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,177 +0,0 @@
import { useTranslations } from '@/hooks/translations';
import { addCurrency, ELocalesPlacement } from '@/locales';
import styles from "./styles.module.scss";
import { LegacyRef, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { selectors } from '@/store';
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
import cn from "classnames";
import { getFormattedPrice } from '@/utils/price.utils';
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
import Title from '@/components/Title';
import { useNavigate } from 'react-router-dom';
import routes from '@/routes';
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
import NMIPaymentForm from '@/components/Payment/nmi/PaymentForm';
import PaymentModal from '@/components/Payment/PaymentModal';
const placementKey = EPlacementKeys['aura.placement.palmistry.redesign'];
interface IPaymentFormProps {
activeProduct: IPaywallProduct;
}
function PaymentForm({
activeProduct,
}: IPaymentFormProps) {
const navigate = useNavigate();
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
const ref = useRef<HTMLDivElement>();
const currency = useSelector(selectors.selectCurrency);
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
const [isPaymentError, setIsPaymentError] = useState(false);
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
const onPaymentError = (error?: string) => {
setIsPaymentError(true);
if (error === "Product not found") {
navigate(routes.client.palmistryV1TrialChoice());
}
}
const onPaymentSuccess = () => {
setIsPaymentSuccess(true);
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS_PALMISTRY, [
// EMetrics.YANDEX,
// ]);
// if (activeProduct) {
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
// });
// }
setTimeout(() => {
navigate(routes.client.palmistryV1SkipTrial());
}, 1500);
}
const onModalClosed = () => {
setIsPaymentModalOpen(false);
navigate(routes.client.palmistryV1SaveOff());
}
if (isPaymentError) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
if (isPaymentSuccess) {
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
</div>
);
}
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={cn(
styles.paymentModalContainer
)}
>
<PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal>
<div className={styles.paymentModalPrice}>
{translate(
"/payment.total_due",
{
trialPrice: addCurrency(
getFormattedPrice(activeProduct.trialPrice || 0),
currency
),
},
ELocalesPlacement.PalmistryV1
)}
</div>
<div
className={styles.paymentCreditCard}
onClick={() => setIsPaymentModalOpen(true)}
// onClick={() => showCreditCardForm()}
>
<CreditCardIcon />
<div>Credit / Debit Card</div>
</div>
{/* <GooglePayButton />
<ApplePayButton /> */}
<div className={styles.infoContainer}>
<SecurityPayments />
<p className={styles.address}>
{translate(
"payment_modal.address",
undefined,
ELocalesPlacement.V1
)}
</p>
</div>
</div>
);
}
export default PaymentForm

View File

@ -1,94 +0,0 @@
.paymentModalContainer {
display: flex;
flex-direction: column;
position: relative;
margin: -12px -20px;
padding: 12px 20px;
gap: 6px;
transition: height 1s ease-out;
.address {
color: gray;
font-size: 10px;
margin-bottom: 16px;
text-transform: uppercase;
}
.infoContainer>* {
padding-top: 16px;
}
.paymentCreditCard {
background: #066fde;
color: #fff !important;
gap: 6px;
display: flex;
font-size: 14px;
line-height: 18px;
align-items: center;
font-weight: 400;
min-height: 48px;
border-radius: 5px;
justify-content: center;
cursor: pointer;
}
&Loading {
background: rgba(215, 213, 213, .5);
}
.paymentModalLoader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
color: #2f2e37;
position: absolute;
width: 100%;
margin-left: -20px;
}
.paymentModalPrice {
color: #066fde;
font-size: 16px;
font-weight: 700;
line-height: 25px;
text-align: center;
margin-bottom: 12px;
}
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
&>.icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
&>.text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
.modal-content {
overflow-x: hidden;
}

View File

@ -1,32 +0,0 @@
import { selectors } from '@/store';
import { HTMLAttributes } from 'react';
import { useSelector } from 'react-redux';
import styles from "./styles.module.scss";
import PaymentForm from './PaymentForm';
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
return (
<>
{
activeProductFromStore && (
<div
{...props}
className={styles.container}
>
<div
className={`${styles.widget} ${props.className}`}
>
<PaymentForm
activeProduct={activeProductFromStore}
/>
</div>
</div>
)
}
</>
);
}
export default PaymentModalV1;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -1,11 +1,11 @@
import Title from "@/components/Title"; import Title from "@/components/Title";
import styles from "./styles.module.scss"; import styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker"; import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react"; import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import Button from "../../components/Button"; import Button from "../../components/Button";
import metricService from "@/services/metric/metricService"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import routes, { palmistryV1Prefix } from "@/routes"; import routes, { palmistryV1Prefix } from "@/routes";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
@ -60,6 +60,17 @@ function Birthdate() {
navigate(routes.client.palmistryV1PalmsInformation()); navigate(routes.client.palmistryV1PalmsInformation());
}; };
useEffect(() => {
const ua = window.navigator.userAgent;
if (ua.includes("FBAN") || ua.includes("FBAV") || ua.includes("FBIOS")) {
metricService.reachGoal(EGoals.STAYED_IN_FB, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
if (ua.includes("Instagram")) {
metricService.reachGoal(EGoals.STAYED_IN_INSTAGRAM, [EMetrics.YANDEX, EMetrics.KLAVIYO])
}
}, [])
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}> <Title variant="h2" className={styles.title}>

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import routes from "@/routes"; import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.palmistry.redesign'];
function Payment() { function Payment() {
const navigate = useNavigate(); const navigate = useNavigate();
const { products, currency, getText } = usePaywall({ const { products, currency, getText } = usePaywall({
@ -27,69 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 0; const trialPrice = activeProductFromStore?.trialPrice || 0;
const fullPrice = activeProductFromStore?.price || 0; const fullPrice = activeProductFromStore?.price || 0;
// const isShowPaymentModal = useSelector(
// selectors.selectPalmistryIsShowPaymentModalV1
// );
// const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
// const [isPaymentError, setIsPaymentError] = useState(false);
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const onPaymentError = (error?: string) => {
// setIsPaymentError(true);
// if (error === "Product not found") {
// return navigate(routes.client.palmistryV1TrialChoice());
// }
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// }
// const onPaymentSuccess = () => {
// setIsPaymentSuccess(true);
// // metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
// // if (activeProductFromStore) {
// // metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// // currency: "USD",
// // value: ((activeProductFromStore.trialPrice || 100) / 100).toFixed(2),
// // });
// // }
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProductFromStore?.trialPrice || 100) / 100).toFixed(2),
// });
// setTimeout(() => {
// navigate(routes.client.palmistryV1SkipTrial());
// }, 1500);
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// navigate(routes.client.palmistryV1SaveOff());
// }
const handlePayment = () => { const handlePayment = () => {
// dispatch(actions.compatibilityV2.setIsShowPaymentModalV1(true));
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
navigate(routes.client.palmistryV1PaymentModal()); navigate(routes.client.palmistryV1PaymentModal());
}; };
// useEffect(() => {
// window.onpopstate = function (_event) {
// console.log("####PAYMENT: ", document.location.toString());
// if (document.location.toString() === `${window.location.origin}${routes.client.palmistryV1TrialPayment()}`) {
// return navigate(routes.client.palmistryV1SaveOff());
// }
// };
// return () => {
// setTimeout(() => {
// window.onpopstate = null;
// }, 0);
// };
// }, [])
useEffect(() => { useEffect(() => {
if (!products.length) return; if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id); const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -100,13 +40,6 @@ function Payment() {
return ( return (
<> <>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}> <div className={styles["app-number-one"]}>
<p className={styles.text}> <p className={styles.text}>
{translate("/payment.app_number_one", { {translate("/payment.app_number_one", {
@ -118,10 +51,6 @@ function Payment() {
<PaymentInformation /> <PaymentInformation />
<div className={styles["prices-description"]}> <div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", { {translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice( splitPrice: addCurrency(getFormattedPrice(
( (
fullPrice / ( fullPrice / (
@ -150,29 +79,9 @@ function Payment() {
})} })}
</div> </div>
<Guarantees /> <Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}> <Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")} {translate("/payment.get_personal_prediction")}
</Button> </Button>
{/* )} */}
{/* {isPaymentError && (
<Toast
variant="error"
classNameContainer={styles.toast}
>
<p>Something went wrong</p>
</Toast>
)}
{isPaymentSuccess && (
<Toast
variant="success"
classNameContainer={styles.toast}
>
<p>Payment successful</p>
</Toast>
)} */}
</> </>
); );
} }

View File

@ -28,14 +28,9 @@ function SecretDiscount() {
localesPlacement: ELocalesPlacement.EmailMarketingPalmistryV2, localesPlacement: ELocalesPlacement.EmailMarketingPalmistryV2,
}); });
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingPalmistryV2); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingPalmistryV2);
// const activeProductFromStore = useSelector(selectors.selectActiveProduct);
// const activeProduct = products.find(product => product?.trialPrice === activeProductFromStore?.trialPrice) || products[0]
const activeProduct = products[0] const activeProduct = products[0]
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!activeProduct) return; if (!activeProduct) return;
@ -44,90 +39,19 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// const onPaymentSuccess = () => {
// metricService.reachGoal(EGoals.PAYMENT_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.PURCHASE, [EMetrics.FACEBOOK], {
// currency: "USD",
// value: ((activeProduct?.trialPrice || 100) / 100).toFixed(2),
// });
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// // setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// metricService.reachGoal(EGoals.PAYMENT_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// return navigate(routes.client.paymentFail())
// }
// const onModalClosed = () => {
// setIsPaymentModalOpen(false);
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// metricService.reachGoal(EGoals.PAYMENT_METHODS_OPENED, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
// metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED, [EMetrics.KLAVIYO]);
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.palmistryV1SecretDiscountPaymentModal()) navigate(routes.client.palmistryV1SecretDiscountPaymentModal())
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return ( return (
<section className={styles.container} ref={elementRef} style={{ <section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px` paddingBottom: `${height + 42}px`
}}> }}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header <Header
className={styles.header} className={styles.header}
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -1,77 +0,0 @@
import { useSearchParams } from "react-router-dom";
import styles from "./styles.module.scss";
import { HTMLAttributes, useState } from "react";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import PaymentModalNew from "@/components/PaymentModalNew";
import { EPlacementKeys } from "@/api/resources/Paywall";
import routes from "@/routes";
function PaymentModal(props: HTMLAttributes<HTMLDivElement>) {
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [searchParams] = useSearchParams();
const subscriptionStatus =
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
const [height, setHeight] = useState(
subscriptionStatus === "subscribed" ? 246 : 146
);
const returnUrl = window.location.origin + routes.client.palmistryV1Payment();
return (
<>
{activeProductFromStore && (
<div
{...props}
className={styles.container}
style={{ minHeight: `${height}px`, ...props.style }}
>
<div
className={`${styles.widget} ${
subscriptionStatus === "subscribed"
? styles["widget_success"]
: ""
} ${props.className}`}
>
{subscriptionStatus !== "subscribed" && (
<PaymentModalNew
setHeight={setHeight}
activeProduct={activeProductFromStore}
returnUrl={returnUrl}
placementKey={
EPlacementKeys["aura.placement.palmistry.redesign"]
}
noProductRedirect={{
pagesFrom: [routes.client.palmistryV1TrialPayment()],
url: routes.client.palmistryV1TrialChoice(),
}}
/>
)}
{subscriptionStatus === "subscribed" && (
<div className={styles.success}>
<svg
className={styles.icon}
xmlns="http://www.w3.org/2000/svg"
width="512"
height="512"
viewBox="0 0 52 52"
>
<path
fill="#4ec794"
d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm14.495 17.329-16 18a1.997 1.997 0 0 1-2.745.233l-10-8a2 2 0 0 1 2.499-3.124l8.517 6.813L37.505 14.67a2.001 2.001 0 0 1 2.99 2.659z"
/>
</svg>
<div className={styles.text}>Payment success</div>
</div>
)}
</div>
</div>
)}
</>
);
}
export default PaymentModal;

View File

@ -1,66 +0,0 @@
.container {
height: 100%;
}
.widget {
position: fixed;
background: #fff;
bottom: 0;
box-shadow: 0 -2px 16px rgba(18, 22, 32, 0.1);
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: 0.5s height;
max-width: 560px;
// margin-left: -66px;
// left: 50%;
// transform: translate(-50%, 0);
left: 0;
right: 0;
margin: 0 auto;
}
.widget_success {
height: 400px;
}
.success {
width: 100%;
height: 100%;
background: #fff;
z-index: 99;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30px;
padding: 40px;
& > .icon {
width: 100px;
height: 100px;
max-width: 50%;
flex-shrink: 0;
}
& > .text {
font-size: 24px;
line-height: 32px;
text-align: center;
color: #121620;
}
}
@media screen and (max-width: 560px) {
.widget {
width: 100%;
padding: 12px 20px;
text-align: center;
text-align: -webkit-center;
transition: height 1s linear;
max-width: 560px;
margin-left: 0;
left: 0;
}
}

View File

@ -30,7 +30,6 @@ function SecretDiscount() {
const { translate } = useTranslations(ELocalesPlacement.EmailMarketingPalmistryV2); const { translate } = useTranslations(ELocalesPlacement.EmailMarketingPalmistryV2);
const activeProduct = products[0]; const activeProduct = products[0];
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100; const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
@ -41,48 +40,10 @@ function SecretDiscount() {
})) }))
}, [activeProduct]) }, [activeProduct])
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// const onPaymentSuccess = () => {
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// // setIsPaymentModalOpen(false);
// }
// const onPaymentError = () => {
// return navigate(routes.client.paymentFail())
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.palmistryV2SecretDiscountPaymentModal()); navigate(routes.client.palmistryV2SecretDiscountPaymentModal());
}; };
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return ( return (
<section className={styles.container} ref={elementRef} style={{ <section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px` paddingBottom: `${height + 42}px`
@ -92,20 +53,6 @@ function SecretDiscount() {
classNameTitle={styles["header-title"]} classNameTitle={styles["header-title"]}
isBackButtonVisible={true} isBackButtonVisible={true}
/> />
{/* {activeProduct && (
<Modal
containerClassName={styles.modal}
open={isPaymentModalOpen}
onClose={onModalClosed}
type="hidden"
>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal>
)} */}
<Blob3 className={styles.blob3} /> <Blob3 className={styles.blob3} />
<Title className={styles.title} variant="h1"> <Title className={styles.title} variant="h1">
{translate("secret-discount.title")} {translate("secret-discount.title")}

View File

@ -49,59 +49,7 @@ function TrialPayment() {
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const navigate = useNavigate(); const navigate = useNavigate();
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
// const {
// error,
// isPaymentSuccess,
// isModalClosed,
// isLoading,
// showCreditCardForm,
// } = usePayment({
// placementKey,
// activeProduct,
// paymentFormType: "lightbox"
// });
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
// useEffect(() => {
// if (isModalClosed) {
// onModalClosed();
// }
// }, [isModalClosed])
// const onPaymentSuccess = () => {
// return navigate(routes.client.paymentSuccess())
// }
// const onModalClosed = () => {
// // setIsPaymentModalOpen(false);
// if (isPaymentSuccess || isLoading || error) return;
// return handleDiscount()
// }
// const handleDiscount = () => {
// navigate(routes.client.palmistryV2SaveOff());
// };
// const onPaymentError = () => {
// return navigate(routes.client.paymentFail())
// }
const openPaymentModal = () => { const openPaymentModal = () => {
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.palmistryV2PaymentModal()); navigate(routes.client.palmistryV2PaymentModal());
}; };
@ -129,13 +77,6 @@ function TrialPayment() {
return ( return (
<> <>
{/* <Modal containerClassName={styles.modal} open={isPaymentModalOpen} onClose={onModalClosed}>
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/>
</Modal> */}
<div className={styles.background} /> <div className={styles.background} />
<div className={styles.header}> <div className={styles.header}>
<Title className={styles.title}> <Title className={styles.title}>

View File

@ -1,55 +0,0 @@
import { ReactNode, useEffect, useState } from "react";
import styles from "./styles.module.scss";
interface IPaymentModalProps {
children: ReactNode;
open?: boolean;
onClose: () => void;
}
function PaymentModal({ children, open = false, onClose }: IPaymentModalProps) {
const [isOpen, setIsOpen] = useState(open);
const [isClosing, setIsClosing] = useState(false);
useEffect(() => {
if (open || !isOpen) {
return setIsOpen(open);
}
setIsClosing(true);
const timeout = setTimeout(() => {
setIsClosing(false);
setIsOpen(false);
}, 750);
return () => clearTimeout(timeout);
}, [open]);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "auto";
}
}, [isOpen]);
if (!isOpen) return <></>;
return (
<div className={`${styles.container} ${isClosing ? styles.closing : ""}`} onClick={onClose}>
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
<div className={styles.backArrowContainer}>
<img
className={styles.backArrow}
src="/payment-form/back-arrow.svg"
alt="back"
onClick={onClose}
/>
</div>
{children}
</div>
</div>
)
}
export default PaymentModal

View File

@ -1,81 +0,0 @@
.container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100dvh;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
cursor: pointer;
&>.modal {
position: absolute;
bottom: 0dvh;
left: 50%;
transform: translate(-50%, 0);
width: 100vw;
max-width: 500px;
height: fit-content;
max-height: calc(100dvh - 24px);
background-color: #F1F5FF;
border-radius: 17px 17px 0 0;
padding: 20px 24px 42px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
z-index: 1000;
animation: slideUp .8s cubic-bezier(0.6, 0.24, 0.1, 0.91);
pointer-events: all;
overflow-y: auto;
overflow-x: hidden;
cursor: default;
&>.backArrowContainer {
position: sticky;
top: 20px;
left: 0px;
margin-left: -100%;
z-index: 1000;
width: fit-content;
height: fit-content;
background: #fff;
padding: 6px 12px;
border-radius: 50%;
cursor: pointer;
&>.backArrow {
width: 11px;
height: 20px;
margin-left: -2px;
cursor: pointer;
}
}
}
&.closing>.modal {
animation: slideDown .8s cubic-bezier(0.6, 0.24, 0.1, 0.91);
}
}
@keyframes slideUp {
0% {
display: none;
transform: translate(-50%, 100%);
}
100% {
display: block;
transform: translate(-50%, 0);
}
}
@keyframes slideDown {
0% {
display: block;
transform: translate(-50%, 0);
}
100% {
transform: translate(-50%, 100%);
display: none;
}
}

View File

@ -1,229 +0,0 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import PaymentMethodsChoice from "../pages/TrialPayment/components/PaymentMethodsChoice";
import { useCallback, useEffect, useMemo, useState } from "react";
import { EPaymentMethod, paymentMethods } from "@/data/paymentMethods";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "@/components/PaymentPage/methods/CheckoutForm";
import {
AvailablePaymentMethods,
Stripe,
StripeElementLocale,
loadStripe,
} from "@stripe/stripe-js";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
import Loader from "@/components/Loader";
import SecurityPayments from "../pages/TrialPayment/components/SecurityPayments";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { useMakePayment } from "@/hooks/payment/useMakePayment";
import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations";
import { addCurrency, ELocalesPlacement, language } from "@/locales";
interface IPaymentModalProps {
activeProduct?: IPaywallProduct;
noTrial?: boolean;
returnUrl?: string;
placementKey: EPlacementKeys;
}
function PaymentModal({
activeProduct,
noTrial,
returnUrl,
placementKey,
}: IPaymentModalProps) {
const { translate } = useTranslations(ELocalesPlacement.V1);
const navigate = useNavigate();
const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null);
const currency = useSelector(selectors.selectCurrency);
const { products, placementId, paywallId } = usePaywall({
placementKey,
});
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const getPrice = useCallback((product: IPaywallProduct | null) => {
if (!product) {
return 0;
}
return (product.trialPrice === 100 ? 99 : product.trialPrice || 0) / 100;
}, []);
const _activeProduct = activeProduct ? activeProduct : activeProductFromStore;
const {
paymentIntentId,
clientSecret,
returnUrl: checkoutUrl,
paymentType,
publicKey,
isLoading: isLoadingPayment,
error,
} = useMakePayment({
productId: _activeProduct?._id || "",
placementId,
paywallId,
returnPaidUrl: returnUrl,
});
const [availableMethods, setAvailableMethods] = useState<
AvailablePaymentMethods | undefined
>();
const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] =
useState(true);
const isLoading = useMemo(() => {
return isLoadingPayment || isLoadingExpressCheckout;
}, [isLoadingPayment, isLoadingExpressCheckout]);
if (checkoutUrl?.length) {
window.location.href = checkoutUrl;
}
const paymentMethodsButtons = useMemo(() => {
return paymentMethods(availableMethods || null);
}, [availableMethods]);
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(
paymentMethodsButtons[0].id
);
const onSelectPaymentMethod = (method: EPaymentMethod) => {
setSelectedPaymentMethod(method);
};
useEffect(() => {
(async () => {
if (!products?.length || !publicKey) return;
setStripePromise(loadStripe(publicKey));
const isActiveProduct = products.find(
(product) => product._id === _activeProduct?._id
);
if (!_activeProduct || !isActiveProduct) {
navigate(routes.client.trialChoice());
}
})();
}, [_activeProduct, navigate, products, publicKey]);
const onAvailableExpressCheckout = (
isAvailable: boolean,
availableMethods: AvailablePaymentMethods | undefined
) => {
if (isAvailable && availableMethods) {
setAvailableMethods(availableMethods);
return setSelectedPaymentMethod(EPaymentMethod.PAYMENT_BUTTONS);
}
return setAvailableMethods(undefined);
};
if (error?.length) {
return (
<div className={styles["payment-modal"]}>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
return (
<>
{isLoading && (
<div className={styles["payment-modal"]}>
<div className={styles["payment-loader"]}>
<Loader />
</div>
</div>
)}
<div
className={`${styles["payment-modal"]} ${isLoading ? styles.hide : ""}`}
>
<Title variant="h3" className={styles.title}>
{translate("payment_modal.title")}
</Title>
<PaymentMethodsChoice
paymentMethods={paymentMethodsButtons}
selectedPaymentMethod={selectedPaymentMethod}
onSelectPaymentMethod={onSelectPaymentMethod}
/>
{_activeProduct && (
<div>
{!noTrial && (
<>
<p className={styles["sub-plan-description"]}>
{translate("payment_modal.description", {
priceForDays: (
<b>
{translate("payment_modal.price_for_days", {
trialPrice: addCurrency(
getPrice(_activeProduct),
currency
),
trialDuration: _activeProduct?.trialDuration,
})}
</b>
),
emailReminder: (
<b>{translate("payment_modal.email_reminder")}</b>
),
})}
</p>
</>
)}
</div>
)}
<div className={styles["payment-method-container"]}>
{stripePromise && clientSecret && (
<>
<Elements
stripe={stripePromise}
options={{
clientSecret,
locale: language as StripeElementLocale | undefined,
}}
>
<ExpressCheckoutStripe
clientSecret={clientSecret}
returnUrl={returnUrl}
isHide={
selectedPaymentMethod !== EPaymentMethod.PAYMENT_BUTTONS
}
onAvailable={(_isAvailable, _availableMethods) =>
onAvailableExpressCheckout(_isAvailable, _availableMethods)
}
onChangeLoading={(isLoading) =>
setIsLoadingExpressCheckout(isLoading)
}
/>
</Elements>
<Elements
stripe={stripePromise}
options={{
clientSecret,
locale: language as StripeElementLocale | undefined,
}}
>
<CheckoutForm
confirmType={paymentType}
subscriptionReceiptId={paymentIntentId}
returnUrl={returnUrl}
isHide={selectedPaymentMethod !== EPaymentMethod.CREDIT_CARD}
/>
</Elements>
</>
)}
</div>
<SecurityPayments />
<p className={styles.address}>{translate("payment_modal.address")}</p>
</div>
</>
);
}
export default PaymentModal;

View File

@ -1,55 +0,0 @@
.payment-modal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 250px;
gap: 25px;
color: #2f2e37;
}
.payment-modal.hide {
min-height: 0;
height: 0;
opacity: 0;
}
.title {
font-weight: 700;
font-size: 20px;
line-height: 20px;
text-align: center;
margin: 0;
}
.sub-plan-description {
font-size: 12px;
text-align: center;
line-height: 150%;
white-space: pre-wrap;
}
.payment-method-container {
width: 100%;
display: flex;
flex-direction: column;
gap: 24px;
}
.address {
margin-bottom: 24px;
text-transform: uppercase;
}
.payment-method {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
}
.address {
color: gray;
font-size: 10px;
}

View File

@ -1,9 +0,0 @@
export default function CreditCardIcon() {
return (
<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" width="22" height="16" rx="2" fill="white"></rect>
<rect x="0.5" y="2.66406" width="22" height="2.66667" fill="#9FB8FF"></rect>
<rect x="3" y="7.35938" width="17" height="2" rx="1" fill="#CEDBFF"></rect>
</svg>
)
}

View File

@ -1,56 +0,0 @@
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm, {
TConfirmType,
} from "@/components/PaymentPage/methods/CheckoutForm";
import Modal from "@/components/Modal";
import { Stripe, StripeElementLocale } from "@stripe/stripe-js";
import { Dispatch, SetStateAction } from "react";
import styles from "./styles.module.scss";
import "./style.scss";
import { language } from "@/locales";
interface IPaymentCardModalProps {
clientSecret?: string;
stripePromise: Promise<Stripe | null> | null;
paymentType?: TConfirmType;
paymentIntentId?: string;
returnUrl?: string;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
removeNoScroll?: boolean;
}
export default function PaymentCardModal({
clientSecret,
stripePromise,
paymentType,
paymentIntentId,
returnUrl,
isOpen,
setIsOpen,
removeNoScroll,
}: IPaymentCardModalProps) {
return (
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
removeNoScroll={removeNoScroll}
containerClassName={styles["modal-container"]}
>
<Elements
stripe={stripePromise}
options={{
clientSecret,
locale: language as StripeElementLocale | undefined,
}}
>
<CheckoutForm
confirmType={paymentType}
subscriptionReceiptId={paymentIntentId}
returnUrl={returnUrl}
/>
</Elements>
</Modal>
);
}

View File

@ -1,7 +0,0 @@
:global(.paymentCardModalContainer) {
background: none;
.p-PaymentMethodSelector {
display: none !important;
}
}

View File

@ -1,7 +0,0 @@
.modal-container {
max-height: calc(100dvh - 32px);
height: 100%;
top: auto;
bottom: 0;
transform: translate(-50%, 0);
}

View File

@ -1,221 +0,0 @@
import Loader from "@/components/Loader";
import styles from "./styles.module.scss";
import cn from "classnames";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { useLocation, useNavigate } from "react-router-dom";
import {
Dispatch,
LegacyRef,
SetStateAction,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { loadStripe, Stripe, StripeElementLocale } from "@stripe/stripe-js";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { useMakePayment } from "@/hooks/payment/useMakePayment";
import { getFormattedPrice } from "@/utils/price.utils";
import routes from "@/routes";
import Title from "@/components/Title";
import { Elements } from "@stripe/react-stripe-js";
import ExpressCheckoutStripe from "@/components/PaymentPage/methods/ExpressCheckoutStripe";
import SecurityPayments from "@/components/pages/TrialPayment/components/SecurityPayments";
import PaymentCardModal from "@/components/PaymentModalNew/PaymentCardModal";
import CreditCardIcon from "@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon";
import { useTranslations } from "@/hooks/translations";
import { addCurrency, ELocalesPlacement, language } from "@/locales";
import { selectors } from "@/store";
import { useSelector } from "react-redux";
interface IPaymentModalNewProps {
returnUrl: string;
placementKey: EPlacementKeys;
activeProduct: IPaywallProduct;
setHeight?: Dispatch<SetStateAction<number>>;
noProductRedirect?: {
pagesFrom: string[];
url: string;
};
}
export default function PaymentModalNew({
returnUrl,
activeProduct,
placementKey,
setHeight,
noProductRedirect,
}: IPaymentModalNewProps) {
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
const navigate = useNavigate();
const location = useLocation();
const ref = useRef<HTMLDivElement>();
const currency = useSelector(selectors.selectCurrency);
const [stripePromise, setStripePromise] =
useState<Promise<Stripe | null> | null>(null);
const { products, placementId, paywallId } = usePaywall({
placementKey,
});
const [isOpenCardModal, setIsOpenCardModal] = useState(false);
const {
paymentIntentId,
clientSecret,
returnUrl: checkoutUrl,
paymentType,
publicKey,
isLoading: isLoadingPayment,
error,
} = useMakePayment({
productId: activeProduct?._id || "",
placementId,
paywallId,
returnPaidUrl: returnUrl,
});
const [isLoadingExpressCheckout, setIsLoadingExpressCheckout] =
useState(true);
const isLoading = useMemo(() => {
return isLoadingPayment || isLoadingExpressCheckout;
}, [isLoadingPayment, isLoadingExpressCheckout]);
if (checkoutUrl?.length) {
window.location.href = checkoutUrl;
}
useEffect(() => {
(async () => {
if (!products?.length || !publicKey) return;
setStripePromise(loadStripe(publicKey));
const isActiveProduct = products.find(
(product) => product._id === activeProduct?._id
);
if (!activeProduct || !isActiveProduct) {
if (noProductRedirect) {
if (noProductRedirect.pagesFrom.includes(location.pathname)) {
return navigate(noProductRedirect.url);
}
return;
}
navigate(routes.client.trialChoice());
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeProduct, navigate, products, publicKey]);
const resizeHandler = () => {
setHeight?.(ref?.current?.clientHeight || 32);
};
if (error?.length) {
setTimeout(resizeHandler, 300);
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={styles["payment-modal"]}
>
<Title variant="h3" className={styles.title}>
Something went wrong
</Title>
</div>
);
}
return (
<div
ref={ref as LegacyRef<HTMLDivElement>}
className={cn(
styles.paymentModalContainer,
isLoading && styles.paymentModalContainerLoading
)}
>
{isLoading && (
<div className={cn(styles.paymentModalLoader)}>
<Loader />
</div>
)}
<div className={styles.paymentModalPrice}>
{translate(
"/payment.total_due",
{
trialPrice: addCurrency(
getFormattedPrice(activeProduct.trialPrice || 0),
currency
),
},
ELocalesPlacement.PalmistryV1
)}
</div>
{!isLoadingPayment && (
<>
{!isLoading && (
<div
className={styles.paymentCreditCard}
onClick={() => setIsOpenCardModal(true)}
>
<CreditCardIcon />
<div>Credit / Debit Card</div>
</div>
)}
<Elements
stripe={stripePromise}
options={{
clientSecret,
locale: language as StripeElementLocale | undefined,
}}
>
<ExpressCheckoutStripe
clientSecret={clientSecret!}
returnUrl={returnUrl}
paymentMethodOrderList={["google_pay", "apple_pay", "link"]}
onChangeLoading={(isLoading) => {
setIsLoadingExpressCheckout(isLoading);
setTimeout(resizeHandler, 300);
}}
/>
</Elements>
<div
style={
!isOpenCardModal
? { visibility: "hidden", position: "absolute" }
: {}
}
>
<PaymentCardModal
isOpen={true}
setIsOpen={setIsOpenCardModal}
clientSecret={clientSecret}
stripePromise={stripePromise}
paymentType={paymentType}
paymentIntentId={paymentIntentId}
returnUrl={returnUrl}
removeNoScroll={isOpenCardModal}
/>
</div>
{!isLoading && (
<>
<div className={styles.infoContainer}>
<SecurityPayments />
<p className={styles.address}>
{translate(
"payment_modal.address",
undefined,
ELocalesPlacement.V1
)}
</p>
</div>
</>
)}
</>
)}
</div>
);
}

View File

@ -1,60 +0,0 @@
.paymentModalContainer {
display: flex;
flex-direction: column;
position: relative;
margin: -12px -20px;
padding: 12px 20px;
gap: 6px;
transition: height 1s ease-out;
.address {
color: gray;
font-size: 10px;
margin-bottom: 16px;
text-transform: uppercase;
}
.infoContainer > * {
padding-top: 16px;
}
.paymentCreditCard {
background: #066fde;
color: #fff !important;
gap: 6px;
display: flex;
font-size: 14px;
line-height: 18px;
align-items: center;
font-weight: 400;
min-height: 48px;
border-radius: 5px;
justify-content: center;
}
&Loading {
background: rgba(215, 213, 213, .5);
}
.paymentModalLoader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
color: #2f2e37;
position: absolute;
width: 100%;
margin-left: -20px;
}
.paymentModalPrice {
color: #066fde;
font-size: 16px;
font-weight: 700;
line-height: 25px;
text-align: center;
margin-bottom: 12px;
}
}

View File

@ -1,108 +1,111 @@
import MainButton from "@/components/MainButton"; // import MainButton from "@/components/MainButton";
import Title from "@/components/Title"; // import Title from "@/components/Title";
import routes from "@/routes"; // import routes from "@/routes";
import { actions } from "@/store"; // import { actions } from "@/store";
import { // import {
PaymentElement, // PaymentElement,
useElements, // useElements,
useStripe, // useStripe,
} from "@stripe/react-stripe-js"; // } from "@stripe/react-stripe-js";
import { useState } from "react"; // import { useState } from "react";
import { useDispatch } from "react-redux"; // import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom"; // import { useNavigate } from "react-router-dom";
import styles from "./styles.module.css"; // import styles from "./styles.module.css";
export type TConfirmType = "payment" | "setup"; // export type TConfirmType = "payment" | "setup";
interface ICheckoutFormProps { // interface ICheckoutFormProps {
children?: JSX.Element | null; // children?: JSX.Element | null;
subscriptionReceiptId?: string; // subscriptionReceiptId?: string;
returnUrl?: string; // returnUrl?: string;
confirmType?: TConfirmType; // confirmType?: TConfirmType;
isHide?: boolean; // isHide?: boolean;
} // }
export default function CheckoutForm({ export default function CheckoutForm(
children, // {
subscriptionReceiptId, // children,
returnUrl, // subscriptionReceiptId,
confirmType = "payment", // returnUrl,
isHide = false, // confirmType = "payment",
}: ICheckoutFormProps) { // isHide = false,
const stripe = useStripe(); // }: ICheckoutFormProps
const elements = useElements(); ) {
const dispatch = useDispatch(); // const stripe = useStripe();
const navigate = useNavigate(); // const elements = useElements();
// const dispatch = useDispatch();
// const navigate = useNavigate();
const [message, setMessage] = useState(""); // const [message, setMessage] = useState("");
const [isProcessing, setIsProcessing] = useState(false); // const [isProcessing, setIsProcessing] = useState(false);
const [formReady, setFormReady] = useState(false); // const [formReady, setFormReady] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { // const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); // e.preventDefault();
if (!stripe || !elements) { // if (!stripe || !elements) {
return; // return;
} // }
setIsProcessing(true); // setIsProcessing(true);
try { // try {
const { error } = await stripe[ // const { error } = await stripe[
confirmType === "payment" ? "confirmPayment" : "confirmSetup" // confirmType === "payment" ? "confirmPayment" : "confirmSetup"
]({ // ]({
elements, // elements,
confirmParams: { // confirmParams: {
return_url: returnUrl // return_url: returnUrl
? returnUrl // ? returnUrl
: `https://${window.location.host}/payment/result/${subscriptionReceiptId}/`, // : `https://${window.location.host}/payment/result/${subscriptionReceiptId}/`,
}, // },
}); // });
if (error) { // if (error) {
setMessage(error?.message || "Oops! Something went wrong."); // setMessage(error?.message || "Oops! Something went wrong.");
} else { // } else {
dispatch(actions.status.update("subscribed")); // dispatch(actions.status.update("subscribed"));
navigate(routes.client.paymentSuccess()); // navigate(routes.client.paymentSuccess());
} // }
} catch (error) { // } catch (error) {
console.log("error -> ", error); // console.log("error -> ", error);
setMessage("Oops! Something went wrong."); // setMessage("Oops! Something went wrong.");
} finally { // } finally {
setIsProcessing(false); // setIsProcessing(false);
} // }
}; // };
return ( return (
<form <></>
className={`payment-form-stripe ${isHide ? styles.hide : ""}`} // <form
id="payment-form" // className={`payment-form-stripe ${isHide ? styles.hide : ""}`}
onSubmit={handleSubmit} // id="payment-form"
> // onSubmit={handleSubmit}
{children ? children : null} // >
<PaymentElement // {children ? children : null}
options={{ // <PaymentElement
terms: { card: "never" }, // options={{
wallets: { // terms: { card: "never" },
googlePay: "never", // wallets: {
applePay: "never", // googlePay: "never",
}, // applePay: "never",
}} // },
onReady={() => setFormReady(true)} // }}
/> // onReady={() => setFormReady(true)}
<MainButton // />
color="blue" // <MainButton
disabled={isProcessing || !formReady} // color="blue"
id="submit" // disabled={isProcessing || !formReady}
className={styles.button} // id="submit"
> // className={styles.button}
<img src="/lock.svg" alt="Secure" /> // >
<span id="button-text">{isProcessing ? "Processing..." : "Start"}</span> // <img src="/lock.svg" alt="Secure" />
</MainButton> // <span id="button-text">{isProcessing ? "Processing..." : "Start"}</span>
{!!message.length && ( // </MainButton>
<Title variant="h5" style={{ color: "red" }}> // {!!message.length && (
{message} // <Title variant="h5" style={{ color: "red" }}>
</Title> // {message}
)} // </Title>
</form> // )}
// </form>
); );
} }

View File

@ -1,120 +1,123 @@
import { useMemo, useState } from "react"; // import { useMemo, useState } from "react";
import styles from "./styles.module.css"; // import styles from "./styles.module.css";
import { // import {
useStripe, // useStripe,
useElements, // useElements,
ExpressCheckoutElement, // ExpressCheckoutElement,
} from "@stripe/react-stripe-js"; // } from "@stripe/react-stripe-js";
import { // import {
AvailablePaymentMethods, // AvailablePaymentMethods,
StripeExpressCheckoutElementReadyEvent, // StripeExpressCheckoutElementReadyEvent,
} from "@stripe/stripe-js"; // } from "@stripe/stripe-js";
import { checkExpressCheckoutStripeFormAvailable } from "@/data/paymentMethods"; // import { checkExpressCheckoutStripeFormAvailable } from "@/data/paymentMethods";
interface IExpressCheckoutStripeProps { // interface IExpressCheckoutStripeProps {
clientSecret: string; // clientSecret: string;
returnUrl?: string; // returnUrl?: string;
isHide?: boolean; // isHide?: boolean;
onAvailable?: ( // onAvailable?: (
isAvailable: boolean, // isAvailable: boolean,
availableMethods: AvailablePaymentMethods | undefined // availableMethods: AvailablePaymentMethods | undefined
) => void; // ) => void;
onChangeLoading?: (isLoading: boolean) => void; // onChangeLoading?: (isLoading: boolean) => void;
paymentMethodOrderList?: string[]; // paymentMethodOrderList?: string[];
} // }
function ExpressCheckoutStripe({ function ExpressCheckoutStripe(
clientSecret, // {
returnUrl = `https://${window.location.host}/payment/result/`, // clientSecret,
isHide = false, // returnUrl = `https://${window.location.host}/payment/result/`,
onAvailable, // isHide = false,
onChangeLoading, // onAvailable,
paymentMethodOrderList // onChangeLoading,
}: IExpressCheckoutStripeProps) { // paymentMethodOrderList
const stripe = useStripe(); // }: IExpressCheckoutStripeProps
const elements = useElements(); ) {
const [errorMessage, setErrorMessage] = useState<string>(); // const stripe = useStripe();
const [isAvailable, setIsAvailable] = useState(false); // const elements = useElements();
const isHideForm = useMemo( // const [errorMessage, setErrorMessage] = useState<string>();
() => isHide || !isAvailable, // const [isAvailable, setIsAvailable] = useState(false);
[isAvailable, isHide] // const isHideForm = useMemo(
); // () => isHide || !isAvailable,
// [isAvailable, isHide]
// );
const onConfirm = async () => // const onConfirm = async () =>
// event: StripeExpressCheckoutElementConfirmEvent // // event: StripeExpressCheckoutElementConfirmEvent
{ // {
if (!stripe || !elements) { // if (!stripe || !elements) {
// Stripe.js hasn't loaded yet. // // Stripe.js hasn't loaded yet.
// Make sure to disable form submission until Stripe.js has loaded. // // Make sure to disable form submission until Stripe.js has loaded.
return; // return;
} // }
const { error: submitError } = await elements.submit(); // const { error: submitError } = await elements.submit();
if (submitError) { // if (submitError) {
setErrorMessage(submitError.message); // setErrorMessage(submitError.message);
return; // return;
} // }
// // Create the PaymentIntent and obtain clientSecret // // // Create the PaymentIntent and obtain clientSecret
// const res = await fetch("/create-intent", { // // const res = await fetch("/create-intent", {
// method: "POST", // // method: "POST",
// }); // // });
// const { client_secret: clientSecret } = await res.json(); // // const { client_secret: clientSecret } = await res.json();
// Confirm the PaymentIntent using the details collected by the Express Checkout Element // // Confirm the PaymentIntent using the details collected by the Express Checkout Element
const { error } = await stripe.confirmPayment({ // const { error } = await stripe.confirmPayment({
// `elements` instance used to create the Express Checkout Element // // `elements` instance used to create the Express Checkout Element
elements, // elements,
// `clientSecret` from the created PaymentIntent // // `clientSecret` from the created PaymentIntent
clientSecret, // clientSecret,
confirmParams: { // confirmParams: {
return_url: returnUrl, // return_url: returnUrl,
}, // },
}); // });
if (error) { // if (error) {
// This point is only reached if there's an immediate error when // // This point is only reached if there's an immediate error when
// confirming the payment. Show the error to your customer (for example, payment details incomplete) // // confirming the payment. Show the error to your customer (for example, payment details incomplete)
setErrorMessage(error.message); // setErrorMessage(error.message);
} else { // } else {
// The payment UI automatically closes with a success animation. // // The payment UI automatically closes with a success animation.
// Your customer is redirected to your `return_url`. // // Your customer is redirected to your `return_url`.
} // }
}; // };
const onReady = (event: StripeExpressCheckoutElementReadyEvent) => { // const onReady = (event: StripeExpressCheckoutElementReadyEvent) => {
const _isAvailable = checkExpressCheckoutStripeFormAvailable( // const _isAvailable = checkExpressCheckoutStripeFormAvailable(
event.availablePaymentMethods // event.availablePaymentMethods
); // );
setIsAvailable(_isAvailable); // setIsAvailable(_isAvailable);
onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods); // onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods);
onChangeLoading && onChangeLoading(false); // onChangeLoading && onChangeLoading(false);
}; // };
return ( return (
<div className={`${styles.container} ${isHideForm ? styles.hide : ""}`}> <></>
<ExpressCheckoutElement // <div className={`${styles.container} ${isHideForm ? styles.hide : ""}`}>
onReady={onReady} // <ExpressCheckoutElement
onLoadError={(e) => { // onReady={onReady}
console.log("Error: ", e); // onLoadError={(e) => {
onChangeLoading && onChangeLoading(false); // console.log("Error: ", e);
}} // onChangeLoading && onChangeLoading(false);
onConfirm={onConfirm} // }}
options={{ // onConfirm={onConfirm}
layout: { // options={{
maxColumns: 1, // layout: {
overflow: "never", // maxColumns: 1,
}, // overflow: "never",
paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"], // },
wallets: { // paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"],
googlePay: "always", // wallets: {
applePay: "always", // googlePay: "always",
}, // applePay: "always",
}} // },
/> // }}
{errorMessage && <p className={styles.error}>{errorMessage}</p>} // />
</div> // {errorMessage && <p className={styles.error}>{errorMessage}</p>}
// </div>
); );
} }

View File

@ -1,33 +0,0 @@
import { IPaywallProduct } from "@/api/resources/Paywall";
import { useCanUseStripeButton } from "@/hooks/payment/useCanUseStripeButton";
import { actions } from "@/store";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
interface ICheckAvailableStripeButtonProps {
activeProduct: IPaywallProduct | null;
clientSecret: string;
}
function CheckAvailableStripeButton({
activeProduct,
clientSecret,
}: ICheckAvailableStripeButtonProps) {
const dispatch = useDispatch();
const { paymentRequest, availableMethods } = useCanUseStripeButton({
activeProduct,
client_secret: clientSecret,
});
useEffect(() => {
if (paymentRequest && availableMethods) {
dispatch(
actions.payment.updateStripeButton({ paymentRequest, availableMethods })
);
}
}, [availableMethods, dispatch, paymentRequest]);
return <></>;
}
export default CheckAvailableStripeButton;

View File

@ -1,27 +0,0 @@
import { PaymentRequestButtonElement } from "@stripe/react-stripe-js";
import { CanMakePaymentResult, PaymentRequest } from "@stripe/stripe-js";
import styles from "./styles.module.css";
export type TCanMakePaymentResult = CanMakePaymentResult | null;
interface ApplePayButtonProps {
paymentRequest: PaymentRequest;
}
function StripeButton({ paymentRequest }: ApplePayButtonProps) {
return (
<>
{paymentRequest && (
<PaymentRequestButtonElement
className={styles["stripe-element"]}
options={{
paymentRequest,
style: { paymentRequestButton: { height: "60px" } },
}}
/>
)}
</>
);
}
export default StripeButton;

View File

@ -1,4 +0,0 @@
.stripe-element {
width: 100%;
max-width: 300px;
}

View File

@ -11,9 +11,6 @@ import { selectors } from "@/store";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { ResponsePost } from "@/api/resources/SinglePayment"; import { ResponsePost } from "@/api/resources/SinglePayment";
import { createSinglePayment } from "@/services/singlePayment"; import { createSinglePayment } from "@/services/singlePayment";
import Modal from "@/components/Modal";
import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm";
import { getPriceCentsToDollars } from "@/services/price";
import Loader, { LoaderColor } from "@/components/Loader"; import Loader, { LoaderColor } from "@/components/Loader";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
@ -26,11 +23,10 @@ function AddConsultationPage() {
const api = useApi(); const api = useApi();
const tokenFromStore = useSelector(selectors.selectToken); const tokenFromStore = useSelector(selectors.selectToken);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [paymentIntent, setPaymentIntent] = useState<ResponsePost | null>(null); const [_paymentIntent, setPaymentIntent] = useState<ResponsePost | null>(null);
const [isError, setIsError] = useState(false); const [isError, setIsError] = useState(false);
const returnUrl = `${window.location.protocol}//${ const returnUrl = `${window.location.protocol}//${window.location.host
window.location.host }${routes.client.getInformationPartner()}`;
}${routes.client.getInformationPartner()}`;
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
return await api.getSinglePaymentProducts({ token: tokenFromStore }); return await api.getSinglePaymentProducts({ token: tokenFromStore });
@ -80,26 +76,6 @@ function AddConsultationPage() {
}; };
return ( return (
<div className={styles.container}> <div className={styles.container}>
{!isLoading &&
paymentIntent &&
"paymentIntent" in paymentIntent &&
!!tokenFromStore.length && (
<>
<Modal
open={!!paymentIntent}
onClose={() => setPaymentIntent(null)}
>
<Title variant="h1" className={styles["modal-title"]}>
{getPriceCentsToDollars(currentProduct?.price || 0)}$
</Title>
<PaymentForm
stripePublicKey={paymentIntent.paymentIntent.data.public_key}
clientSecret={paymentIntent.paymentIntent.data.client_secret}
returnUrl={returnUrl}
/>
</Modal>
</>
)}
{!currentProduct?.price && ( {!currentProduct?.price && (
<Loader className={styles.loader} color={LoaderColor.Black} /> <Loader className={styles.loader} color={LoaderColor.Black} />
)} )}

Some files were not shown because too many files have changed in this diff Show More