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",
"@reduxjs/toolkit": "^1.9.5",
"@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",
"apng-js": "^1.1.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",
"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": {
"version": "8.0.0",
"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",
"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": {
"version": "8.0.0",
"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",
"@reduxjs/toolkit": "^1.9.5",
"@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",
"apng-js": "^1.1.1",
"core-js": "^3.37.1",

View File

@ -26,6 +26,12 @@
"males": "Males",
"females": "Females",
"/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": {
"title": "Your Personalized Offer Reserved",
"get-prediction-in-app": "Get prediction in<br>the App"
@ -37,12 +43,12 @@
"life": "Life line ✅"
},
"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",
"copy": "COPY",
"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_4": "1. Copy Your Access Code",
"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",
"why_love": "Why does everyone <color> ?",
"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": {
"title": "Gain Clarity and Confidence in Life",
@ -82,69 +115,201 @@
}
},
"v2": {
"title": "Compatibility Test",
"subtitle": "It all starts with you! Choose your gender.",
"title": "Тест на Совместимость",
"subtitle": "Все начинается с тебя!<br>Выбери свой пол.",
"points": {
"point1": "The test takes less than a minute.",
"point2": "You'll receive a compatibility analysis through palmistry based on the lines on your hand.",
"point3": "Resolve relationship issues in a month.",
"point4": "Save hundreds of dollars on unreliable forecasts.",
"point5": "Get a personalized analysis."
"point1": "Тест займет не более 1 мин.",
"point2": "Ты получишь полный разбор совместимости.",
"point3": "Решишь проблемы в отношениях за месяц.",
"point4": "Сэкономите сотни долларов на ненадёжных прогнозах.",
"point5": "Получите персональный анализ."
}
}
},
"/birthdate": {
"title": "When Were You Born?",
"text": "Your birth date can reveal strengths and values that may help you move forward"
"title": "Когда вы родились?",
"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": {
"aries": {
"title": "♈ Aries",
"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."
"title": "♈ Овен",
"description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> разделяют их страсть к действию и независимость. Решительность и амбиции определяют, насколько легко вы находите общий ритм с партнером.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Что укрепляет отношения, а что создаёт сложности?",
"description_percent": "81%"
},
"taurus": {
"title": "♉ Taurus",
"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."
"title": "♉ Телец",
"description": "Согласно нашим исследованиям, в <percent> гармоничных союзов партнеры <zodiac> разделяют их стремление к комфорту и верности. Спокойствие, преданность и терпение вот что делает тебя идеальным спутником для долгосрочных отношений.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "86%"
},
"gemini": {
"title": "♊ Gemini",
"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."
"title": "♊ Близнецы",
"description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> готовы к переменам, легкости и спонтанности. Остроумие, коммуникабельность и жажда новизны делают тебя увлекательным спутником, но требуют от партнера гибкости и умения идти в ногу с твоими идеями.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "78%"
},
"cancer": {
"title": "♋ Cancer",
"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."
"title": "♋ Рак",
"description": "Согласно нашим данным, в <percent> крепких отношений партнеры <zodiac> обладают развитой эмпатией и интуитивным пониманием друг друга. Ты ценишь заботу, стабильность и душевную близость, а значит, гармония возможна только с тем, кто умеет разделять твои чувства.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "82%"
},
"leo": {
"title": "♌ Leo",
"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."
"title": "♌ Лев",
"description": "Согласно нашим данным, в <percent> счастливых союзов партнеры <zodiac> искренне восхищаются ими и поддерживают их амбиции. Ты стремишься к ярким, вдохновляющим отношениям, где тебя ценят, уважают и не пытаются затмить.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "84%"
},
"virgo": {
"title": "♍ Virgo",
"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."
"title": "♍ Дева",
"description": "Согласно нашим наблюдениям, в <percent> успешных союзов партнеры <zodiac> ценят предсказуемость, заботу и рациональный подход к отношениям. Ты стремишься к порядку и гармонии, а значит, идеальный партнер тот, кто разделяет твои ценности и уважает твой перфекционизм.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "79%"
},
"libra": {
"title": "♎ Libra",
"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."
"title": "♎ Весы",
"description": "Согласно нашим данным, в <percent> удачных союзов партнеры <zodiac> разделяют их стремление к гармонии и открытому диалогу. Ты ценишь красивые, легкие и справедливые отношения, где оба партнера готовы работать над взаимопониманием.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "80%"
},
"scorpio": {
"title": "♏ Scorpio",
"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."
"title": "♏ Скорпион",
"description": "Согласно нашим исследованиям, в <percent> успешных союзов партнеры <zodiac> обладают такой же эмоциональной интенсивностью, не боясь глубокой связи и полной вовлеченности. Ты не приемлешь поверхностности в отношениях тебе нужен партнер, который готов разделить с тобой и взлеты, и падения.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "83%"
},
"sagittarius": {
"title": "♐ Sagittarius",
"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."
"title": "♐ Стрелец",
"description": "Согласно нашим данным, в <percent> долгосрочных союзов партнеры <zodiac> разделяют их жажду новых впечатлений и открытий. Ты ценишь легкость, оптимизм и независимость, а значит, тебе нужен человек, который не станет ограничивать тебя, а будет готов идти рядом в новом путешествии.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "77%"
},
"capricorn": {
"title": "♑ Capricorn",
"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."
"title": "♑ Козерог",
"description": "Согласно нашим данным, в <percent> счастливых браков партнеры <zodiac> поддерживают их стремление к успеху и стабильности. Ты ценишь верность, ответственность и долгосрочные перспективы, а значит, тебе нужен партнер, который разделит твои амбиции и будет готов идти к общему будущему.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "85%"
},
"aquarius": {
"title": "♒ Aquarius",
"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."
"title": "♒ Водолей",
"description": "Согласно нашим исследованиям, в <percent> гармоничных союзов партнеры <zodiac> разделяют их стремление к независимости и нестандартный взгляд на жизнь. Ты ценишь оригинальность, свободу мысли и дружеские отношения в паре, а значит, тебе нужен партнер, который не будет тебя ограничивать, а будет вдохновлять.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "75%"
},
"pisces": {
"title": "♓ Pisces",
"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."
"title": "♓ Рыбы",
"description": "Согласно нашим данным, в <percent> крепких отношений партнеры <zodiac> обладают схожей чувствительностью и умением понимать друг друга без слов. Ты ценишь романтику, душевную близость и тонкие невидимые нити, связывающие сердца.<br><bold> Далее разберём, какие знаки и типы личности тебе подходят.",
"description_bold": "Как укрепить связь и избежать недопонимания?",
"description_percent": "84%"
}
},
"/what-aspects": {
@ -155,11 +320,34 @@
"answer4": "Life Transitions"
},
"/relationship-status": {
"title": "To Better Understand You, Indicate Your Current Relationship Status",
"answer1": "Single",
"answer2": "In a Relationship",
"answer3": "Married",
"answer4": "Divorced"
"title": "Чтобы яснее разобрать вашу суть, укажите какие у вас отношения сейчас?",
"answer1": "Начало",
"answer2": "Долгие отношения",
"answer3": "Развиваются непонятно куда",
"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": {
"title": "Which Element Empowers You the Most?",
@ -181,11 +369,11 @@
"answer7": "Turquoise"
},
"/head-or-heart": {
"title": "What Guides You in Life: The Call of the Heart or the Voice of Reason?",
"answer1": "Follow My Heart",
"answer2": "Rely on Reason",
"answer3": "Combine Both Approaches",
"answer4": "Depends on the Situation"
"title": "Чем вы руководствуетесь в жизни: зовом сердца или голосом разума?",
"answer1": "Следую за сердцем",
"answer2": "Опираюсь на разум",
"answer3": "Сочетаю сердце и разум",
"answer4": "Зависит от ситуации"
},
"/relate-following": {
"title": "How Important Is It for You to Meet Your Partner's Expectations?",
@ -198,13 +386,29 @@
"strongly_disagree": "Not Important at All"
},
"/gender-partner": {
"title": "Your Partner's Gender",
"description": "Select your partner's gender for a personalized astrological analysis. The stars consider every detail! ✨",
"title": "Теперь добавим твоего партнера",
"description": "Чтобы определить ключевые точки вашей совместимости.",
"already_have_account": ""
},
"/partner-similarity": {
"title": "Насколько ваш партнер похож на вас по характеру?",
"answer1": "Очень похож",
"answer2": "Есть сходства, но и различия тоже",
"answer3": "Мы совершенно разные"
},
"/birthdate-partner": {
"title": "Your Partner's Birth Date?",
"text": "We'll incorporate zodiac influences for a more precise compatibility assessment"
"title": "Дата рождения партнера",
"text": "Это основа для понимания динамики вашей пары."
},
"/birthplace-partner": {
"title": "Место рождения партнера",
"text": "Чтобы рассчитать взаимодействие ваших планет, нам нужно место рождения партнера.",
"placeholder": "Страна, Город",
"checkbox": "Я не знаю место его рождения"
},
"/almost-there": {
"title": "Мы почти у цели",
"description": "Еще несколько вопросов чтобы расчеты стали максимально точными."
},
"/date-event": {
"single": {
@ -212,58 +416,70 @@
"text": "💫 A significant date can help reveal planetary influences on your life and provide a more personalized analysis."
},
"relationship": {
"title": "Enter a Significant Date Important to You or Your Partner.",
"text": "💫 A significant date can help reveal planetary influences on your relationship and provide a more personalized analysis."
"title": "Значимая дата важная для вас или вашей пары",
"text": "Она поможет раскрыть ваши отношения и дать более персонализированный анализ."
}
},
"/palms-information-partner": {
"aries": {
"title": "♈ Aries",
"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!"
"title": "♈ Овен",
"description": "Страстный, прямолинейный и инициативный. Влюбляется быстро, действует решительно, но может быть нетерпеливым и ревнивым. Любит лидерство, стремится к ярким эмоциям, не терпит рутины. Если чувства угасают уходит без сожалений.<br><bold>",
"description_bold": "С тобой получится интересное сочетание, давай разберем детали..."
},
"taurus": {
"title": "♉ Taurus",
"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!"
"title": "♉ Телец",
"description": "Надежный, чувственный и преданный. В любви движется медленно, но основательно. Ценит стабильность, комфорт и телесный контакт. Может быть собственником и долго не отпускать прошлое. Не терпит предательства и резких перемен.<br><bold>",
"description_bold": "Как сложится ваша пара? У нас есть ответы..."
},
"gemini": {
"title": "♊ Gemini",
"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!"
"title": "♊ Близнецы",
"description": "Легкий, общительный и непредсказуемый. Быстро увлекается, но может охладеть так же стремительно. Любит интеллектуальное взаимодействие, флирт и свободу. Иногда непостоянен, но с тем, кто сможет удерживать его интерес, готов на многое.<br><bold>",
"description_bold": "Какие у вас шансы на гармонию? Давай разберемся вместе..."
},
"cancer": {
"title": "♋ Cancer",
"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!"
"title": "♋ Рак",
"description": "Чувствительный, заботливый и верный. Эмоции его главная стихия. Влюбляется глубоко, но медленно раскрывается. Может быть ранимым и нуждается в эмоциональной безопасности. Если разочарован уходит, но долго переживает.<br><bold>",
"description_bold": "Какая у вас эмоциональная совместимость? Мы знаем ответ..."
},
"leo": {
"title": "♌ Leo",
"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!"
"title": "♌ Лев",
"description": "Яркий, харизматичный и великодушный. Любит внимание, комплименты и драму. В любви ведет себя как завоеватель, но хочет восхищения и преданности. Может быть эгоцентричным, но при этом предан тем, кто ценит его по достоинству.<br><bold>",
"description_bold": "Сможете ли вы стать настоящей королевской парой? Давай проверим..."
},
"virgo": {
"title": "♍ Virgo",
"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!"
"title": "♍ Дева",
"description": "Рациональный, надежный и требовательный. В любви осторожен, не спешит открываться. Ценит порядок, честность и глубину. Может быть критичным и сдержанным, но если доверится становится преданным партнером.<br><bold>",
"description_bold": "Ваше сочетание может быть неожиданно сильным давай узнаем детали..."
},
"libra": {
"title": "♎ Libra",
"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!"
"title": "♎ Весы",
"description": "Очаровательный, дипломатичный и романтичный. Любит гармонию, красивое ухаживание и интеллектуальное общение. Может долго колебаться перед выбором, но стремится к равноправию и взаимопониманию в отношениях.<br><bold>",
"description_bold": "Какую роль ты займешь в этой связи? Мы расскажем..."
},
"scorpio": {
"title": "♏ Scorpio",
"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!"
"title": "♏ Скорпион",
"description": "Глубокий, страстный и притягательный. Влюбляется всерьез и надолго, но может быть ревнивым и собственником. Ему важно доверие и эмоциональная связь, а предательства не прощает. Если любит то до конца.<br><bold>",
"description_bold": "Какие тайны скрываются в вашем союзе? Мы знаем..."
},
"sagittarius": {
"title": "♐ Sagittarius",
"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!"
"title": "♐ Стрелец",
"description": "Свободолюбивый, энергичный и оптимистичный. Любит приключения, новизну и независимость. Влюбляется быстро, но не терпит давления. Может избегать обязательств, но с тем, кто разделяет его дух свободы, становится верным партнером.<br><bold>",
"description_bold": "Будет ли ваш союз полон страсти или свободы? Давай выясним.."
},
"capricorn": {
"title": "♑ Capricorn",
"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?"
"title": "♑ Козерог",
"description": "Сдержанный, целеустремленный и надежный. В любви серьезен и практичен, не терпит игр и легкомысленности. Долго присматривается, но если решится строит крепкие, долговременные отношения. Верный, но иногда слишком строгий.<br><bold>",
"description_bold": "Совместимость с ним может быть судьбоносной хочешь узнать больше?"
},
"aquarius": {
"title": "♒ Aquarius",
"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!"
"title": "♒ Водолей",
"description": "Оригинальный, независимый и интеллектуальный. Любит свободу, эксперименты и дружбу в отношениях. Может быть эмоционально отстраненным, но с тем, кто разделяет его взгляды, становится преданным союзником.<br><bold>",
"description_bold": "Твоя связь с ним может быть нестандартной давай разберем детали..."
},
"pisces": {
"title": "♓ Pisces",
"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!"
"title": "♓ Рыбы",
"description": "Романтичный, интуитивный и мечтательный. Влюбляется глубоко, но может быть склонен к иллюзиям. Ищет духовную связь, нежность и заботу. Может растворяться в партнере, но если разочаруется уходит, оставляя за собой загадку.<br><bold>",
"description_bold": "Будет ли ваш союз волшебным или иллюзорным? Мы знаем ответ..."
}
},
"/let-scan": {
@ -285,13 +501,13 @@
"color": "#1 Astrology"
},
"/trial-payment": {
"information-title": "We're Ready to Give You All the Answers, Don't Spend Years in Doubt!",
"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-title": "Мы готовы дать тебе все ответы, не трать годы на сомнения!",
"information-description-single": "Ты когда-нибудь задумывался, почему одни отношения развиваются легко, а другие будто натянуты, как струна? Совпадение или знак судьбы? <color> в вашей жизни есть скрытые знаки, которые вы ещё не заметили. <eventDescription><br>Получите детальный анализ совместимости и откройте ответы, которые уже написаны в вашей судьбе.",
"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-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-single-event-description": "Ваша дата <dateEvent> могла стать поворотной точкой или скрытым сигналом.",
"information-description-with-partner": "Ты когда-нибудь задумывался, почему одни отношения развиваются легко, а другие будто натянуты, как струна? Совпадение или знак судьбы? <color> — два знака, созданные для глубины, но какие тайны скрывает ваш союз? <eventDescription> Получите детальный анализ совместимости и откройте ответы, которые уже написаны в вашей судьбе.",
"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": {
"title": "Your Palm Reading <color>",
"title_color": "Is Ready",
@ -307,10 +523,10 @@
"get_personal_prediction": "Get personal prediction",
"how_work": {
"title": "How does AURA work?",
"point1_title": "Send us your palm scan",
"point1_text": "We analyze your palm lines to get hints about your future",
"point2_title": "Your palm reading is generated",
"point2_text": "One of our professional palm readers puts together a report filled with hints about your future",
"point1_title": "Подготовка данных о тебе и твоем партнере",
"point1_text": "Мы анализируем все данные чтобы получить максимально точный прогноз",
"point2_title": "Твой разбор совместимости генерируется",
"point2_text": "Наша команда профессиональных астрологов уже проверила ваш прогноз и внесла дополнительные заделы указанные вами ранее",
"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.",
"point4_title": "Talk with a palm reading specialist anytime",
@ -370,6 +586,41 @@
"text1": "Questions? Were here to help",
"text2": "Customer Support",
"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": {
@ -503,15 +754,15 @@
},
"/depends": {
"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": {
"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-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": {
"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-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": {
"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": {
"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": {
"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": {
@ -540,10 +791,81 @@
"answer3": "Don't see the point"
},
"/checking-phone": {
"title": "What Are Your Thoughts on Checking a Partner's Phone or Messages?",
"answer1": "Strongly against",
"answer2": "Only in extreme cases",
"answer3": "Fine with it"
"title": "Как вы относитесь к проверке телефона или переписок партнёра?",
"answer1": "Против",
"answer2": "Допускаю иногда",
"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": {
"aries": "Aries",
@ -558,5 +880,19 @@
"capricorn": "Capricorn",
"aquarius": "Aquarius",
"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": {
"title": "Твой анализ",
"point1": "Анализирую ваши природные склонности в отношениях...",
"point2": "Точки риска, которые могут привести к сложным отношениям...",
"point3": "Формируем динамику отношений заложеную в тебе...",
"point4": "Разбираем, где вы сами создаете преграды в любви.",
"point5": "Обработка твоих сильных и слабых сторон в любви...",
"point6": "Анализ повторяющихся сценариев в твоих отношениях...",
"point7": "Расшифровываем твою силу в любви и на что нужно обратить внимание...",
"point8": "Раскрываем ваш потенциал в отношениях...",
"point9": "Обрабатываем твой эмоциональный ритм и стиль привязанности...",
"point10": "Разбираем, что ведет вас к счастью в отношениях...",
"point11": "Анализируем, какие качества должны быть у вашего идеального партнера",
"point12": "Разбираем, где вы сами создаете преграды в любви.",
"point13": "Разбираем ваш личный потенциал, чтобы не тратить время на несовместимость."
"point1": "Разбираем, где вы сами создаете преграды в любви...",
"point2": "Анализ повторяющихся сценариев в твоих отношениях...",
"point3": "Расшифровываем твою силу в любви и на что нужно обратить внимание...",
"point4": "Разбираем ваш личный потенциал, чтобы не тратить время на несовместимость..."
},
"/partner-analysis": {
"title": "Анализ твоего партнера",
"point1": "Подбор паттернов совпадения и не совпадения...",
"point2": "Выявляем сильные и уязвимые стороны связи...",
"point3": "Выявляем, как он влияет на вашу совместимость...",
"point4": "Анализируем что движет им на самом деле...",
"point5": "Составляем, какие аспекты требуют внимания...",
"point6": "Анализируем ключевые зоны ваших отношений...",
"point7": "Разбираем его сильные и слабые стороны в отношениях...",
"point8": "Рассчитываем насколько он совместим с вами на глубинном уровне",
"point9": "Проверяем его сильные стороны в паре насколько они подходят вам...",
"point10": "Ищем его черты которые могут создавать сложности...",
"point11": "Подбор паттернов совпадения и не совпадения..."
"point1": "Анализируем что движет им на самом деле...",
"point2": "Рассчитываем, насколько он совместим с вами на глубинном уровне...",
"point3": "Ищем его черты, которые могут создавать сложности...",
"point4": "Выявляем, как он влияет на вашу совместимость..."
},
"/result-analysis": {
"title": "Какой результат разбора отношений вам важнее всего?",
@ -229,7 +213,12 @@
}
},
"/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",
"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": "Начало",
"answer2": "Долгие отношения",
"answer3": "Развиваются непонятно куда",
"answer4": "Разрыв/кризис"
"answer4": "Разрыв/кризис",
"answer5": "Single"
},
"/relationship-status-result": {
"single": {
"title": "Вам кажется, что достойных партнёров нет или вы сталкиваетесь «не с теми»? Дело в несовместимых выборах.",
"text": "Наша система прогнозирует совместимость с точностью до 98%, помогая найти подходящего партнёра и открывая новые перспективы в личной жизни."
},
"start": {
"title": "Как понять, что ваш партнер — действительно “ваш человек” и не ошибиться?",
"text": "Понимание эмоций партнёра — ключ к гармоничным отношениям. Наша система прогнозирует совместимость с точностью до 98% и мы дадим тебе точные отчеты."
@ -402,8 +396,8 @@
},
"/date-event": {
"single": {
"title": "Enter a Significant Date Important to You.",
"text": "💫 A significant date can help reveal planetary influences on your life and provide a more personalized analysis."
"title": "Значимая дата важная для вас",
"text": "Она поможет лучше понять, чего вы ищете в любви, и даст более персонализированный анализ."
},
"relationship": {
"title": "Значимая дата важная для вас или вашей пары",
@ -747,7 +741,7 @@
"title": "Редкий дар. Только <percent> <gender> знака <zodiacSign> принимают решения с полной логической ясностью. В Разборе ты узнаешь, как твой баланс влияет на <partnerZodiacSign> и что с этим делать."
},
"single": {
"title": "На основонии наших данных лишь 9% <gender> рожденных под знаком <zodiacSign> имеют четкую логическую ясность — редкий дар. Мы обязательно это учтем в вашем анализе на совместимость."
"title": "Лишь <percent> <gender> <zodiacSign> решают с полной ясностью — это влияет и на выбор, и на будущее отношений. В разборе ты узнаешь, как работает твой внутренний выбор и что изменить, чтобы вовремя впустить любовь."
}
},
"/with-heart": {
@ -755,7 +749,7 @@
"title": "Ваш выбор естественен — <percent> <gender> с знаком <zodiacSign> следуют зову сердца, но гармония в отношениях — это не только чувства. В вашем разборе мы покажем, какие ошибки можно избежать и как использовать совместимость, чтобы укрепить связь с <partnerZodiacSign>."
},
"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": {
@ -763,7 +757,7 @@
"title": "Даже среди <zodiacSign> не всё решает сердце — <percent> <gender> этого знака опираются на разум. В разборе мы покажем, как сбалансировать логику и тревожность, чтобы избежать ошибок и построить гармоничные отношения с <partnerZodiacSign>!"
},
"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": {
@ -771,7 +765,7 @@
"title": "Факты говорят сами за себя! По нашим данным, только <percent> <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений с <partnerZodiacSign>. В Разборе ты узнаешь, как твой баланс влияет на него и что с этим делать."
},
"single": {
"title": "Факты говорят сами за себя! По нашим данным, только 15% <gender>, рожденных под знаком <zodiacSign>, одинаково следуют и разуму, и сердцу. Именно в этом скрыт секрет гармоничных отношений и мы учтем эту особенность в вашем анализе на совместимость."
"title": "Лишь <percent> <gender> <zodiacSign> одинаково полагаются на разум и сердце — а в этом часто и кроется секрет гармонии. В разборе ты узнаешь, как внутренний баланс влияет на притяжение и что мешает построить настоящую связь."
}
},
"/romantic-gestures": {
@ -786,6 +780,24 @@
"answer2": "Допускаю иногда",
"answer3": "Нормально"
},
"/partner-password": {
"title": "Когда вы начнёте встречаться, как будете относиться к паролю на телефоне партнёра?",
"answer1": "Нормально",
"answer2": "Не нравится",
"answer3": "Обменяемся паролями"
},
"/your-fear": {
"title": "Вы больше боитесь…",
"answer1": "Быть в одиночестве",
"answer2": "Потерять себя в отношениях",
"answer3": "Снова сделать неправильный выбор"
},
"/your-inclination": {
"title": "Когда человек вам нравится, вы склонны…",
"answer1": "Сразу влюбляться",
"answer2": "Осторожничать",
"answer3": "Долго присматриваться"
},
"/complex-relationship-aspect": {
"title": "Какой аспект ваших отношений вам кажется самым сложным?",
"answer1": "Нам трудно понимать друг друга",
@ -798,7 +810,14 @@
"answer1": "Ищем компромисс",
"answer2": "Берем паузу",
"answer3": "Злимся",
"answer4": "Отдаляемся"
"answer4": "Отдаляемся",
"single": {
"title": "Как вы ведетя себя в соре… будь вы в отношениях?",
"answer1": "Ищу компромис",
"answer2": "Беру паузу",
"answer3": "Вспыхиваю",
"answer4": "Закрываюсь"
}
},
"/stress-response-result": {
"compromise": {
@ -820,18 +839,73 @@
"title": "Нужно пространство, чтобы переварить ситуацию и разобраться в своих чувствах. <bold>",
"title_bold": "Важно, чтобы дистанция не превращалась в стену.",
"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": {
"title": "Что нам еще добавить в твой персональный Разбор",
"5_compatibility_zones": "5 ключевых зон совместимости",
"former_partner": "Бывшего партнера",
"potential_partner": "Потенциального партнера",
"analyzing_mistakes_relationships": "Разбор ошибок в отношениях",
"astrological_triggers": "Астрологические триггеры",
"relationship_timing": "Тайминг отношений",
"practices_strengthen_communication": "Практики для усиления связи",
"scenarios_times_crisis": "Сценарии в кризисные периоды",
"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": {
"loaders": {
@ -840,7 +914,15 @@
"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..."
"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": {
"title-1": "Clarifying question.",

View File

@ -74,7 +74,7 @@ export interface IAnswersSessionCompatibilityV3 {
export interface IAnswersSessionCompatibilityV4 {
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';
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';
@ -95,6 +95,9 @@ export interface IAnswersSessionCompatibilityV4 {
what_add_to_analysis: string // '5_compatibility_zones' | 'analyzing_mistakes_relationships' |
// 'astrological_triggers' | 'relationship_timing' | 'practices_strengthen_communication' |
// '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 {

View File

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

View File

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

View File

@ -24,6 +24,7 @@ interface CameraModalProps {
onVideoReady?: () => void;
reinitializeKey?: number; // for reinitializing the camera (change the key to reinitialize the camera)
isCameraVisible?: boolean;
className?: string;
}
function CameraModal({
@ -32,7 +33,8 @@ function CameraModal({
onError,
onVideoReady,
reinitializeKey = 0,
isCameraVisible = true
isCameraVisible = true,
className = ""
}: CameraModalProps) {
const [isVideoReady, setIsVideoReady] = useState(false);
const [isTorchOn, setIsTorchOn] = useState(false);
@ -115,7 +117,7 @@ function CameraModal({
return <ModalOverlay
type={ModalOverlayType.Dark}
className={styles.overlay}
className={`${styles.overlay} ${className}`}
onClick={onClickOverlay}
>
<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 AndroidCamera from "./android";
import IphoneCamera from "./iphone";
import { isWebView } from "@/services/hacks/webviewToSystemBrowser";
function Camera() {
const isIphoneSafari = useMemo((): boolean => {
@ -13,7 +14,7 @@ function Camera() {
!/EdgiOS/i.test(userAgent) && // не Edge
!/OPiOS/i.test(userAgent); // не Opera
return isIOS && isSafari;
return isIOS && (isSafari || isWebView());
}, []);
useEffect(() => {

View File

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

View File

@ -108,4 +108,12 @@
:global(.dark-theme) .modal-container {
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 { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import Loader, { LoaderColor } from "@/components/Loader";
import { useEffect } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -35,6 +37,16 @@ function PalmsInformation() {
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 (
<div className={styles["page-container"]}>
{zodiacImages !== "new" && (

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.v2'];
function Payment() {
const navigate = useNavigate();
const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 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 = () => {
// 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());
};
// 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(() => {
if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return (
<>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}>
<p className={styles.text}>
{translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation />
<div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice(
(
fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})}
</div>
<Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")}
</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}
className={classNameScannedPhoto}
isDecorationShown={isDecorationShown}
isShowLineScale={true}
/>
<h2
className={`palmistry-container__waiting-title ${styles.waitingTitle} ${!isDecorationShown ? styles.hidden : ""}`}

View File

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

View File

@ -30,10 +30,7 @@ function SecretDiscount() {
});
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 price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +42,19 @@ function SecretDiscount() {
}))
}, [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 = () => {
// 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());
};
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return (
<section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px`
}}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header
className={styles.header}
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{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 { useSelector } from "react-redux";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { useEffect } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV3);
@ -24,6 +26,16 @@ function PalmsInformation() {
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 (
<div className={styles["page-container"]}>
<div className={styles.zodiac}>

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.v3'];
function Payment() {
const navigate = useNavigate();
const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 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 = () => {
// 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());
};
// 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(() => {
if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return (
<>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}>
<p className={styles.text}>
{translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation />
<div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice(
(
fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})}
</div>
<Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")}
</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,
});
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 price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +41,19 @@ function SecretDiscount() {
}))
}, [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 = () => {
// 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());
};
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return (
<section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px`
}}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header
className={styles.header}
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{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 styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import Button from "../../components/Button";
import metricService from "@/services/metric/metricService";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import routes from "@/routes";
import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations";
@ -56,6 +56,17 @@ function Birthdate() {
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 (
<div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}>

View File

@ -18,7 +18,8 @@ function DateEvent() {
const navigate = useNavigate();
const dispatch = useDispatch();
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 [isDisabled, setIsDisabled] = useState(true);
@ -30,7 +31,11 @@ function DateEvent() {
const handleNext = () => {
dispatch(actions.compatibilityV4Answers.update({ dateEvent }));
navigate(routes.client.compatibilityV4PartnerAnalysis());
navigate(
isSingle
? routes.client.compatibilityV4HeadOrHeart()
: routes.client.compatibilityV4PartnerAnalysis()
);
updateSession(
{
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 [progress, setProgress] = useState(0);
const interval = useRef<NodeJS.Timeout>();
const { whatAddToAnalysis } = useSelector(selectors.selectCompatibilityV4Answers);
const { whatAddToAnalysis, relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const feature = useSelector(selectors.selectFeature);
const isIOSPath = useMemo(() => feature?.toLowerCase()?.includes("ios"), [feature]);
const authCode = useSelector(selectors.selectAuthCode);
@ -48,6 +48,7 @@ function Loading() {
const { gender, partnerGender, birthdate, partnerBirthdate } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate);
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const isSingle = relationshipStatus === "single";
const preloadImages = useMemo(() => {
return [
@ -71,8 +72,8 @@ function Loading() {
const loadingProfilePoints: IPoint[] = useMemo(() => {
const titles: IPoint[] = [
{
title1: `/loading.loaders.title-1-1`,
title2: `/loading.loaders.title-1-2`,
title1: `/loading.loaders${isSingle ? ".single" : ""}.title-1-1`,
title2: `/loading.loaders${isSingle ? ".single" : ""}.title-1-2`,
modal: {
title: `/loading.modals.title-1`,
description: `/loading.modals.description-1`,
@ -81,8 +82,8 @@ function Loading() {
}
},
{
title1: `/loading.loaders.title-2-1`,
title2: `/loading.loaders.title-2-2`,
title1: `/loading.loaders${isSingle ? ".single" : ""}.title-2-1`,
title2: `/loading.loaders${isSingle ? ".single" : ""}.title-2-2`,
modal: {
title: `/loading.modals.title-2`,
description: `/loading.modals.description-2`,
@ -91,8 +92,8 @@ function Loading() {
}
},
{
title1: `/loading.loaders.title-3-1`,
title2: `/loading.loaders.title-3-2`,
title1: `/loading.loaders${isSingle ? ".single" : ""}.title-3-1`,
title2: `/loading.loaders${isSingle ? ".single" : ""}.title-3-2`,
modal: {
title: `/loading.modals.title-3`,
description: `/loading.modals.description-3`,
@ -104,11 +105,11 @@ function Loading() {
const whatAddToAnalysisAnswers = whatAddToAnalysis.split(",").slice(0, 3);
whatAddToAnalysisAnswers.forEach((answer, index) => {
if (!!answer?.length) {
titles[index].title3 = `/what-add-to-analysis.${answer}`
titles[index].title3 = `/what-add-to-analysis${isSingle ? ".single" : ""}.${answer}`
}
})
return titles;
}, [whatAddToAnalysis]);
}, [whatAddToAnalysis, isSingle]);
useEffect(() => {
if (isIOSPath) {

View File

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

View File

@ -12,7 +12,7 @@ import { selectors } from "@/store";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { usePreloadImages } from "@/hooks/preload/images";
const POINTS_COUNT = 11;
const POINTS_COUNT = 4;
const TOTAL_ANIMATION_TIME = 24600;
function PartnerAnalysis() {
@ -39,8 +39,6 @@ function PartnerAnalysis() {
setListHeight(maxHeight);
}, [listRef]);
// const [currentPoint, setCurrentPoint] = useState(0);
const handleNext = useCallback(() => {
navigate(routes.client.compatibilityV4PalmsInformationPartner());
}, [navigate]);
@ -62,24 +60,6 @@ function PartnerAnalysis() {
})();
}, [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 (
<div className={styles.container}>
<div className={styles["progress-container"]}>
@ -102,7 +82,6 @@ function PartnerAnalysis() {
{translate("/partner-analysis.title")}
</Title>
<div className={styles.list} style={{
// height: `${getPointsHeight()}px`
height: `${listHeight}px`
}}>
{Array.from(Array(POINTS_COUNT).keys()).map((index) => (
@ -111,12 +90,6 @@ function PartnerAnalysis() {
className={styles.item}
ref={(el) => listRef.current[index] = el}
style={{
// marginTop: (() => {
// if (index < currentPoint) {
// return `${-(listRef.current[index]?.offsetHeight || 0) - 32}px`
// }
// return "0px"
// })(),,
animationDuration: `${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 routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.compatibility.4'];
function Payment() {
const navigate = useNavigate();
const { products, currency, getText } = usePaywall({
@ -27,68 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 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 = () => {
// 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());
};
// 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(() => {
if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -99,13 +40,6 @@ function Payment() {
return (
<>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}>
<p className={styles.text}>
{translate("/payment.app_number_one", {
@ -117,10 +51,6 @@ function Payment() {
<PaymentInformation />
<div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice(
(
fullPrice / (
@ -149,29 +79,9 @@ function Payment() {
})}
</div>
<Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")}
</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 { useSession } from "@/hooks/session/useSession";
import { ESourceAuthorization } from "@/api/resources/User";
import { useSelector } from "react-redux";
import { selectors } from "@/store";
function RelateFollowing() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV4);
@ -15,6 +17,8 @@ function RelateFollowing() {
const navigate = useNavigate();
const { questionId } = useParams();
const [activeButton, setActiveButton] = useState<number | null>();
const { relationshipStatus } = useSelector(selectors.selectCompatibilityV4Answers);
const isSingle = relationshipStatus === "single";
const questions: {
id: "partner_expectations";
@ -60,6 +64,7 @@ function RelateFollowing() {
}`
);
}
if (isSingle) return navigate(routes.client.compatibilityV4PartnerPassword());
return navigate(routes.client.compatibilityV4CheckingPhone());
};

View File

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

View File

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

View File

@ -30,10 +30,7 @@ function SecretDiscount() {
});
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 price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
@ -45,86 +42,19 @@ function SecretDiscount() {
}))
}, [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 = () => {
// 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());
};
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return (
<section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px`
}}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header
className={styles.header}
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{translate("secret-discount.title")}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import { useSelector } from "react-redux";
import { selectors } from "@/store";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
const POINTS_COUNT = 13;
const POINTS_COUNT = 4;
const TOTAL_ANIMATION_TIME = 24600;
function YourAnalysis() {
@ -62,24 +62,6 @@ function YourAnalysis() {
})();
}, [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 (
<div className={styles.container}>
<div className={styles["progress-container"]}>
@ -102,7 +84,6 @@ function YourAnalysis() {
{translate("/your-analysis.title")}
</Title>
<div className={styles.list} style={{
// height: `${getPointsHeight()}px`
height: `${listHeight}px`
}}>
{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 activeProduct = products[0];
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
@ -41,48 +40,6 @@ function SecretDiscount() {
}))
}, [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 = () => {
navigate(routes.client.emailMarketingV1SecretDiscountPaymentModal());
}
@ -96,20 +53,6 @@ function SecretDiscount() {
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{translate("secret-discount.title")}

View File

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

View File

@ -1,4 +1,6 @@
import { useTheme } from "@/hooks/theme/useTheme";
import "./styles.css";
import { useMemo } from "react";
export enum LoaderColor {
White = "white",
@ -11,13 +13,16 @@ type LoaderProps = {
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 {
const { darkTheme } = useTheme();
const colorClasses = useMemo(() => ({
[LoaderColor.White]: "loader__white",
[LoaderColor.Black]: darkTheme ? "loader__white" : "loader__black",
[LoaderColor.Red]: "loader__red",
}), [darkTheme]);
return (
<div className={`loader-container ${className}`}>
<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 styles from "./styles.module.scss";
import { DatePicker } from "@/components/DateTimePicker";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
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 { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations";
@ -60,6 +60,17 @@ function Birthdate() {
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 (
<div className={styles["page-container"]}>
<Title variant="h2" className={styles.title}>

View File

@ -14,8 +14,6 @@ import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import routes from "@/routes";
// const placementKey = EPlacementKeys['aura.placement.palmistry.redesign'];
function Payment() {
const navigate = useNavigate();
const { products, currency, getText } = usePaywall({
@ -27,69 +25,11 @@ function Payment() {
const trialPrice = activeProductFromStore?.trialPrice || 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 = () => {
// 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());
};
// 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(() => {
if (!products.length) return;
const _targetProduct = products.find(product => product._id === activeProductFromStore?._id);
@ -100,13 +40,6 @@ function Payment() {
return (
<>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<div className={styles["app-number-one"]}>
<p className={styles.text}>
{translate("/payment.app_number_one", {
@ -118,10 +51,6 @@ function Payment() {
<PaymentInformation />
<div className={styles["prices-description"]}>
{translate("/payment.will_be_charged", {
// trialPrice: addCurrency(getFormattedPrice(trialPrice), currency),
// fullPrice: (
// <s>{addCurrency(getFormattedPrice(fullPrice), currency)}</s>
// ),
splitPrice: addCurrency(getFormattedPrice(
(
fullPrice / (
@ -150,29 +79,9 @@ function Payment() {
})}
</div>
<Guarantees />
{/* {!isShowPaymentModal && ( */}
<Button className={styles.button} onClick={handlePayment}>
{translate("/payment.get_personal_prediction")}
</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,
});
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 price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
// const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
useEffect(() => {
if (!activeProduct) return;
@ -44,90 +39,19 @@ function SecretDiscount() {
}))
}, [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 = () => {
// 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())
};
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return (
<section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px`
}}>
{/* <PaymentModal
// containerClassName={styles["modal-content"]}
open={isPaymentModalOpen}
onClose={onModalClosed}
>
<PaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={placementKey} />
</PaymentModal> */}
<Header
className={styles.header}
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{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 activeProduct = products[0];
// const price = (activeProduct?.price || 0) / 100;
const trialPrice = (activeProduct?.trialPrice || 0) / 100;
const trialDuration = activeProduct?.trialDuration || 7;
@ -41,48 +40,10 @@ function SecretDiscount() {
}))
}, [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 = () => {
// setIsPaymentModalOpen(true);
// showCreditCardForm();
navigate(routes.client.palmistryV2SecretDiscountPaymentModal());
};
// useEffect(() => {
// if (error) {
// onPaymentError();
// }
// }, [error])
// useEffect(() => {
// if (isPaymentSuccess) {
// onPaymentSuccess();
// }
// }, [isPaymentSuccess])
return (
<section className={styles.container} ref={elementRef} style={{
paddingBottom: `${height + 42}px`
@ -92,20 +53,6 @@ function SecretDiscount() {
classNameTitle={styles["header-title"]}
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} />
<Title className={styles.title} variant="h1">
{translate("secret-discount.title")}

View File

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

View File

@ -1,120 +1,123 @@
import { useMemo, useState } from "react";
import styles from "./styles.module.css";
// import { useMemo, useState } from "react";
// import styles from "./styles.module.css";
import {
useStripe,
useElements,
ExpressCheckoutElement,
} from "@stripe/react-stripe-js";
import {
AvailablePaymentMethods,
StripeExpressCheckoutElementReadyEvent,
} from "@stripe/stripe-js";
import { checkExpressCheckoutStripeFormAvailable } from "@/data/paymentMethods";
// import {
// useStripe,
// useElements,
// ExpressCheckoutElement,
// } from "@stripe/react-stripe-js";
// import {
// AvailablePaymentMethods,
// StripeExpressCheckoutElementReadyEvent,
// } from "@stripe/stripe-js";
// import { checkExpressCheckoutStripeFormAvailable } from "@/data/paymentMethods";
interface IExpressCheckoutStripeProps {
clientSecret: string;
returnUrl?: string;
isHide?: boolean;
onAvailable?: (
isAvailable: boolean,
availableMethods: AvailablePaymentMethods | undefined
) => void;
onChangeLoading?: (isLoading: boolean) => void;
paymentMethodOrderList?: string[];
}
// interface IExpressCheckoutStripeProps {
// clientSecret: string;
// returnUrl?: string;
// isHide?: boolean;
// onAvailable?: (
// isAvailable: boolean,
// availableMethods: AvailablePaymentMethods | undefined
// ) => void;
// onChangeLoading?: (isLoading: boolean) => void;
// paymentMethodOrderList?: string[];
// }
function ExpressCheckoutStripe({
clientSecret,
returnUrl = `https://${window.location.host}/payment/result/`,
isHide = false,
onAvailable,
onChangeLoading,
paymentMethodOrderList
}: IExpressCheckoutStripeProps) {
const stripe = useStripe();
const elements = useElements();
const [errorMessage, setErrorMessage] = useState<string>();
const [isAvailable, setIsAvailable] = useState(false);
const isHideForm = useMemo(
() => isHide || !isAvailable,
[isAvailable, isHide]
);
function ExpressCheckoutStripe(
// {
// clientSecret,
// returnUrl = `https://${window.location.host}/payment/result/`,
// isHide = false,
// onAvailable,
// onChangeLoading,
// paymentMethodOrderList
// }: IExpressCheckoutStripeProps
) {
// const stripe = useStripe();
// const elements = useElements();
// const [errorMessage, setErrorMessage] = useState<string>();
// const [isAvailable, setIsAvailable] = useState(false);
// const isHideForm = useMemo(
// () => isHide || !isAvailable,
// [isAvailable, isHide]
// );
const onConfirm = async () =>
// event: StripeExpressCheckoutElementConfirmEvent
{
if (!stripe || !elements) {
// Stripe.js hasn't loaded yet.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
// const onConfirm = async () =>
// // event: StripeExpressCheckoutElementConfirmEvent
// {
// if (!stripe || !elements) {
// // Stripe.js hasn't loaded yet.
// // Make sure to disable form submission until Stripe.js has loaded.
// return;
// }
const { error: submitError } = await elements.submit();
if (submitError) {
setErrorMessage(submitError.message);
return;
}
// const { error: submitError } = await elements.submit();
// if (submitError) {
// setErrorMessage(submitError.message);
// return;
// }
// // Create the PaymentIntent and obtain clientSecret
// const res = await fetch("/create-intent", {
// method: "POST",
// });
// const { client_secret: clientSecret } = await res.json();
// // // Create the PaymentIntent and obtain clientSecret
// // const res = await fetch("/create-intent", {
// // method: "POST",
// // });
// // const { client_secret: clientSecret } = await res.json();
// Confirm the PaymentIntent using the details collected by the Express Checkout Element
const { error } = await stripe.confirmPayment({
// `elements` instance used to create the Express Checkout Element
elements,
// `clientSecret` from the created PaymentIntent
clientSecret,
confirmParams: {
return_url: returnUrl,
},
});
// // Confirm the PaymentIntent using the details collected by the Express Checkout Element
// const { error } = await stripe.confirmPayment({
// // `elements` instance used to create the Express Checkout Element
// elements,
// // `clientSecret` from the created PaymentIntent
// clientSecret,
// confirmParams: {
// return_url: returnUrl,
// },
// });
if (error) {
// 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)
setErrorMessage(error.message);
} else {
// The payment UI automatically closes with a success animation.
// Your customer is redirected to your `return_url`.
}
};
// if (error) {
// // 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)
// setErrorMessage(error.message);
// } else {
// // The payment UI automatically closes with a success animation.
// // Your customer is redirected to your `return_url`.
// }
// };
const onReady = (event: StripeExpressCheckoutElementReadyEvent) => {
const _isAvailable = checkExpressCheckoutStripeFormAvailable(
event.availablePaymentMethods
);
setIsAvailable(_isAvailable);
onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods);
onChangeLoading && onChangeLoading(false);
};
// const onReady = (event: StripeExpressCheckoutElementReadyEvent) => {
// const _isAvailable = checkExpressCheckoutStripeFormAvailable(
// event.availablePaymentMethods
// );
// setIsAvailable(_isAvailable);
// onAvailable && onAvailable(_isAvailable, event.availablePaymentMethods);
// onChangeLoading && onChangeLoading(false);
// };
return (
<div className={`${styles.container} ${isHideForm ? styles.hide : ""}`}>
<ExpressCheckoutElement
onReady={onReady}
onLoadError={(e) => {
console.log("Error: ", e);
onChangeLoading && onChangeLoading(false);
}}
onConfirm={onConfirm}
options={{
layout: {
maxColumns: 1,
overflow: "never",
},
paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"],
wallets: {
googlePay: "always",
applePay: "always",
},
}}
/>
{errorMessage && <p className={styles.error}>{errorMessage}</p>}
</div>
<></>
// <div className={`${styles.container} ${isHideForm ? styles.hide : ""}`}>
// <ExpressCheckoutElement
// onReady={onReady}
// onLoadError={(e) => {
// console.log("Error: ", e);
// onChangeLoading && onChangeLoading(false);
// }}
// onConfirm={onConfirm}
// options={{
// layout: {
// maxColumns: 1,
// overflow: "never",
// },
// paymentMethodOrder: paymentMethodOrderList || ["apple_pay", "google_pay", "amazon_pay", "link"],
// wallets: {
// googlePay: "always",
// applePay: "always",
// },
// }}
// />
// {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 { ResponsePost } from "@/api/resources/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 { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
@ -26,11 +23,10 @@ function AddConsultationPage() {
const api = useApi();
const tokenFromStore = useSelector(selectors.selectToken);
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 returnUrl = `${window.location.protocol}//${
window.location.host
}${routes.client.getInformationPartner()}`;
const returnUrl = `${window.location.protocol}//${window.location.host
}${routes.client.getInformationPartner()}`;
const loadData = useCallback(async () => {
return await api.getSinglePaymentProducts({ token: tokenFromStore });
@ -80,26 +76,6 @@ function AddConsultationPage() {
};
return (
<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 && (
<Loader className={styles.loader} color={LoaderColor.Black} />
)}

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