Merge pull request #65 from pennyteenycat/email-marketing-v2

email-marketing-v2
This commit is contained in:
pennyteenycat 2025-10-18 22:04:37 +02:00 committed by GitHub
commit 9d682a8499
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 6011 additions and 20 deletions

View File

@ -402,6 +402,210 @@
"pricing-summary-trial-description": "You will be charged only {totalToday} for your {trialDuration}-day trial. Subscription renews automatically until cancelled. You can cancel at any time before the end of the trial.",
"reserved-for": "Reserved for {time}"
}
},
"v2": {
"Landing": {
"header-timer": {
"title": "Reserved for {time}"
},
"header": {
"title": "Special Offer!",
"description": "Everything is free, trial period included!"
},
"hi-block": {
"title": "Hello, Sunshine 👋",
"description": "Your well-being and happiness are our top priority!",
"card": {
"title": "That's why we've decided to give you a personalised plan and access to a trial version of our app for FREE!",
"image-description": "soulmate portraits delivered today",
"count": "900+"
}
},
"what-get": {
"title": "What you will receive:",
"detailed-portrait": {
"header": {
"title": "A detailed portrait of your other half is ready"
}
},
"guide": {
"header": {
"title": "Guide “Finding the One”"
},
"list": {
"1": "Why love doesn't come - and how to open the way to it",
"2": "7 signs that you have met your soulmate",
"3": "The main secret of couples who stay together",
"4": "Mistakes that destroy even the strongest feelings",
"5": "5 habits that make relationships happy"
}
},
"individual-advice": {
"header": {
"title": "Personalized advice from a personal relationship psychologist"
},
"messages": {
"me": {
"text": "Why do I still doubt whether he loves me?",
"time": "16:38 AM"
},
"advisor": {
"text": "Because you feel that the connection is special. Fate was right—you are reflections of each other.",
"time": "16:39 AM"
}
},
"typing": "typing "
},
"search-compatible-partner": {
"header": {
"title": "Search for the most compatible partner"
},
"content": {
"partner-name": "Your significant other",
"percent": "97%",
"compatibility": "Compatibility",
"bars": {
"1": {
"percent": "92",
"text": "Love",
"colors": {
"path": "#FD4B4B",
"trail": "#FCE7F3"
}
},
"2": {
"percent": "88",
"text": "Sex",
"colors": {
"path": "#8B5CF6",
"trail": "#EDE9FE"
}
},
"3": {
"percent": "79",
"text": "Family",
"colors": {
"path": "#4F46E5",
"trail": "##EEF2FF"
}
}
}
}
}
},
"special-offer": {
"title": "Special price",
"prices": {
"old-price": {
"description": "Regular price"
},
"new-price": {
"description": "Today"
}
},
"trial-offer": {
"title": "DOUBLED!",
"description": "Full access to all materials"
}
},
"plan-also-includes": {
"title": "Your plan also includes:",
"list": {
"items": {
"1": {
"title": "Чат с экспертом",
"text": "Задай свой вопрос и получи первый совет уже сегодня."
},
"2": {
"title": "Анализ совместимости",
"text": "Пойми, почему вы притягиваетесь друг к другу - и какие различия могут мешать гармонии."
},
"3": {
"title": "Как говорить о чувствах и быть понятым",
"text": "Пошаговый гайд, который поможет восстановить близость и искреннее общение."
},
"4": {
"title": "Медитации и аффирмации для сердца",
"text": "Практики, которые открывают внутреннюю уверенность и притягивают любовь."
}
}
}
},
"reviews": {
"title": "What our users say:",
"items": {
"1": {
"username": "@anna.smith28",
"flag": "🇺🇸",
"text": "The drawing was made before we met, and it matched down to the smallest detail: the same gaze, the same mole. We simply couldn't believe it! Now the portrait hangs in our home as a sign of destiny.",
"answer": {
"title": "Response from the support team",
"text": "Anna, it's just the magic of fate! We are very happy that your portrait has become part of your love story. May it remind you how important it is to believe in the signs of the universe 🌹"
}
},
"2": {
"username": "@mike.andrews_89",
"flag": "🇺🇸",
"text": "I decided to check whether my wife was truly my other half. The portrait matched her down to the last detail—the same gaze, the same energy. We both felt as if fate had just confirmed our union.",
"answer": {
"title": "Response from the support team",
"text": "That's amazing! Stories like this prove that true love really does have an impact on an energetic level. May your portrait preserve the warmth of your connection for many years to come!"
}
},
"3": {
"username": "@emily.harris_ang...",
"flag": "🇬🇧",
"text": "The “Finding the One” guide changed my life! The portrait was 100% accurate, and I met my love in a museum, just as predicted! ✨",
"answer": {
"title": "Response from the support team",
"text": "Elena, your story inspires us! Thank you for sharing your success. May your love be strong and long-lasting!"
}
}
}
},
"statistics": {
"title": "899,247",
"description": "People have already found love",
"more-avatars": "+2K",
"period-statistics": {
"month": {
"title": "54K",
"text": "Per month"
},
"today": {
"title": "2K",
"text": "Today"
}
}
},
"real-time-activity": {
"title": "Real-time activity",
"text": "<bold>@elena_art</bold> received a portrait",
"time": "2 мин назад"
},
"money-back-guarantee": {
"title": "100% Money Back Guarantee",
"term": "30 DAYS",
"description": "We are confident that we can help you gain a deeper understanding of your partner and show you what they are really like. After receiving excellent customer reviews, <bold>we are ready to give you a 100% refund</bold> if this report and portrait are not useful to you.",
"footer": "Без вопросов. Без риска."
},
"guaranteed-security-payments": "Guaranteed security payments",
"button-continue": "CONTINUE"
},
"SpecialOffer": {
"title": "Special Offer!",
"start-trial": "Start your {days}-day trial",
"cancel-anytime": "No pressure. Cancel anytime",
"policy": "By continuing you agree that if you don't cancel prior to the end of the {days}-days trial, you will automatically be charged {price} every {billingPeriod} until you cancel in settings. Learn more about cancellation and <refundLink>Refund policy</refundLink> in <subscriptionLink>Subscription terms</subscriptionLink>",
"button-continue": "CONTINUE",
"pricing-summary-total-today": "Total today:",
"pricing-summary-code-applied": "Code applied!",
"pricing-summary-cost-after-trial": "Your cost per 2 weeks after trial",
"pricing-summary-trial-description": "You will be charged only {trialPrice} for your {trialPeriod} trial. Your plan will then cost {price} per {billingPeriod}. Subscription renews automatically until cancelled. You can cancel at any time before the end of the trial.",
"reserved-for": "Reserved for {time}",
"save-every-period": "Save {price} every period",
"cost-for-one-day": "Your cost for one day after the trial is {cost}"
}
}
}
},

View File

@ -31,6 +31,14 @@ const nextConfig: NextConfig = {
},
],
},
turbopack: {
rules: {
"*.svg": {
loaders: ["@svgr/webpack"],
as: "*.js",
},
},
},
};
const withNextIntl = createNextIntlPlugin();

2789
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@svgr/webpack": "^8.1.0",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,16 @@
<svg width="28" height="30" viewBox="0 0 28 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.6547 17.7527L26.0702 7.37305L25.111 7.44353L11.9868 7.58448L17.9757 26.4299L25.8129 26.5825L26.9473 26.5941C27.6142 25.6197 27.9999 24.4455 27.9999 23.1772C28.0119 20.876 26.69 18.7743 24.6547 17.7527Z" fill="black" fill-opacity="0.2"/>
<mask id="mask0_336_11552" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="26" height="30">
<path d="M0 0H25.6841V30H0V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_336_11552)">
<path d="M25.3799 2.7357C25.1696 2.501 24.8769 2.36004 24.5613 2.36004C24.4561 2.36004 24.3508 2.37168 24.2571 2.40691C24.1988 2.43052 24.1402 2.44215 24.07 2.45379C23.0756 2.72407 22.0346 2.86503 20.9936 2.86503C18.3503 2.86503 15.7301 1.94914 13.6246 0.281582C13.4024 0.105718 13.11 0 12.8292 0C12.5368 0 12.2561 0.09375 12.0339 0.281582C9.92831 1.94914 7.30819 2.85306 4.66488 2.86503C3.62385 2.86503 2.59441 2.72407 1.60007 2.45379C1.54179 2.44215 1.48318 2.41855 1.41299 2.40691C1.30769 2.38364 1.21398 2.36004 1.10869 2.36004C0.793136 2.36004 0.50076 2.501 0.29017 2.7357C0.0795794 2.97041 -0.0141266 3.28757 0.032892 3.59275L1.99774 18.3168C2.14972 19.5967 2.5358 20.8062 3.15565 21.9335C3.78742 23.0605 4.61786 24.0352 5.63572 24.8334L11.718 29.6124C12.0339 29.859 12.4196 29.988 12.8176 30H12.8408C13.2385 30 13.6246 29.859 13.9289 29.6124L20.0111 24.8334C21.0287 24.0352 21.8591 23.0605 22.4909 21.9335C23.1107 20.8062 23.5084 19.5967 23.6488 18.3168L25.6375 3.59275C25.6726 3.28757 25.5789 2.97041 25.3799 2.7357Z" fill="#25BF6A"/>
</g>
<path d="M6.80516 23.3308C5.17937 22.0509 4.11483 20.1486 3.88107 18.0938V18.0705L2.06787 4.52061C2.92182 4.68484 3.78736 4.77892 4.66482 4.77892C7.57732 4.77892 10.4663 3.82779 12.8292 2.07812C15.2036 3.82779 18.081 4.77892 20.9935 4.77892C21.871 4.77892 22.7365 4.69648 23.5905 4.52061L21.7773 18.0821V18.0938C21.5435 20.1486 20.4789 22.0625 18.8532 23.3308L12.9696 27.9451C12.8878 28.0156 12.7706 28.0156 12.6888 27.9451L6.80516 23.3308Z" fill="#22AD60"/>
<path d="M7.2499 22.7556C5.77577 21.605 4.81686 19.867 4.59468 18.0119V17.9767L2.92188 5.40122C3.49504 5.4717 4.07979 5.50694 4.66487 5.50694C7.54228 5.50694 10.4313 4.60302 12.8292 2.9707C15.2388 4.60302 18.1277 5.50694 20.9936 5.50694C21.5786 5.50694 22.1518 5.4717 22.7366 5.40122L21.0522 18.0003V18.0119C20.8416 19.8789 19.8707 21.605 18.3969 22.7556L12.8176 27.1353L7.2499 22.7556Z" fill="#25BF6A"/>
<path d="M7.24978 22.755C5.86935 21.6748 4.93362 20.0778 4.65283 18.3401C4.96872 18.3517 5.2846 18.3637 5.60049 18.3637C8.81696 18.3637 11.9867 17.483 14.7592 15.8274C17.5429 14.1599 19.8239 11.7766 21.356 8.91155L22.5725 6.65723L21.0636 17.9996V18.0113C20.853 19.8783 19.8822 21.6044 18.4084 22.755L12.8291 27.1346L7.24978 22.755Z" fill="#23B564"/>
<path d="M15.8353 7.65523C15.0516 6.84506 13.987 6.39891 12.8642 6.38695C11.7414 6.37531 10.6769 6.80982 9.8696 7.59639C9.06267 8.38329 8.61831 9.45177 8.60639 10.5788L8.57129 14.8058L9.79973 14.8175L9.83483 11.4478L9.84642 10.5904C9.85801 8.93482 11.2149 7.60802 12.8526 7.61999C14.5016 7.63163 15.8234 8.99366 15.8118 10.6376L15.8002 11.4947L15.7767 13.6316L15.7651 14.8647L16.9932 14.8763L17.0283 10.6492C17.0518 9.53389 16.6187 8.4654 15.8353 7.65523Z" fill="white"/>
<path d="M17.1687 12.0335L8.44278 11.9513C7.70572 11.9394 7.10938 12.5384 7.10938 13.2662L7.17957 19.9354C7.19116 20.5697 7.69413 21.0744 8.32557 21.0744L17.4025 21.1568C18.0458 21.1568 18.5723 20.6402 18.5607 19.9942L18.4905 13.3719C18.4905 12.6438 17.9054 12.0335 17.1687 12.0335ZM13.4956 18.7615L12.0102 18.7496C11.8698 18.7496 11.7645 18.6086 11.7996 18.468L12.3261 16.7888C11.9867 16.6126 11.7765 16.2369 11.8232 15.826C11.8698 15.3799 12.2324 15.0158 12.6771 14.969C13.2619 14.9101 13.7532 15.3679 13.7413 15.9434C13.7413 16.319 13.5191 16.6362 13.2152 16.8004L13.7062 18.4912C13.7413 18.6206 13.636 18.7615 13.4956 18.7615Z" fill="#FFD138"/>
<path d="M27.099 23.1897C27.099 23.3586 27.0907 23.5268 27.0745 23.6951C27.058 23.8629 27.0331 24.0298 27.0003 24.1954C26.9676 24.361 26.9268 24.5245 26.8782 24.6861C26.8292 24.8477 26.7725 25.0062 26.7083 25.1622C26.6437 25.3181 26.5722 25.4707 26.4927 25.6196C26.4136 25.7685 26.3272 25.9131 26.2338 26.0534C26.1404 26.1937 26.0401 26.329 25.9335 26.4597C25.8269 26.5903 25.714 26.715 25.5951 26.8344C25.4762 26.954 25.3517 27.0671 25.2219 27.1741C25.0918 27.2812 24.9567 27.3819 24.817 27.4756C24.6772 27.5694 24.5332 27.6562 24.3849 27.7356C24.2365 27.8151 24.0845 27.8872 23.9292 27.952C23.7739 28.0165 23.6157 28.0734 23.4547 28.1223C23.2938 28.1715 23.1309 28.2123 22.966 28.2453C22.8011 28.2782 22.6349 28.3028 22.4674 28.3194C22.3001 28.336 22.1323 28.3443 21.9641 28.3443C21.7959 28.3443 21.6283 28.336 21.4608 28.3194C21.2935 28.3028 21.1273 28.2782 20.9624 28.2453C20.7975 28.2123 20.6346 28.1715 20.4737 28.1223C20.3128 28.0734 20.1545 28.0165 19.9992 27.952C19.8436 27.8872 19.6919 27.8151 19.5436 27.7356C19.3953 27.6562 19.2512 27.5694 19.1115 27.4756C18.9714 27.3819 18.8367 27.2812 18.7065 27.1741C18.5767 27.0671 18.4522 26.954 18.3334 26.8344C18.2142 26.715 18.1016 26.5903 17.9946 26.4597C17.888 26.329 17.788 26.1937 17.6946 26.0534C17.6013 25.9131 17.5148 25.7685 17.4354 25.6196C17.3562 25.4707 17.2844 25.3181 17.2202 25.1622C17.1556 25.0062 17.0993 24.8477 17.0503 24.6861C17.0016 24.5245 16.9606 24.361 16.9278 24.1954C16.895 24.0298 16.8705 23.8629 16.8539 23.6951C16.8374 23.5268 16.8291 23.3586 16.8291 23.1897C16.8291 23.0209 16.8374 22.8523 16.8539 22.6844C16.8705 22.5165 16.895 22.3497 16.9278 22.1841C16.9606 22.0185 17.0016 21.855 17.0503 21.6934C17.0993 21.5318 17.1556 21.3729 17.2202 21.217C17.2844 21.0611 17.3562 20.9088 17.4354 20.7599C17.5148 20.611 17.6013 20.4663 17.6946 20.326C17.788 20.1854 17.888 20.0501 17.9946 19.9195C18.1016 19.7891 18.2142 19.6641 18.3334 19.5448C18.4522 19.4254 18.5767 19.3121 18.7065 19.205C18.8367 19.098 18.9714 18.9976 19.1115 18.9038C19.2512 18.8101 19.3953 18.7233 19.5436 18.6439C19.6919 18.5641 19.8436 18.4919 19.9992 18.4274C20.1545 18.3629 20.3128 18.3061 20.4737 18.2569C20.6346 18.208 20.7975 18.1671 20.9624 18.1342C21.1273 18.1013 21.2935 18.0764 21.4608 18.0598C21.6283 18.0435 21.7959 18.0352 21.9641 18.0352C22.1323 18.0352 22.3001 18.0435 22.4674 18.0598C22.6349 18.0764 22.8011 18.1013 22.966 18.1342C23.1309 18.1671 23.2938 18.208 23.4547 18.2569C23.6157 18.3061 23.7739 18.3629 23.9292 18.4274C24.0845 18.4919 24.2365 18.5641 24.3849 18.6439C24.5332 18.7233 24.6772 18.8101 24.817 18.9038C24.9567 18.9976 25.0918 19.098 25.2219 19.205C25.3517 19.3121 25.4762 19.4254 25.5951 19.5448C25.714 19.6641 25.8269 19.7891 25.9335 19.9195C26.0401 20.0501 26.1404 20.1854 26.2338 20.326C26.3272 20.4663 26.4136 20.611 26.4927 20.7599C26.5722 20.9088 26.6437 21.0611 26.7083 21.217C26.7725 21.3729 26.8292 21.5318 26.8782 21.6934C26.9268 21.855 26.9676 22.0185 27.0003 22.1841C27.0331 22.3497 27.058 22.5165 27.0745 22.6844C27.0907 22.8523 27.099 23.0209 27.099 23.1897Z" fill="#39E083"/>
<path d="M24.2336 21.2988L21.6135 23.9291L20.432 22.7433L19.4844 23.6825L21.6135 25.8194L25.1693 22.2383L24.2336 21.2988Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,9 +1,9 @@
import { notFound } from "next/navigation";
import { PortraitView } from "@/components/domains/portraits";
import { DashboardData, DashboardSchema } from "@/entities/dashboard/types";
import { http } from "@/shared/api/httpClient";
import { API_ROUTES } from "@/shared/constants/api-routes";
import { DashboardData, DashboardSchema } from "@/entities/dashboard/types";
// Force dynamic to always get fresh data
export const dynamic = "force-dynamic";
@ -20,7 +20,7 @@ export default async function PortraitPage({
cache: "no-store",
schema: DashboardSchema,
});
const portrait = dashboard.partnerPortraits?.find(p => p._id === id);
if (!portrait || portrait.status !== "done" || !portrait.imageUrl) {

View File

@ -0,0 +1,14 @@
.container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 460px;
margin: 0 auto;
padding-inline: 16px;
padding-bottom: 140px;
height: fit-content;
min-height: 100dvh;
background-color: #f3f4f6;
}

View File

@ -0,0 +1,64 @@
import {
GuaranteedSecurityPayments,
Header,
HeaderTimer,
HiBlock,
LandingButtonWrapper,
MoneyBackGuarantee,
Payments,
PlanAlsoIncludes,
RealTimeActivity,
Reviews,
SpecialOffer,
Statistics,
WhatGet,
} from "@/components/domains/email-marketing/compatibility/v2";
import { loadFunnelPaymentById } from "@/entities/session/funnel/loaders";
import {
IFunnelPaymentPlacement,
IFunnelPaymentVariant,
} from "@/entities/session/funnel/types";
import { Currency, ELocalesPlacement } from "@/types";
import styles from "./page.module.scss";
const payload = {
funnel: ELocalesPlacement.EmailMarketingCompatibilityV2,
};
export default async function EmailMarketingCompatibilityV1Landing() {
const payment = (await loadFunnelPaymentById(
payload,
"main"
)) as IFunnelPaymentPlacement | null;
const variant = payment?.variants?.[0];
const currency = payment?.currency || Currency.USD;
const oldTrialInterval = 7; // TODO
const newTrialInterval = payment?.trialInterval || 7;
const trialPeriod = payment?.trialPeriod || "DAY";
return (
<div className={styles.container}>
<HeaderTimer />
<Header />
<HiBlock />
<WhatGet />
<SpecialOffer
variant={variant as IFunnelPaymentVariant}
currency={currency}
oldTrialInterval={oldTrialInterval}
newTrialInterval={newTrialInterval}
trialPeriod={trialPeriod}
/>
<PlanAlsoIncludes />
<Reviews />
<Statistics />
<RealTimeActivity />
<MoneyBackGuarantee />
<GuaranteedSecurityPayments />
<Payments />
<LandingButtonWrapper />
</div>
);
}

View File

@ -0,0 +1,10 @@
<svg width="20" height="23" viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_339_96" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="23">
<path d="M0 0H20V22.6667H0V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_339_96)">
<path d="M15.2679 1.12718C13.3208 1.12718 11.4571 0.727467 10 0C8.54335 0.727467 6.67945 1.12718 4.73211 1.12718C3.01673 1.12718 1.36526 0.816768 0 0.245102V9.65372C0 15.7775 4.47844 21.5946 10 22.6664C15.5218 21.5946 20 15.7775 20 9.65372V0.245102C18.635 0.816768 16.9835 1.12718 15.2679 1.12718Z" fill="#00AE37"/>
</g>
<path d="M8.03704 15.101L3.87109 10.9977L5.11941 9.76813L8.03704 12.6419L14.8802 5.90137L16.1285 7.13091L8.03704 15.101Z" fill="white"/>
<path d="M13.9968 20.9727C17.2096 18.7979 19.5429 14.9801 19.9398 10.8858L16.1286 7.13184L8.03711 15.1019L13.9968 20.9727Z" fill="#008E28"/>
</svg>

After

Width:  |  Height:  |  Size: 901 B

View File

@ -0,0 +1,75 @@
.container {
width: 100%;
max-width: 560px;
height: fit-content;
min-height: 100dvh;
// overflow-x: hidden;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(90deg, #a355f6 0%, #634ae9 100%);
color: #fff;
margin: 0 auto;
& > .headerTimer {
width: 100%;
}
}
.title {
font-size: 36px;
font-weight: 800;
letter-spacing: -1.4px;
margin-top: 16px;
}
.content {
width: 100%;
height: fit-content;
background-color: #fff;
border-radius: 30px 30px 0 0;
min-height: calc(100dvh - 39px - 26px * 1.25 - 29px);
margin-top: 12px;
padding: 24px 16px 160px;
color: #000;
display: flex;
flex-direction: column;
align-items: center;
& > .contentTitle {
font-size: 24px;
line-height: 32px;
font-weight: 700;
color: #111827;
}
& > .contentDescription {
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
font-weight: 600;
line-height: 28px;
color: #111827;
margin-top: 19px;
}
& > .contentPolicy {
font-size: 12px;
line-height: 125%;
font-weight: 300;
margin-bottom: 0;
margin-top: 39px;
text-align: center;
color: #6f6d6d;
& > a {
text-decoration: underline;
}
}
& > .button {
margin-top: 59px;
max-width: 307px;
}
}

View File

@ -0,0 +1,101 @@
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
HeaderTimer,
PricingSummary,
SpecialOfferButtonWrapper,
} from "@/components/domains/email-marketing/compatibility/v2";
import { Typography } from "@/components/ui";
import { loadFunnelPaymentById } from "@/entities/session/funnel/loaders";
import { IFunnelPaymentPlacement } from "@/entities/session/funnel/types";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import { getPeriodTextServer } from "@/shared/utils/period-server";
import { getFormattedPrice } from "@/shared/utils/price";
import { Currency, ELocalesPlacement } from "@/types";
import GuaranteeIcon from "./guarantee.svg";
import styles from "./page.module.scss";
const payload = {
funnel: ELocalesPlacement.EmailMarketingCompatibilityV2,
};
export default async function SpecialOfferPage() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("SpecialOffer")
);
const payment = (await loadFunnelPaymentById(
payload,
"main"
)) as IFunnelPaymentPlacement | null;
const trialInterval = payment?.trialInterval || 7;
const trialPeriod = payment?.trialPeriod || "DAY";
const billingInterval = payment?.billingInterval || 7;
const billingPeriod = payment?.billingPeriod || "DAY";
const variant = payment?.variants?.[0];
const productId = variant?.id || "";
const placementId = payment?.placementId || "";
const paywallId = payment?.paywallId || "";
const trialPrice = variant?.trialPrice || 0;
const price = variant?.price || 0;
const currency = payment?.currency || Currency.USD;
return (
<div className={styles.container}>
<HeaderTimer className={styles.headerTimer} />
<Typography as="h1" color="white" className={styles.title}>
{t("title")}
</Typography>
<div className={styles.content}>
<Typography as="h2" className={styles.contentTitle}>
{t("start-trial", { days: trialInterval })}
</Typography>
<Typography as="p" className={styles.contentDescription}>
<GuaranteeIcon />
{t("cancel-anytime")}
</Typography>
<PricingSummary
trialPrice={trialPrice}
trialInterval={trialInterval}
trialPeriod={trialPeriod}
price={price}
currency={currency}
billingInterval={billingInterval}
billingPeriod={billingPeriod}
/>
<SpecialOfferButtonWrapper
productId={productId}
placementId={placementId}
paywallId={paywallId}
/>
<p className={styles.contentPolicy}>
{t.rich("policy", {
days: trialInterval,
price: getFormattedPrice(price, currency),
billingPeriod: await getPeriodTextServer(
billingPeriod,
billingInterval
),
refundLink: chunks => (
<Link href="https://witlab.us/refund" target="_blank">
{chunks}
</Link>
),
subscriptionLink: chunks => (
<Link href="https://witlab.us/terms" target="_blank">
{chunks}
</Link>
),
})}
</p>
</div>
</div>
);
}

View File

@ -2,14 +2,24 @@ import clsx from "clsx";
import styles from "./MessageBubble.module.scss";
interface MessageBubbleProps {
interface MessageBubbleProps extends React.ComponentProps<"div"> {
isOwn: boolean;
children: React.ReactNode;
}
export default function MessageBubble({ isOwn, children }: MessageBubbleProps) {
export default function MessageBubble({
isOwn,
children,
className,
}: MessageBubbleProps) {
return (
<div className={clsx(styles.bubble, isOwn ? styles.own : styles.other)}>
<div
className={clsx(
styles.bubble,
isOwn ? styles.own : styles.other,
className
)}
>
{children}
</div>
);

View File

@ -1,17 +1,25 @@
import clsx from "clsx";
import { Typography } from "@/components/ui";
import styles from "./MessageMeta.module.scss";
interface MessageMetaProps {
interface MessageMetaProps extends React.ComponentProps<"div"> {
time: string | null;
timeClassName?: string;
children?: React.ReactNode;
}
export default function MessageMeta({ time, children }: MessageMetaProps) {
export default function MessageMeta({
time,
timeClassName,
children,
className,
}: MessageMetaProps) {
return (
<div className={styles.meta}>
<div className={clsx(styles.meta, className)}>
{time && (
<Typography size="xs" color="secondary">
<Typography size="xs" color="secondary" className={clsx(timeClassName)}>
{time}
</Typography>
)}

View File

@ -1,3 +1,5 @@
"use client";
import clsx from "clsx";
import { Typography } from "@/components/ui";

View File

@ -11,6 +11,10 @@ export {
default as ChatMessage,
type ChatMessageProps,
} from "./ChatMessage/ChatMessage";
export { default as MessageBubble } from "./ChatMessage/MessageBubble/MessageBubble";
export { default as MessageMeta } from "./ChatMessage/MessageMeta/MessageMeta";
export { default as MessageStatus } from "./ChatMessage/MessageStatus/MessageStatus";
export { default as MessageText } from "./ChatMessage/MessageText/MessageText";
export { default as ChatMessages } from "./ChatMessages/ChatMessages";
export {
default as ChatMessagesWrapper,

View File

@ -16,7 +16,7 @@ interface AdvisersSectionProps {
}
const getChatByAssistantId = (assistantId: string, chats: IChat[]) => {
return chats.find(chat => chat.assistantId === assistantId) || null;
return chats?.find(chat => chat.assistantId === assistantId) || null;
};
const getOptimalColumns = (count: number) => {

View File

@ -0,0 +1,43 @@
.card {
width: 100%;
margin-top: 21px;
background-color: #fff;
box-shadow: 0px 2px 11px 0px #00000024;
padding: 24px 5px 5px 5px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
border-radius: 24px;
& > .header {
display: flex;
gap: 16px;
padding-left: 19px;
padding-right: 10px;
& > .icon-container {
width: 48px;
height: 48px;
border-radius: 16px;
background-color: #fce7f3;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
& > .title {
font-size: 18px;
font-weight: 600;
line-height: 28px;
color: #111827;
}
}
& > .portrait {
max-height: 311px;
object-fit: cover;
border-radius: 24px;
}
}

View File

@ -0,0 +1,69 @@
"use client";
import Image from "next/image";
import { useTranslations } from "next-intl";
import { Typography } from "@/components/ui";
import { useUser } from "@/providers/user-provider";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./DetailedPortraitCard.module.scss";
export default function DetailedPortraitCard() {
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2(
"Landing.what-get.detailed-portrait"
)
);
const { user } = useUser();
const gender = user?.profile?.gender;
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles["icon-container"]}>
<svg
width="20"
height="19"
viewBox="0 0 20 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.8035 13.156C11.7633 13.0189 12.6281 12.474 13.166 11.6478L18.8508 2.85522C19.2938 2.16967 19.1812 1.26264 18.5836 0.703654C17.9859 0.144669 17.0754 0.0954507 16.4215 0.587638L8.1 6.83139C7.25625 7.4642 6.75703 8.45209 6.75 9.50678L10.8035 13.156ZM10.1145 14.049L6.03633 10.3787C3.93398 10.456 2.25 12.1892 2.25 14.3126C2.25 14.4497 2.25703 14.5869 2.27109 14.7205C2.33437 15.3357 1.9125 16.0001 1.29375 16.0001H1.125C0.502734 16.0001 0 16.5029 0 17.1251C0 17.7474 0.502734 18.2501 1.125 18.2501H6.1875C8.36367 18.2501 10.125 16.4888 10.125 14.3126C10.125 14.2247 10.1215 14.1369 10.118 14.049H10.1145Z"
fill="#EC4899"
/>
</svg>
</div>
<Typography as="p" align="left" className={styles.title}>
{t("header.title")}{" "}
<svg
width="13"
height="10"
viewBox="0 0 13 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
display: "inline-block",
}}
>
<path
d="M11.9931 0.631836C12.3349 0.973633 12.3349 1.52871 11.9931 1.87051L4.99307 8.87051C4.65127 9.21231 4.09619 9.21231 3.75439 8.87051L0.254395 5.37051C-0.0874023 5.02871 -0.0874023 4.47363 0.254395 4.13184C0.596191 3.79004 1.15127 3.79004 1.49307 4.13184L4.3751 7.01113L10.7571 0.631836C11.0989 0.290039 11.654 0.290039 11.9958 0.631836H11.9931Z"
fill="#10B981"
/>
</svg>
</Typography>
</div>
<Image
src={emailMarketingCompV2Images(
gender === "male" ? "gpt-portrait-2.jpg" : "gpt-portrait-1.jpg"
)}
alt="portrait"
className={styles.portrait}
width={333}
height={311}
/>
</div>
);
}

View File

@ -0,0 +1,14 @@
.container {
width: 100%;
max-width: 265px;
display: flex;
align-items: center;
gap: 10px;
margin-top: 30px;
& > .text {
font-size: 15px;
line-height: 25px;
color: #000;
}
}

View File

@ -0,0 +1,27 @@
import Image from "next/image";
import { useTranslations } from "next-intl";
import { Typography } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./GuaranteedSecurityPayments.module.scss";
export default function GuaranteedSecurityPayments() {
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2("Landing")
);
return (
<div className={styles.container}>
<Image
src={emailMarketingCompV2Images("guaranteed.svg")}
alt="guaranteed"
width={28}
height={30}
/>
<Typography as="p" align="left" className={styles.text}>
{t("guaranteed-security-payments")}
</Typography>
</div>
);
}

View File

@ -0,0 +1,83 @@
.card {
position: relative;
width: 100%;
margin-top: 30px;
background: linear-gradient(135deg, #a855f7 0%, #4f46e5 70.71%);
box-shadow: 0px 2px 11px 0px #00000024;
padding: 24px 24px 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
border-radius: 24px;
overflow: hidden;
& > .header {
display: flex;
gap: 16px;
& > .icon-container {
width: 48px;
height: 48px;
border-radius: 16px;
background: #ffffff33;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
& > .title {
font-size: 18px;
font-weight: 600;
line-height: 28px;
}
}
& > .list-container {
width: 100%;
padding: 20px 16px 0 16px;
display: flex;
flex-direction: column;
gap: 12px;
background: #ffffff1a;
border-radius: 16px 16px 0 0;
& > .item {
display: flex;
gap: 12px;
& > .number {
background: #ffffff33;
border-radius: 50%;
width: 32px;
height: 32px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 700;
font-size: 14px;
}
& > .item-text {
font-size: 14px;
line-height: 20px;
}
}
}
& > .blur.blur {
position: absolute;
width: 100%;
height: 94px;
bottom: 0;
left: 0;
background: #f8fafc8f;
& > .blur-gradient {
inset: -30px -100px -30px;
}
}
}

View File

@ -0,0 +1,60 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { BlurComponent } from "@/components/widgets";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./GuideCard.module.scss";
export default async function GuideCard() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.what-get.guide")
);
const list = t.raw("list") as Record<string, string>;
const listItems = Object.values(list);
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles["icon-container"]}>
<svg
width="24"
height="22"
viewBox="0 0 24 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.23125 13.081L10.7016 20.9888C11.0531 21.317 11.5172 21.4998 12 21.4998C12.4828 21.4998 12.9469 21.317 13.2984 20.9888L21.7687 13.081C23.1938 11.7545 24 9.89351 24 7.9482V7.67633C24 4.39976 21.6328 1.60601 18.4031 1.06695C16.2656 0.7107 14.0906 1.40914 12.5625 2.93726L12 3.49976L11.4375 2.93726C9.90938 1.40914 7.73438 0.7107 5.59688 1.06695C2.36719 1.60601 0 4.39976 0 7.67633V7.9482C0 9.89351 0.80625 11.7545 2.23125 13.081Z"
fill="#F472B6"
/>
</svg>
</div>
<Typography as="p" align="left" color="white" className={styles.title}>
{t("header.title")}
</Typography>
</div>
<div className={styles["list-container"]}>
{listItems.map((item, index) => (
<div className={styles.item} key={`list-${index}`}>
<div className={styles.number}>{index + 1}</div>
<Typography
as="p"
color="white"
align="left"
className={styles["item-text"]}
>
{item}
</Typography>
</div>
))}
</div>
<BlurComponent
className={styles.blur}
gradientClassName={styles["blur-gradient"]}
isActiveBlur
/>
</div>
);
}

View File

@ -0,0 +1,29 @@
.header {
background: linear-gradient(135deg, #eef2ff 0%, #faf5ff 70.71%);
padding: 26px 0 22px;
width: 100%;
display: flex;
align-items: flex-start;
gap: 8px;
& > .text-container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
& > .title {
font-size: 36px;
font-weight: 700;
line-height: 40px;
color: #111827;
}
& > .description {
font-size: 20px;
font-weight: 500;
line-height: 24px;
color: #4b5563;
}
}
}

View File

@ -0,0 +1,37 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./Header.module.scss";
export default async function Header() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.header")
);
return (
<header className={styles.header}>
<div className={styles["text-container"]}>
<Typography as="h1" align="left" className={styles.title}>
{t("title")}
</Typography>
<Typography as="p" align="left" className={styles.description}>
{t("description")}
</Typography>
</div>
<svg
width="43"
height="42"
viewBox="0 0 43 42"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.2676 5.64375L19.1223 10.5H19.0156H13.1094C11.2965 10.5 9.82812 9.03164 9.82812 7.21875C9.82812 5.40586 11.2965 3.9375 13.1094 3.9375H13.2898C14.5121 3.9375 15.6523 4.58555 16.2676 5.64375ZM5.89062 7.21875C5.89062 8.4 6.17773 9.51562 6.67812 10.5H3.26562C1.81367 10.5 0.640625 11.673 0.640625 13.125V18.375C0.640625 19.827 1.81367 21 3.26562 21H40.0156C41.4676 21 42.6406 19.827 42.6406 18.375V13.125C42.6406 11.673 41.4676 10.5 40.0156 10.5H36.6031C37.1035 9.51562 37.3906 8.4 37.3906 7.21875C37.3906 3.23203 34.1586 0 30.1719 0H29.9914C27.3746 0 24.9465 1.38633 23.6176 3.64219L21.6406 7.01367L19.6637 3.65039C18.3348 1.38633 15.9066 0 13.2898 0H13.1094C9.12266 0 5.89062 3.23203 5.89062 7.21875ZM33.4531 7.21875C33.4531 9.03164 31.9848 10.5 30.1719 10.5H24.2656H24.159L27.0137 5.64375C27.6371 4.58555 28.7691 3.9375 29.9914 3.9375H30.1719C31.9848 3.9375 33.4531 5.40586 33.4531 7.21875ZM3.26562 23.625V38.0625C3.26562 40.2363 5.0293 42 7.20312 42H19.0156V23.625H3.26562ZM24.2656 42H36.0781C38.252 42 40.0156 40.2363 40.0156 38.0625V23.625H24.2656V42Z"
fill="#E7489E"
/>
</svg>
</header>
);
}

View File

@ -0,0 +1,13 @@
.container {
width: calc(100% + 32px);
padding: 14px 31px;
background: #ef4444;
position: sticky;
top: 0;
z-index: 8888;
& > .title {
font-size: 18px;
font-weight: 600;
}
}

View File

@ -0,0 +1,31 @@
"use client";
import { useTranslations } from "next-intl";
import clsx from "clsx";
import { Typography } from "@/components/ui";
import { useTimer } from "@/hooks/timer/useTimer";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./HeaderTimer.module.scss";
type IHeaderTimerProps = React.ComponentProps<"div">;
export default function HeaderTimer({ className }: IHeaderTimerProps) {
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.header-timer")
);
const { time } = useTimer({
initialSeconds: 600,
persist: true,
storageKey: "email-marketing-v2-timer",
});
return (
<div className={clsx(styles.container, className)}>
<Typography as="p" color="white" className={styles.title}>
{t("title", { time })}
</Typography>
</div>
);
}

View File

@ -0,0 +1,98 @@
.container {
display: flex;
flex-direction: column;
gap: 14px;
& > .text-container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding-left: 15px;
& > .title {
font-size: 24px;
font-weight: 600;
line-height: 32px;
color: #111827;
}
& > .description {
font-size: 18px;
font-weight: 500;
line-height: 24px;
color: #4b5563;
}
}
& > .card {
width: 100%;
border-radius: 28px;
padding: 15px 5px 5px;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
background: linear-gradient(135deg, #f37eb7 0%, #9333ea 70.71%);
& > .title {
font-size: 20px;
font-weight: 500;
line-height: 30px;
padding-inline: 18px;
}
& > .image-container {
width: 100%;
height: 220px;
background-size: cover;
background-position: top;
background-repeat: no-repeat;
border-radius: 24px;
box-shadow: 0px 6px 50px 0px #00000040;
display: flex;
align-items: flex-end;
justify-content: space-around;
gap: 9px;
padding: 15px 10px 15px 20px;
& > .avatars {
display: flex;
& > .avatar {
border: 2px solid #fff;
box-shadow: 0px 4px 4px 0px #00000040;
width: 36px;
}
& > .count {
height: 36px;
background-color: #fff;
border-radius: 9999px;
color: #ff6b9d;
font-size: 12px;
font-weight: 700;
padding-inline: 6px;
box-shadow:
0px 4px 6px 0px #0000001a,
0px 2px 4px 0px #0000001a;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
& > *:not(:first-child) {
margin-left: -8px;
}
}
& > .description {
font-size: 14px;
line-height: 20px;
text-shadow: 0px 1px 5px 0px #000000fc;
width: fit-content;
}
}
}
}

View File

@ -0,0 +1,72 @@
import { getTranslations } from "next-intl/server";
import { Typography, UserAvatar } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./HiBlock.module.scss";
export default async function HiBlock() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.hi-block")
);
return (
<div className={styles.container}>
<div className={styles["text-container"]}>
<Typography as="h2" align="left" className={styles.title}>
{t("title")}
</Typography>
<Typography as="p" align="left" className={styles.description}>
{t("description")}
</Typography>
</div>
<div className={styles.card}>
<Typography
as="p"
align="center"
color="white"
className={styles.title}
>
{t("card.title")}
</Typography>
<div
className={styles["image-container"]}
style={{
backgroundImage: `url(${emailMarketingCompV2Images("gpt-portrait.jpg")})`,
}}
>
<div className={styles.avatars}>
<UserAvatar
src={emailMarketingCompV2Images("avatar1.jpg")}
alt="avatar 1"
size="s"
className={styles.avatar}
/>
<UserAvatar
src={emailMarketingCompV2Images("avatar2.jpg")}
alt="avatar 2"
size="s"
className={styles.avatar}
/>
<UserAvatar
src={emailMarketingCompV2Images("avatar3.jpg")}
alt="avatar 3"
size="s"
className={styles.avatar}
/>
<Typography className={styles.count}>{t("card.count")}</Typography>
</div>
<Typography
as="p"
align="left"
color="white"
className={styles.description}
>
{t("card.image-description")}
</Typography>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,97 @@
.card {
width: 100%;
margin-top: 30px;
background-color: #fff;
box-shadow: 0px 2px 11px 0px #00000024;
padding: 24px 5px 5px 5px;
display: flex;
flex-direction: column;
align-items: center;
gap: 21px;
border-radius: 24px;
& > .header {
display: flex;
gap: 16px;
padding-left: 19px;
padding-right: 10px;
& > .icon-container {
width: 48px;
height: 48px;
border-radius: 16px;
background-color: #fce7f3;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
& > .title {
font-size: 18px;
font-weight: 600;
line-height: 28px;
color: #111827;
}
}
& > .messages {
display: flex;
flex-direction: column;
padding-left: 13px;
padding-right: 11px;
gap: 27px;
& > .message-container {
width: fit-content;
display: flex;
flex-direction: column;
max-width: calc(100% - 35px);
gap: 8px;
&.own {
align-items: flex-end;
align-self: flex-end;
margin-left: auto;
}
& > .message {
&.advisor {
background: #efeded;
box-shadow: 0px 1px 5px 0px #00000054;
}
&.own > .meta > .metaTime {
color: #fff;
}
& > .meta {
justify-content: flex-end;
margin-top: -12px;
padding-right: 12px;
padding-bottom: 7px;
}
}
}
}
& > .typingContainer {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
padding-inline: 13px;
padding-bottom: 13px;
margin-top: -6px;
& > .text {
color: #6b7280;
font-size: 14px;
line-height: 16px;
& > svg {
display: inline-block;
}
}
}
}

View File

@ -0,0 +1,108 @@
import { getTranslations } from "next-intl/server";
import clsx from "clsx";
import {
MessageBubble,
MessageMeta,
MessageStatus,
MessageText,
} from "@/components/domains/chat";
import { Typography, UserAvatar } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./IndividualAdviceCard.module.scss";
interface IMessage {
text: string;
time: string;
}
type TMessagesKey = "me" | "advisor";
export default async function IndividualAdviceCard() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2(
"Landing.what-get.individual-advice"
)
);
const messages = t.raw("messages") as Record<TMessagesKey, IMessage>;
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles["icon-container"]}>
<svg
width="22"
height="18"
viewBox="0 0 22 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.15062 12.375C11.1004 12.375 14.3009 9.60469 14.3009 6.1875C14.3009 2.77031 11.1004 0 7.15062 0C3.2008 0 0.000376228 2.77031 0.000376228 6.1875C0.000376228 7.54453 0.505706 8.79961 1.36167 9.82266C1.24136 10.1531 1.0626 10.4449 0.873531 10.691C0.708525 10.909 0.540082 11.0777 0.416328 11.1937C0.354451 11.25 0.302886 11.2957 0.26851 11.3238C0.251322 11.3379 0.237572 11.3484 0.230697 11.352L0.223821 11.359C0.0347524 11.5031 -0.0477504 11.7562 0.0278772 11.9848C0.103505 12.2133 0.313199 12.375 0.550395 12.375C1.2998 12.375 2.05607 12.1781 2.68515 11.9355C3.00142 11.8125 3.29705 11.6754 3.55487 11.5348C4.61022 12.0691 5.83745 12.375 7.15062 12.375ZM15.4009 6.1875C15.4009 10.1355 11.9942 13.1098 7.95846 13.4648C8.7938 16.0805 11.5645 18 14.8509 18C16.164 18 17.3913 17.6941 18.4501 17.1598C18.7079 17.3004 19.0001 17.4375 19.3163 17.5605C19.9454 17.8031 20.7017 18 21.4511 18C21.6883 18 21.9014 17.8418 21.9736 17.6098C22.0458 17.3777 21.9667 17.1246 21.7742 16.9805L21.7674 16.9734C21.7605 16.9664 21.7467 16.9594 21.7295 16.9453C21.6952 16.9172 21.6436 16.875 21.5817 16.8152C21.458 16.6992 21.2895 16.5305 21.1245 16.3125C20.9355 16.0664 20.7567 15.7711 20.6364 15.4441C21.4924 14.4246 21.9977 13.1695 21.9977 11.809C21.9977 8.54648 19.0791 5.87109 15.3768 5.63906C15.3906 5.81836 15.3975 6.00117 15.3975 6.18398L15.4009 6.1875Z"
fill="#60A5FA"
/>
</svg>
</div>
<Typography as="p" align="left" color="white" className={styles.title}>
{t("header.title")}
</Typography>
</div>
<div className={styles.messages}>
{(Object.keys(messages) as TMessagesKey[]).map((key, index) => {
const isOwn = key === "me";
return (
<div
key={`message-${index}`}
className={clsx(styles["message-container"], isOwn && styles.own)}
>
<MessageBubble
isOwn={isOwn}
className={clsx(
styles.message,
!isOwn && styles.advisor,
isOwn && styles.own
)}
>
<MessageText text={messages[key]?.text} isOwn={isOwn} />
<MessageMeta
time={messages[key]?.time}
timeClassName={styles.metaTime}
className={styles.meta}
>
<MessageStatus isRead />
</MessageMeta>
</MessageBubble>
</div>
);
})}
</div>
<div className={styles.typingContainer}>
<UserAvatar
src={emailMarketingCompV2Images("avatar4.jpg")}
alt="avatar 4"
size="s"
className={styles.avatar}
/>
<Typography className={styles.text}>
{t("typing")}
<svg
width="13"
height="4"
viewBox="0 0 13 4"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.21875 1.75C0.21875 1.34389 0.380078 0.954408 0.667243 0.667243C0.954408 0.380078 1.34389 0.21875 1.75 0.21875C2.15611 0.21875 2.54559 0.380078 2.83276 0.667243C3.11992 0.954408 3.28125 1.34389 3.28125 1.75C3.28125 2.15611 3.11992 2.54559 2.83276 2.83276C2.54559 3.11992 2.15611 3.28125 1.75 3.28125C1.34389 3.28125 0.954408 3.11992 0.667243 2.83276C0.380078 2.54559 0.21875 2.15611 0.21875 1.75ZM4.59375 1.75C4.59375 1.34389 4.75508 0.954408 5.04224 0.667243C5.32941 0.380078 5.71889 0.21875 6.125 0.21875C6.53111 0.21875 6.92059 0.380078 7.20776 0.667243C7.49492 0.954408 7.65625 1.34389 7.65625 1.75C7.65625 2.15611 7.49492 2.54559 7.20776 2.83276C6.92059 3.11992 6.53111 3.28125 6.125 3.28125C5.71889 3.28125 5.32941 3.11992 5.04224 2.83276C4.75508 2.54559 4.59375 2.15611 4.59375 1.75ZM10.5 0.21875C10.9061 0.21875 11.2956 0.380078 11.5828 0.667243C11.8699 0.954408 12.0312 1.34389 12.0312 1.75C12.0312 2.15611 11.8699 2.54559 11.5828 2.83276C11.2956 3.11992 10.9061 3.28125 10.5 3.28125C10.0939 3.28125 9.70441 3.11992 9.41724 2.83276C9.13008 2.54559 8.96875 2.15611 8.96875 1.75C8.96875 1.34389 9.13008 0.954408 9.41724 0.667243C9.70441 0.380078 10.0939 0.21875 10.5 0.21875Z"
fill="#6B7280"
/>
</svg>
</Typography>
</div>
</div>
);
}

View File

@ -0,0 +1,26 @@
.buttonContainer {
width: 100%;
display: flex;
justify-content: center;
position: fixed;
bottom: 0;
pointer-events: none;
z-index: 9999;
padding-inline: 8px;
.buttonContinue {
position: relative;
z-index: 1000;
max-width: 358px;
min-height: 62px;
margin-top: 48px;
margin-bottom: 25px;
pointer-events: all;
background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);
& > .text {
font-size: 18px;
font-weight: 500;
}
}
}

View File

@ -0,0 +1,34 @@
"use client";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Button, Typography } from "@/components/ui";
import { BlurComponent } from "@/components/widgets";
import { ROUTES } from "@/shared/constants/client-routes";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./LandingButtonWrapper.module.scss";
export default function LandingButtonWrapper() {
const router = useRouter();
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2("Landing")
);
const handleContinue = () => {
router.push(ROUTES.emailMarketingCompatibilityV2SpecialOffer());
};
return (
<div className={styles.buttonContainer}>
<BlurComponent isActiveBlur={true}>
<Button className={styles.buttonContinue} onClick={handleContinue}>
<Typography color="white" weight="medium" className={styles.text}>
{t("button-continue")}
</Typography>
</Button>
</BlurComponent>
</div>
);
}

View File

@ -0,0 +1,77 @@
.container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 25px 34px;
background: linear-gradient(135deg, #17a34c 0%, #064e3b 70.71%);
border: 2px solid #15803d;
border-radius: 28px;
box-shadow:
0px 10px 15px 0px #0000001a,
0px 4px 6px 0px #0000001a;
margin-top: 30px;
& > .iconContainer {
width: 68px;
height: 68px;
border-radius: 50%;
background: #d9d9d9e5;
display: flex;
align-items: center;
justify-content: center;
}
& > .title {
font-size: 20px;
font-weight: 700;
line-height: 28px;
margin-top: 9px;
}
& > .term {
padding: 8px 13px;
border-radius: 9999px;
background: #d9d9d9e5;
margin-top: 8px;
font-size: 12px;
font-weight: 700;
color: #252525;
}
& > .descriptionContainer {
margin-top: 28px;
width: 100%;
background: #15803d;
border: 1px solid #16a34a;
border-radius: 24px;
box-shadow: 0px 1px 2px 0px #0000000d;
padding: 19px 20px 12px;
& > .description {
font-size: 14px;
font-weight: 300;
line-height: 21px;
color: #f4f4f4;
& > b {
font-weight: 700;
color: #fff;
}
}
}
& > .footer {
margin-top: 18px;
display: grid;
grid-template-columns: 16px 1fr;
gap: 8px;
& > .text {
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: #bbf7d0;
}
}
}

View File

@ -0,0 +1,51 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import GuaranteeIcon from "./guarantee.svg";
import styles from "./MoneyBackGuarantee.module.scss";
export default async function MoneyBackGuarantee() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.money-back-guarantee")
);
return (
<div className={styles.container}>
<div className={styles.iconContainer}>
<GuaranteeIcon />
</div>
<Typography as="h3" color="white" className={styles.title}>
{t("title")}
</Typography>
<Typography as="p" className={styles.term}>
{t("term")}
</Typography>
<div className={styles.descriptionContainer}>
<Typography as="p" className={styles.description}>
{t.rich("description", {
bold: chunks => <b>{chunks}</b>,
})}
</Typography>
</div>
<div className={styles.footer}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.84344 15.1571 5.87827 16 8 16ZM11.5312 6.53125L7.53125 10.5312C7.2375 10.825 6.7625 10.825 6.47188 10.5312L4.47188 8.53125C4.17813 8.2375 4.17813 7.7625 4.47188 7.47188C4.76562 7.18125 5.24062 7.17813 5.53125 7.47188L7 8.94063L10.4688 5.46875C10.7625 5.175 11.2375 5.175 11.5281 5.46875C11.8187 5.7625 11.8219 6.2375 11.5281 6.52812L11.5312 6.53125Z"
fill="#BBF7D0"
/>
</svg>
<Typography as="p" className={styles.text}>
{t("footer")}
</Typography>
</div>
</div>
);
}

View File

@ -0,0 +1,24 @@
<svg width="48" height="52" viewBox="0 0 48 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_334_11403)">
<mask id="mask0_334_11403" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="9" y="7" width="30" height="34">
<path d="M9 7H39V41H9V7Z" fill="white"/>
</mask>
<g mask="url(#mask0_334_11403)">
<path d="M31.9018 8.69077C28.9812 8.69077 26.1857 8.0912 24 7C21.815 8.0912 19.0192 8.69077 16.0982 8.69077C13.5251 8.69077 11.0479 8.22515 9 7.36765V21.4806C9 30.6662 15.7177 39.3919 24 40.9996C32.2827 39.3919 39 30.6662 39 21.4806V7.36765C36.9525 8.22515 34.4753 8.69077 31.9018 8.69077Z" fill="#00AE37"/>
</g>
<path d="M21.056 29.652L14.8071 23.497L16.6796 21.6527L21.056 25.9633L31.3208 15.8525L33.1933 17.6969L21.056 29.652Z" fill="white"/>
<path d="M29.9952 38.4586C34.8143 35.1964 38.3144 29.4696 38.9098 23.3282L33.1929 17.6973L21.0557 29.6524L29.9952 38.4586Z" fill="#008E28"/>
</g>
<defs>
<filter id="filter0_d_334_11403" x="0" y="0" width="48" height="52" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="4.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_334_11403"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_334_11403" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,8 @@
.container {
width: 100%;
margin: 20px auto 0;
& > img {
width: 100%;
}
}

View File

@ -0,0 +1,20 @@
import Image from "next/image";
import {
emailMarketingCompV2Images,
} from "@/shared/constants/images";
import styles from "./Payments.module.scss";
export default function Payments() {
return (
<div className={styles.container}>
<Image
src={emailMarketingCompV2Images("payments.png")}
alt="payments"
width={460}
height={215}
/>
</div>
);
}

View File

@ -0,0 +1,48 @@
.container {
width: 100%;
margin-top: 41px;
& > .title {
font-size: 18px;
font-weight: 600;
color: #111827;
}
& > .list {
width: calc(100% - 48px);
margin-inline: auto;
margin-top: 16px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
& > .item {
display: grid;
grid-template-columns: 13px 1fr;
gap: 17px;
& > svg {
margin-top: 12px;
}
& > .textContainer {
display: flex;
flex-direction: column;
& > .title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
color: #374151;
}
& > .text {
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: #374151;
}
}
}
}
}

View File

@ -0,0 +1,55 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./PlanAlsoIncludes.module.scss";
interface IListItem {
title: string;
text: string;
}
export default async function PlanAlsoIncludes() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.plan-also-includes")
);
const listItems = Object.values(
t.raw("list.items") as Record<string, IListItem>
);
return (
<div className={styles.container}>
<Typography as="h2" align="left" className={styles.title}>
{t("title")}
</Typography>
<ul className={styles.list}>
{listItems.map((item, index) => (
<li key={`item-${index}`} className={styles.item}>
<svg
width="13"
height="9"
viewBox="0 0 13 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9931 0.256348C12.3349 0.598145 12.3349 1.15322 11.9931 1.49502L4.99307 8.49502C4.65127 8.83682 4.09619 8.83682 3.75439 8.49502L0.254395 4.99502C-0.0874023 4.65322 -0.0874023 4.09814 0.254395 3.75635C0.596191 3.41455 1.15127 3.41455 1.49307 3.75635L4.3751 6.63564L10.7571 0.256348C11.0989 -0.0854492 11.654 -0.0854492 11.9958 0.256348H11.9931Z"
fill="#10B981"
/>
</svg>
<div className={styles.textContainer}>
<Typography align="left" as="p" className={styles.title}>
{item.title}
</Typography>
<Typography align="left" as="p" className={styles.text}>
{item.text}
</Typography>
</div>
</li>
))}
</ul>
</div>
);
}

View File

@ -0,0 +1,117 @@
.container {
width: 100%;
max-width: 300px;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 19px;
margin-inline: auto;
}
.oldPriceContainer {
position: relative;
// width: fit-content;
width: 128px;
aspect-ratio: 1 / 1;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
&::before {
background: linear-gradient(135deg, #d1d5db 0%, #767b83 70.71%);
border: 6px solid #ffffff;
border-radius: 24px;
box-shadow: 0px 4px 6px 0px #0000001a;
content: "";
position: absolute;
inset: 0;
z-index: 0;
opacity: 0.6;
}
& * {
z-index: 1;
}
& > .discount {
position: absolute;
top: -15px;
right: -8px;
padding: 6px;
padding-inline: 8px;
background-color: #ef4444;
border-radius: 9999px;
font-size: 16px;
font-weight: 700;
color: #fff;
}
& > .oldPrice {
font-size: 24px;
font-weight: 700;
text-decoration: line-through;
}
& > .oldPriceDescription {
font-size: 14px;
font-weight: 600;
line-height: 16px;
}
}
.newPriceContainer {
position: relative;
// width: fit-content;
width: 128px;
aspect-ratio: 1 / 1;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
&::before {
background: linear-gradient(90deg, #ffc7f3 0%, #c590ff 100%);
border-radius: 28px;
content: "";
position: absolute;
inset: -8px;
z-index: 0;
opacity: 0.75;
}
&::after {
content: "";
position: absolute;
inset: 0px;
z-index: 0;
background: linear-gradient(
135deg,
#ec4899 0%,
#9333ea 35.36%,
#4338ca 70.71%
);
box-shadow: 0px 2px 7px 0px #000000ad;
border-radius: 24px;
}
& * {
z-index: 1;
}
& > .newPrice {
font-size: 36px;
font-weight: 900;
}
& > .newPriceDescription {
font-size: 16px;
font-weight: 700;
line-height: 20px;
color: #ffffffe5;
}
}

View File

@ -0,0 +1,52 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import { getFormattedPrice } from "@/shared/utils/price";
import { Currency } from "@/types";
import styles from "./Prices.module.scss";
interface IPricesProps {
oldPrice: number;
newPrice: number;
currency: Currency;
}
const computeDiscount = (oldPrice: number, newPrice: number) => {
return Math.ceil(((oldPrice - newPrice) / oldPrice) * 100);
};
export default async function Prices({
oldPrice,
newPrice,
currency,
}: IPricesProps) {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.special-offer.prices")
);
return (
<div className={styles.container}>
<div className={styles.oldPriceContainer}>
<div className={styles.discount}>
-{computeDiscount(oldPrice, newPrice)}%
</div>
<Typography color="white" className={styles.oldPrice}>
{getFormattedPrice(oldPrice, currency)}
</Typography>
<Typography color="white" className={styles.oldPriceDescription}>
{t("old-price.description")}
</Typography>
</div>
<div className={styles.newPriceContainer}>
<Typography color="white" className={styles.newPrice}>
{getFormattedPrice(newPrice, currency)}
</Typography>
<Typography color="white" className={styles.newPriceDescription}>
{t("new-price.description")}
</Typography>
</div>
</div>
);
}

View File

@ -0,0 +1,124 @@
.container {
display: flex;
align-items: center;
flex-direction: column;
width: 100%;
& > .title {
margin-top: 8px;
font-size: 18px;
font-weight: 700;
line-height: 28px;
color: #22c55e;
}
& > .description {
max-width: 327px;
font-size: 12px;
line-height: 20px;
margin-top: 20px;
text-align: center;
color: #6b7280;
}
}
.table {
background: #ededed;
border-radius: 24px;
padding: 12px 16px;
width: 100%;
margin-top: 18px;
display: flex;
flex-direction: column;
gap: 8px;
& > .description {
font-size: 14px;
line-height: 20px;
color: #6b7280;
max-width: 196px;
}
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 12px 32px 12px 17px;
background: #ffffff;
box-shadow: 0px 1px 2px 0px #0000000d;
border-radius: 20px;
& > .label {
font-size: 18px;
font-weight: 500;
color: #111827;
}
}
.totalRow {
& > .price {
font-size: 24px;
font-weight: 700;
line-height: 32px;
color: #111827;
}
}
.codeRow {
& > .prices {
display: flex;
align-items: center;
gap: 16px;
& > .oldPrice {
font-size: 18px;
line-height: 28px;
text-decoration: line-through;
color: #9ca3af;
}
& > .price {
font-size: 18px;
font-weight: 700;
line-height: 32px;
color: #111827;
}
}
}
.costRow {
background-color: #ededed;
border-radius: 32px;
font-size: 17px;
line-height: 125%;
margin-top: 17px;
.label {
font-size: 14px;
font-weight: 500;
max-width: 60%;
line-height: 125%;
}
.prices {
display: flex;
gap: 14px;
align-items: flex-end;
}
.originalPrice {
font-size: 13px;
text-decoration: line-through;
color: #5a5a5a;
line-height: 125%;
font-weight: 500;
}
.discountedPrice {
font-weight: 700;
font-size: 17px;
line-height: 125%;
}
}

View File

@ -0,0 +1,90 @@
import { getTranslations } from "next-intl/server";
import clsx from "clsx";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import { getPeriodTextServer } from "@/shared/utils/period-server";
import { getFormattedPrice } from "@/shared/utils/price";
import { Currency } from "@/types";
import { PeriodType } from "@/types/period";
import styles from "./PricingSummary.module.scss";
interface PricingSummaryProps {
trialPrice: number;
trialInterval: number;
trialPeriod: PeriodType;
price: number;
billingInterval: number;
billingPeriod: PeriodType;
currency: Currency;
}
export default async function PricingSummary({
trialPrice,
trialInterval,
trialPeriod,
price,
billingInterval,
billingPeriod,
currency,
}: PricingSummaryProps) {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("SpecialOffer")
);
return (
<div className={styles.container}>
<div className={styles.table}>
<div className={clsx(styles.row, styles.totalRow)}>
<span className={styles.label}>
{t("pricing-summary-total-today")}
</span>
<span className={styles.price}>
{getFormattedPrice(Number(trialPrice), currency)}
</span>
</div>
<div className={clsx(styles.row, styles.codeRow)}>
<span className={styles.label}>
{t("pricing-summary-code-applied")}
</span>
<div className={styles.prices}>
<span className={styles.oldPrice}>
{getFormattedPrice(Number(trialPrice), currency)}
</span>
<span className={styles.price}>
{getFormattedPrice(Number(trialPrice), currency)}
</span>
</div>
</div>
<Typography as="p" align="left" className={styles.description}>
{t("cost-for-one-day", {
cost: getFormattedPrice(price / billingInterval, currency),
})}
</Typography>
</div>
<Typography className={styles.title}>
{t("save-every-period", {
price: getFormattedPrice(price - trialPrice, currency),
})}
</Typography>
<p className={styles.description}>
{t("pricing-summary-trial-description", {
trialPrice: getFormattedPrice(trialPrice, currency),
trialPeriod: await getPeriodTextServer(
trialPeriod,
trialInterval,
"period_adjective"
),
price: getFormattedPrice(price, currency),
billingPeriod: await getPeriodTextServer(
billingPeriod,
billingInterval
),
})}
</p>
</div>
);
}

View File

@ -0,0 +1,51 @@
.container {
background: linear-gradient(
90deg,
rgba(236, 72, 153, 0.1) 0%,
rgba(168, 85, 247, 0.1) 100%
);
width: 100%;
padding: 16px 11px 8px 9px;
border-radius: 28px;
margin-top: 21px;
display: flex;
flex-direction: column;
gap: 16px;
& > .title {
padding-inline: 7px;
font-size: 16px;
font-weight: 500;
line-height: 20px;
color: #374151;
& > .indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #4ade80;
display: inline-block;
margin-right: 8px;
}
}
& > .content {
display: grid;
grid-template-columns: 36px 1fr fit-content(80px);
align-items: start;
& > .text {
font-size: 14px;
line-height: 16px;
color: #4b5563;
padding-left: 11px;
padding-right: 6px;
}
& > .time {
font-size: 12px;
line-height: 16px;
color: #22c55e;
}
}
}

View File

@ -0,0 +1,35 @@
import { getTranslations } from "next-intl/server";
import { Typography, UserAvatar } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./RealTimeActivity.module.scss";
export default async function RealTimeActivity() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.real-time-activity")
);
return (
<div className={styles.container}>
<Typography as="h4" align="left" className={styles.title}>
<div className={styles.indicator} />
{t("title")}
</Typography>
<div className={styles.content}>
<UserAvatar
src={emailMarketingCompV2Images("real-time-activity/avatar.jpg")}
alt="avatar"
size="sm"
/>
<Typography as="p" align="left" className={styles.text}>
{t.rich("text", {
bold: chunks => <b>{chunks}</b>,
})}
</Typography>
<Typography className={styles.time}>{t("time")}</Typography>
</div>
</div>
);
}

View File

@ -0,0 +1,98 @@
.container {
width: 100%;
border-radius: 24px;
padding: 26px 40px 24px 24px;
background-color: #fff;
& > .text {
margin-top: 16px;
font-size: 14px;
line-height: 20px;
color: #4b5563;
}
}
.header {
display: grid;
grid-template-columns: 48px 1fr;
align-items: center;
gap: 12px;
& > .avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
& > .avatar-chars {
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #e37fd9;
color: #0f1323;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
text-transform: uppercase;
}
& > .info {
display: flex;
flex-direction: column;
gap: 6px;
& > .name {
font-size: 18px;
font-weight: 600;
line-height: 28px;
color: #111827;
& > .flag {
font-size: 18px;
line-height: 28px;
margin-left: 7px;
}
}
& > .stars {
display: flex;
flex-direction: row;
align-items: center;
gap: 3px;
& > img {
width: 14px;
}
}
}
}
.answer {
width: 100%;
padding: 12px 16px;
background-color: #eff6ff;
border-left: 4px solid #60a5fa;
border-radius: 0 12px 12px 0;
margin-top: 16px;
& > .title {
font-size: 12px;
font-weight: 600;
line-height: 16px;
color: #1d4ed8;
& > svg {
display: inline-block;
margin-right: 8px;
}
}
& > .text {
margin-top: 4px;
font-size: 12px;
line-height: 16px;
color: #2563eb;
}
}

View File

@ -0,0 +1,92 @@
import Image from "next/image";
import { Typography } from "@/components/ui";
import styles from "./Review.module.scss";
export interface IReviewProps {
username: string;
stars?: number;
avatar: string;
text: string;
flag?: string;
answer?: {
title: string;
text: string;
};
}
export default function Review({
username,
stars = 5,
avatar,
text,
flag,
answer,
}: IReviewProps) {
return (
<div className={`${styles.container}`}>
<div className={styles.header}>
{!!avatar?.length && (
<Image
src={avatar}
alt="Avatar"
className={styles.avatar}
width={48}
height={48}
/>
)}
{!avatar?.length && (
<div className={styles["avatar-chars"]}>{username.slice(0, 2)}</div>
)}
<div className={styles.info}>
<Typography as="p" align="left" className={styles.name}>
{username}
<Typography className={styles.flag}>{flag}</Typography>
</Typography>
<div className={styles.stars}>
{Array.from({ length: stars }).map((_, index) => (
<svg
width="15"
height="14"
viewBox="0 0 15 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
key={`star-${index}`}
>
<path
d="M8.00506 0.492188C7.86014 0.191406 7.55389 0 7.21756 0C6.88123 0 6.57771 0.191406 6.43006 0.492188L4.67186 4.10977L0.745293 4.68945C0.417168 4.73867 0.143731 4.96836 0.0425591 5.28281C-0.0586128 5.59727 0.0234183 5.94453 0.258575 6.17695L3.10779 8.99609L2.43514 12.9801C2.38045 13.3082 2.51717 13.6418 2.78787 13.8359C3.05857 14.0301 3.41678 14.0547 3.71209 13.8988L7.22029 12.0258L10.7285 13.8988C11.0238 14.0547 11.382 14.0328 11.6527 13.8359C11.9234 13.6391 12.0601 13.3082 12.0055 12.9801L11.3301 8.99609L14.1793 6.17695C14.4144 5.94453 14.4992 5.59727 14.3953 5.28281C14.2914 4.96836 14.0207 4.73867 13.6926 4.68945L9.76326 4.10977L8.00506 0.492188Z"
fill="#FACC15"
/>
</svg>
))}
</div>
</div>
</div>
<Typography as="p" size="sm" align="left" className={styles.text}>
{text}
</Typography>
<div className={styles.answer}>
<Typography as="h4" align="left" className={styles.title}>
<svg
width="12"
height="11"
viewBox="0 0 12 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.80469 0.0660972C5.07422 0.185628 5.25 0.45516 5.25 0.750472V2.25047H7.875C10.1531 2.25047 12 4.09735 12 6.37547C12 9.03094 10.0898 10.2169 9.65156 10.4559C9.59297 10.4888 9.52734 10.5005 9.46172 10.5005C9.20625 10.5005 9 10.2919 9 10.0388C9 9.86297 9.10078 9.70125 9.22969 9.58172C9.45 9.37547 9.75 8.96297 9.75 8.25282C9.75 7.01063 8.74219 6.00282 7.5 6.00282H5.25V7.50282C5.25 7.79813 5.07656 8.06766 4.80469 8.18719C4.53281 8.30672 4.21875 8.2575 3.99844 8.06063L0.248438 4.68563C0.0914063 4.54032 0 4.33875 0 4.12547C0 3.91219 0.0914063 3.71063 0.248438 3.56766L3.99844 0.19266C4.21875 -0.00655905 4.53516 -0.0557778 4.80469 0.0660972Z"
fill="#3B82F6"
/>
</svg>
{answer?.title}
</Typography>
<Typography as="p" align="left" className={styles.text}>
{answer?.text}
</Typography>
</div>
</div>
);
}

View File

@ -0,0 +1,20 @@
.container {
width: 100%;
margin-top: 53px;
& > .title {
font-size: 24px;
font-weight: 700;
line-height: 32px;
color: #111827;
}
& > .reviews {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
margin-top: 24px;
}
}

View File

@ -0,0 +1,53 @@
import { useTranslations } from "next-intl";
import { Typography } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import Review, { IReviewProps } from "../Review/Review";
import styles from "./Reviews.module.scss";
type IReviewFromTranslate = Omit<IReviewProps, "stars" | "avatar">;
type ILocalReview = Pick<IReviewProps, "stars" | "avatar">;
export default function Reviews() {
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.reviews")
);
const reviewsFromTranslate = Object.values(
t.raw("items") as Record<string, IReviewFromTranslate>
);
const reviews: ILocalReview[] = [
{
avatar: emailMarketingCompV2Images("anna_smith28.png"),
},
{
avatar: emailMarketingCompV2Images("mike_andrews_89.png"),
},
{
avatar: emailMarketingCompV2Images("emily_harris_ang.png"),
},
];
const mergedReviews: IReviewProps[] = reviewsFromTranslate.map((item, i) => ({
...item,
...(reviews[i] || {}),
}));
return (
<div className={styles.container}>
<Typography as="h2" align="left" className={styles.title}>
{t("title")}
</Typography>
<div className={styles.reviews}>
{mergedReviews.map((review, index) => (
<Review {...review} key={index} />
))}
</div>
</div>
);
}

View File

@ -0,0 +1,151 @@
.card {
width: 100%;
margin-top: 21px;
background: linear-gradient(135deg, #faf5ff 0%, #eef2ff 70.71%);
box-shadow: 0px 2px 11px 0px #00000024;
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
border-radius: 24px;
& > .header {
display: flex;
gap: 16px;
padding-inline: 9px;
& > .icon-container {
width: 40px;
height: 40px;
border-radius: 16px;
background-color: #fce7f3;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 21px;
line-height: 28px;
font-weight: 600;
}
& > .title {
font-size: 18px;
font-weight: 600;
line-height: 28px;
color: #111827;
}
}
& > .content {
width: 100%;
background-color: #fff;
border-radius: 24px;
padding: 19px 16px 32px;
display: flex;
flex-direction: column;
align-items: center;
& > .partners {
display: grid;
grid-template-columns: 1fr 20px 1fr;
justify-items: center;
align-items: start;
justify-content: center;
gap: 12px;
& > .partner {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
& > .avatar {
width: 64px;
height: 64px;
flex-shrink: 0;
border: solid 2px #e9d5ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 500;
color: #6b7280;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
}
}
& > .percent {
font-size: 25px;
font-weight: 700;
color: #ec4899;
margin-top: 32px;
}
& > .compatibility {
font-size: 14px;
font-weight: 700;
color: #ec4899;
margin-top: 8px;
}
& > .progress {
width: 100%;
height: 8px;
border-radius: 1000px;
background-color: #f3e8ff;
margin-top: 12px;
overflow: hidden;
& > div {
width: 92%;
height: 100%;
border-radius: 1000px;
background-color: #ec4899;
}
}
& > .bars {
width: 100%;
max-width: 200px;
display: flex;
justify-content: space-between;
gap: 12px;
margin-top: 26px;
& > .barContainer {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
& > .progressBarContainer {
position: relative;
width: 46px;
height: 46px;
& > .value {
font-size: 12px;
font-weight: 600;
line-height: 16px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
& > .barName {
font-size: 12px;
font-weight: 600;
line-height: 21px;
color: #5a5858;
}
}
}
}
}

View File

@ -0,0 +1,122 @@
"use client";
import { CircularProgressbar } from "react-circular-progressbar";
import { useTranslations } from "next-intl";
import { Typography } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./SearchCompatiblePartnerCard.module.scss";
// TODO
const username = "@annAgejsdbvjsbdv";
interface IBar {
text: string;
percent: string;
colors: {
path: string;
trail: string;
};
}
export default function SearchCompatiblePartnerCard() {
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2(
"Landing.what-get.search-compatible-partner"
)
);
const bars = t.raw("content.bars") as Record<string, IBar>;
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles["icon-container"]}>💞</div>
<Typography as="p" align="left" className={styles.title}>
{t("header.title")}
</Typography>
</div>
<div className={styles.content}>
<div className={styles.partners}>
<div className={styles.partner}>
<div className={styles.avatar}>
{username.charAt(1).toUpperCase()}
</div>
<Typography as="p" className={styles.name}>
{username.slice(0, 7)}...
</Typography>
</div>
<svg
width="21"
height="18"
viewBox="0 0 21 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
marginTop: "68px",
}}
>
<path
d="M2.0625 10.2343L9.12109 16.8242C9.41406 17.0976 9.80078 17.25 10.2031 17.25C10.6055 17.25 10.9922 17.0976 11.2852 16.8242L18.3438 10.2343C19.5312 9.12887 20.2031 7.57809 20.2031 5.957V5.73043C20.2031 2.99997 18.2305 0.67184 15.5391 0.222621C13.7578 -0.0742537 11.9453 0.507778 10.6719 1.78122L10.2031 2.24997L9.73438 1.78122C8.46094 0.507778 6.64844 -0.0742537 4.86719 0.222621C2.17578 0.67184 0.203125 2.99997 0.203125 5.73043V5.957C0.203125 7.57809 0.875 9.12887 2.0625 10.2343Z"
fill="#EC4899"
/>
</svg>
<div className={styles.partner}>
<div
className={styles.avatar}
style={{
backgroundImage: `url(${emailMarketingCompV2Images("empty-face-portrait.jpg")})`,
}}
>
?
</div>
<Typography as="p" className={styles.name}>
{t("content.partner-name")}
</Typography>
</div>
</div>
<Typography className={styles.percent}>
{t("content.percent")}
</Typography>
<Typography className={styles.compatibility}>
{t("content.compatibility")}
</Typography>
<div className={styles.progress}>
<div />
</div>
<div className={styles.bars}>
{Object.values(bars).map((bar, index) => (
<div key={`bar-${index}`} className={styles.barContainer}>
<div className={styles.progressBarContainer}>
<CircularProgressbar
className={styles.progressBar}
styles={{
path: { stroke: bar.colors.path },
trail: { stroke: bar.colors.trail },
}}
maxValue={100}
minValue={0}
value={Number(bar.percent)}
strokeWidth={8}
/>
<Typography
className={styles.value}
style={{
color: bar.colors.path,
}}
>
{bar.percent}%
</Typography>
</div>
<Typography className={styles.barName}>{bar.text}</Typography>
</div>
))}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
.container {
width: 100%;
margin-top: 30px;
& > .title {
width: 100%;
padding-left: 6px;
font-size: 24px;
font-weight: 700;
line-height: 32px;
}
}

View File

@ -0,0 +1,50 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { IFunnelPaymentVariant } from "@/entities/session/funnel/types";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import { Currency } from "@/types";
import { PeriodType } from "@/types/period";
import Prices from "../Prices/Prices";
import TrialIntervalOffer from "../TrialIntervalOffer/TrialIntervalOffer";
import styles from "./SpecialOffer.module.scss";
interface ISpecialOfferProps {
variant: IFunnelPaymentVariant;
currency: Currency;
oldTrialInterval: number;
newTrialInterval: number;
trialPeriod: PeriodType;
}
export default async function SpecialOffer({
variant,
currency,
oldTrialInterval,
newTrialInterval,
trialPeriod,
}: ISpecialOfferProps) {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.special-offer")
);
return (
<div className={styles.container}>
<Typography as="h2" align="left" className={styles.title}>
{t("title")}
</Typography>
<Prices
oldPrice={1499}
newPrice={variant?.trialPrice || 0}
currency={currency}
/>
<TrialIntervalOffer
oldTrialInterval={oldTrialInterval}
newTrialInterval={newTrialInterval}
periodType={trialPeriod}
/>
</div>
);
}

View File

@ -0,0 +1,32 @@
.buttonContainer {
width: 100%;
display: flex;
justify-content: center;
position: sticky;
bottom: calc(0dvh + 25px);
pointer-events: none;
z-index: 1000;
.button {
position: relative;
z-index: 1000;
max-width: 327px;
margin-top: 24px;
pointer-events: all;
background-color: #3b82f6;
border-radius: 16px;
padding-block: 21px;
& > .text {
font-size: 18px;
font-weight: 700;
letter-spacing: 0.9px;
}
}
}
.gradientBlur.gradientBlur {
left: -16px !important;
right: -16px !important;
inset: -25px 0 !important;
}

View File

@ -0,0 +1,53 @@
"use client";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Button, Typography } from "@/components/ui";
import { BlurComponent } from "@/components/widgets";
import { ROUTES } from "@/shared/constants/client-routes";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./SpecialOfferButtonWrapper.module.scss";
interface SpecialOfferButtonWrapperProps {
productId: string;
placementId: string;
paywallId: string;
}
export default function SpecialOfferButtonWrapper({
productId,
placementId,
paywallId,
}: SpecialOfferButtonWrapperProps) {
const router = useRouter();
const t = useTranslations(
translatePathEmailMarketingCompatibilityV2("SpecialOffer")
);
const openPaymentModal = () => {
router.push(
ROUTES.payment({
productId,
placementId,
paywallId,
})
);
};
return (
<div className={styles.buttonContainer}>
<BlurComponent
isActiveBlur={true}
gradientClassName={styles.gradientBlur}
>
<Button className={styles.button} onClick={openPaymentModal}>
<Typography color="white" className={styles.text}>
{t("button-continue")}
</Typography>
</Button>
</BlurComponent>
</div>
);
}

View File

@ -0,0 +1,125 @@
.container {
width: 100%;
margin-top: 42px;
display: flex;
flex-direction: column;
align-items: center;
& > .title {
background-image: linear-gradient(
117.07deg,
#d041b4 12.17%,
#ae3ad2 71.63%
);
color: transparent;
background-clip: text;
font-size: 48px;
font-weight: 700;
}
& > .description {
margin-top: 5px;
font-size: 19px;
font-weight: 500;
line-height: 28px;
color: #000;
max-width: 280px;
}
& > .avatars {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16px;
& > .avatar {
width: 48px;
height: 48px;
flex-shrink: 0;
border-radius: 50%;
object-fit: cover;
&:not(:first-child) {
margin-left: -12px;
}
}
& > .more {
width: 48px;
height: 48px;
flex-shrink: 0;
border-radius: 50%;
background: linear-gradient(135deg, #f472b6 0%, #a855f7 70.71%);
display: flex;
align-items: center;
justify-content: center;
margin-left: -12px;
& > .moreText {
font-size: 12px;
font-weight: 700;
line-height: 16px;
}
}
}
& > .periodStatistics {
width: 100%;
display: flex;
justify-content: center;
gap: 20px;
margin-top: 36px;
& .iconContainer {
width: 56px;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
}
& .title {
font-size: 24px;
font-weight: 700;
line-height: 32px;
color: #111827;
margin-top: 4px;
}
& .description {
font-size: 16px;
font-weight: 500;
line-height: 20px;
color: #4b5563;
}
& > .monthContainer {
display: flex;
flex-direction: column;
align-items: center;
& > .iconContainer {
background: linear-gradient(
135deg,
rgba(236, 72, 153, 0.1) 0%,
rgba(236, 72, 153, 0.2) 70.71%
);
}
}
& > .todayContainer {
display: flex;
flex-direction: column;
align-items: center;
& > .iconContainer {
background: linear-gradient(
135deg,
rgba(168, 85, 247, 0.1) 0%,
rgba(168, 85, 247, 0.2) 70.71%
);
}
}
}
}

View File

@ -0,0 +1,92 @@
import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { emailMarketingCompV2Images } from "@/shared/constants/images";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import styles from "./Statistics.module.scss";
const avatarsUrl = Array.from({ length: 6 }, (_, i) =>
emailMarketingCompV2Images(`statistics/avatar${i + 1}.png`)
);
export default async function Statistics() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.statistics")
);
return (
<div className={styles.container}>
<Typography as="h2" className={styles.title}>
{t("title")}
</Typography>
<Typography as="p" className={styles.description}>
{t("description")}
</Typography>
<div className={styles.avatars}>
{avatarsUrl.map((url, index) => (
<Image
key={`avatar-${index}`}
src={url}
alt={`avatar-${index}`}
width={48}
height={48}
className={styles.avatar}
/>
))}
<div className={styles.more}>
<Typography className={styles.moreText} color="white">
{t("more-avatars")}
</Typography>
</div>
</div>
<div className={styles.periodStatistics}>
<div className={styles.monthContainer}>
<div className={styles.iconContainer}>
<svg
width="20"
height="18"
viewBox="0 0 20 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.85938 10.0879L8.91797 16.6777C9.21094 16.9511 9.59766 17.1035 10 17.1035C10.4023 17.1035 10.7891 16.9511 11.082 16.6777L18.1406 10.0879C19.3281 8.98239 20 7.43161 20 5.81051V5.58395C20 2.85348 18.0273 0.525356 15.3359 0.0761369C13.5547 -0.220738 11.7422 0.361293 10.4688 1.63473L10 2.10348L9.53125 1.63473C8.25781 0.361293 6.44531 -0.220738 4.66406 0.0761369C1.97266 0.525356 0 2.85348 0 5.58395V5.81051C0 7.43161 0.671875 8.98239 1.85938 10.0879Z"
fill="#EC4899"
/>
</svg>
</div>
<Typography as="h4" className={styles.title}>
{t("period-statistics.month.title")}
</Typography>
<Typography as="p" className={styles.description}>
{t("period-statistics.month.text")}
</Typography>
</div>
<div className={styles.todayContainer}>
<div className={styles.iconContainer}>
<svg
width="16"
height="18"
viewBox="0 0 16 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.57143 0C5.20357 0 5.71429 0.502734 5.71429 1.125V2.25H10.2857V1.125C10.2857 0.502734 10.7964 0 11.4286 0C12.0607 0 12.5714 0.502734 12.5714 1.125V2.25H14.2857C15.2321 2.25 16 3.00586 16 3.9375V5.625H0V3.9375C0 3.00586 0.767857 2.25 1.71429 2.25H3.42857V1.125C3.42857 0.502734 3.93929 0 4.57143 0ZM0 6.75H16V16.3125C16 17.2441 15.2321 18 14.2857 18H1.71429C0.767857 18 0 17.2441 0 16.3125V6.75ZM2.85714 9C2.54286 9 2.28571 9.25313 2.28571 9.5625V12.9375C2.28571 13.2469 2.54286 13.5 2.85714 13.5H6.28571C6.6 13.5 6.85714 13.2469 6.85714 12.9375V9.5625C6.85714 9.25313 6.6 9 6.28571 9H2.85714Z"
fill="#8C5EB4"
/>
</svg>
</div>
<Typography as="h4" className={styles.title}>
{t("period-statistics.today.title")}
</Typography>
<Typography as="p" className={styles.description}>
{t("period-statistics.today.text")}
</Typography>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,55 @@
.container {
padding: 20px;
padding-top: 30px;
width: 100%;
background: linear-gradient(90deg, #f0fdf4 0%, #ecfdf5 100%);
border: 1px solid #3ace70;
border-radius: 24px;
margin-top: 32px;
& > .title {
font-size: 18px;
font-weight: 700;
line-height: 28px;
color: #15803d;
& > svg {
display: inline-block;
margin-right: 13px;
margin-left: -16px;
}
}
& > .trialIntervals {
margin-top: 16px;
vertical-align: middle;
& > .oldInterval {
font-size: 18px;
font-weight: 700;
line-height: 28px;
text-decoration: line-through;
color: #16a34a80;
text-transform: uppercase;
}
& > svg {
display: inline-block;
margin-inline: 8px;
}
& > .newInterval {
font-size: 24px;
font-weight: 900;
color: #16a34a;
text-transform: uppercase;
}
}
& > .description {
margin-top: 12px;
font-size: 14px;
line-height: 20px;
color: #16a34a;
}
}

View File

@ -0,0 +1,70 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import { getPeriodTextServer } from "@/shared/utils/period-server";
import { PeriodType } from "@/types/period";
import styles from "./TrialIntervalOffer.module.scss";
interface ITrialIntervalOfferProps {
periodType: PeriodType;
oldTrialInterval: number;
newTrialInterval: number;
}
export default async function TrialIntervalOffer({
periodType,
oldTrialInterval,
newTrialInterval,
}: ITrialIntervalOfferProps) {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2(
"Landing.special-offer.trial-offer"
)
);
return (
<div className={styles.container}>
<Typography as="h3" className={styles.title}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.95312 2.15L7.04063 4H7H4.75C4.05937 4 3.5 3.44062 3.5 2.75C3.5 2.05938 4.05937 1.5 4.75 1.5H4.81875C5.28437 1.5 5.71875 1.74688 5.95312 2.15ZM2 2.75C2 3.2 2.10938 3.625 2.3 4H1C0.446875 4 0 4.44688 0 5V7C0 7.55312 0.446875 8 1 8H15C15.5531 8 16 7.55312 16 7V5C16 4.44688 15.5531 4 15 4H13.7C13.8906 3.625 14 3.2 14 2.75C14 1.23125 12.7688 0 11.25 0H11.1812C10.1844 0 9.25938 0.528125 8.75313 1.3875L8 2.67188L7.24687 1.39062C6.74062 0.528125 5.81562 0 4.81875 0H4.75C3.23125 0 2 1.23125 2 2.75ZM12.5 2.75C12.5 3.44062 11.9406 4 11.25 4H9H8.95938L10.0469 2.15C10.2844 1.74688 10.7156 1.5 11.1812 1.5H11.25C11.9406 1.5 12.5 2.05938 12.5 2.75ZM1 9V14.5C1 15.3281 1.67188 16 2.5 16H7V9H1ZM9 16H13.5C14.3281 16 15 15.3281 15 14.5V9H9V16Z"
fill="#22C55E"
/>
</svg>
{t("title")}
</Typography>
<Typography as="p" className={styles.trialIntervals}>
<Typography className={styles.oldInterval}>
{await getPeriodTextServer(periodType, oldTrialInterval)}
</Typography>
<svg
width="14"
height="12"
viewBox="0 0 14 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.7063 6.70859C14.0969 6.31797 14.0969 5.68359 13.7063 5.29297L8.70625 0.292969C8.31563 -0.0976562 7.68125 -0.0976562 7.29063 0.292969C6.9 0.683594 6.9 1.31797 7.29063 1.70859L10.5875 5.00234H1C0.446875 5.00234 0 5.44922 0 6.00234C0 6.55547 0.446875 7.00234 1 7.00234H10.5844L7.29375 10.2961C6.90312 10.6867 6.90312 11.3211 7.29375 11.7117C7.68437 12.1023 8.31875 12.1023 8.70938 11.7117L13.7094 6.71172L13.7063 6.70859Z"
fill="#22C55E"
/>
</svg>
<Typography className={styles.newInterval}>
{await getPeriodTextServer(periodType, newTrialInterval)}
</Typography>
</Typography>
<Typography as="p" className={styles.description}>
{t("description")}
</Typography>
</div>
);
}

View File

@ -0,0 +1,13 @@
.container {
margin-top: 25px;
width: 100%;
display: flex;
flex-direction: column;
& > .title {
font-size: 24px;
line-height: 32px;
font-weight: 700;
color: #111827;
}
}

View File

@ -0,0 +1,29 @@
import { getTranslations } from "next-intl/server";
import { Typography } from "@/components/ui";
import { translatePathEmailMarketingCompatibilityV2 } from "@/shared/constants/translate";
import DetailedPortraitCard from "../DetailedPortraitCard/DetailedPortraitCard";
import GuideCard from "../GuideCard/GuideCard";
import IndividualAdviceCard from "../IndividualAdviceCard/IndividualAdviceCard";
import SearchCompatiblePartnerCard from "../SearchCompatiblePartnerCard/SearchCompatiblePartnerCard";
import styles from "./WhatGet.module.scss";
export default async function WhatGet() {
const t = await getTranslations(
translatePathEmailMarketingCompatibilityV2("Landing.what-get")
);
return (
<div className={styles.container}>
<Typography as="h2" align="left" className={styles.title}>
{t("title")}
</Typography>
<DetailedPortraitCard />
<GuideCard />
<IndividualAdviceCard />
<SearchCompatiblePartnerCard />
</div>
);
}

View File

@ -0,0 +1,15 @@
export { default as GuaranteedSecurityPayments } from "./GuaranteedSecurityPayments/GuaranteedSecurityPayments";
export { default as Header } from "./Header/Header";
export { default as HeaderTimer } from "./HeaderTimer/HeaderTimer";
export { default as HiBlock } from "./HiBlock/HiBlock";
export { default as LandingButtonWrapper } from "./LandingButtonWrapper/LandingButtonWrapper";
export { default as MoneyBackGuarantee } from "./MoneyBackGuarantee/MoneyBackGuarantee";
export { default as Payments } from "./Payments/Payments";
export { default as PlanAlsoIncludes } from "./PlanAlsoIncludes/PlanAlsoIncludes";
export { default as PricingSummary } from "./PricingSummary/PricingSummary";
export { default as RealTimeActivity } from "./RealTimeActivity/RealTimeActivity";
export { default as Reviews } from "./Reviews/Reviews";
export { default as SpecialOffer } from "./SpecialOffer/SpecialOffer";
export { default as SpecialOfferButtonWrapper } from "./SpecialOfferButtonWrapper/SpecialOfferButtonWrapper";
export { default as Statistics } from "./Statistics/Statistics";
export { default as WhatGet } from "./WhatGet/WhatGet";

View File

@ -1,19 +1,21 @@
import Image from "next/image";
import clsx from "clsx";
import { OnlineIndicator } from "..";
import styles from "./UserAvatar.module.scss";
export interface UserAvatarProps {
export interface UserAvatarProps extends React.ComponentProps<"div"> {
src: string;
alt: string;
size?: "sm" | "md" | "lg";
size?: "s" | "sm" | "md" | "lg";
isOnline?: boolean;
isOnlineIndicator?: boolean;
}
const sizes = {
sm: 48,
s: 32,
sm: 36,
md: 48,
lg: 48,
};
@ -24,9 +26,10 @@ export default function UserAvatar({
size = "md",
isOnline,
isOnlineIndicator = true,
className,
}: UserAvatarProps) {
return (
<div className={styles.avatarContainer}>
<div className={clsx(styles.avatarContainer, className)}>
<Image
src={src}
alt={alt}

View File

@ -4,7 +4,7 @@ import clsx from "clsx";
import styles from "./BlurComponent.module.scss";
type BlurComponentProps = {
children: ReactNode;
children?: ReactNode;
isActiveBlur?: boolean;
className?: string;
gradientClassName?: string;

View File

@ -1,7 +1,7 @@
"use client";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
interface GenerationStatus {
id: string;
@ -42,7 +42,7 @@ export function useGenerationStatus(jobId: string, initialStatus: string) {
if (data.status === "done" && data.imageUrl) {
setImageUrl(data.imageUrl);
// Refresh the page cache to update dashboard data
if (wasProcessing && isDoneNow) {
router.refresh();

View File

@ -15,6 +15,7 @@ const profilePrefix = "profile";
const retainingFunnelPrefix = "retaining";
const emailMarketingPrefix = "em";
const emailMarketingCompatibilityV1Prefix = `${emailMarketingPrefix}/c/v1`;
const emailMarketingCompatibilityV2Prefix = `${emailMarketingPrefix}/c/v2`;
export const ROUTES = {
home: () => createRoute([]),
@ -90,6 +91,12 @@ export const ROUTES = {
emailMarketingCompatibilityV1SpecialOffer: () =>
createRoute([emailMarketingCompatibilityV1Prefix, "special-offer"]),
// Email Marketing Compatibility V2
emailMarketingCompatibilityV2Landing: () =>
createRoute([emailMarketingCompatibilityV2Prefix, "landing"]),
emailMarketingCompatibilityV2SpecialOffer: () =>
createRoute([emailMarketingCompatibilityV2Prefix, "special-offer"]),
// // Compatibility
// compatibilities: () => createRoute(["compatibilities"]),

View File

@ -1,2 +1,5 @@
export const emailMarketingCompV1Images = (path: string) =>
`/email-marketing/comp/v1/${path}`;
export const emailMarketingCompV2Images = (path: string) =>
`/email-marketing/comp/v2/${path}`;

View File

@ -1,2 +1,5 @@
export const translatePathEmailMarketingCompatibilityV1 = (path: string) =>
`EmailMarketing.Compatibility.v1.${path}`;
export const translatePathEmailMarketingCompatibilityV2 = (path: string) =>
`EmailMarketing.Compatibility.v2.${path}`;