diff --git a/messages/en.json b/messages/en.json index 8abf933..576353c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -215,5 +215,71 @@ }, "ActionFieldsForm": { "required_field": "This field is required" + }, + "AdditionalPurchases": { + "caution": { + "title": "Caution!", + "description": "To prevent double charges please don`t close the page and don`t go back." + }, + "add-consultant": { + "title": "More for you", + "exclusive_offer": "Exclusive offer recommended for you to achieve your goals faster", + "your_unique_consultation": "Your unique individual consultation", + "30-minute": "30-minute private consultation with an expert", + "unlock_profound": "Unlock profound insights into your personality, relationships, career trajectory, and life's pivotal moments through astrology, empowering you to make informed decisions and achieve greater fulfillment.", + "one_time_price_offer": "One time price offer: ", + "choose_from": "Choose from 80+ experts astrologers.", + "original_price": "Original price: {oldPrice} ", + "save": "Economisez 50", + "get_my_consultation": "Get my consultation", + "skip_this_offer": "Skip this offer" + }, + "add-guides": { + "title": "Choose your sign-up offer 🔥", + "subtitle": "Available only now", + "description": "*You will be charged for the add-on services or offers selected at the time of purchase. This is a non-recuring payment.", + "button": "Get my copy", + + "products": { + "main_ultra_pack": { + "title": "ULTRA PACK", + "discount": "{discount}% OFF", + "subtitle": "(3 in 1 + 2 secret bonus reading)", + "price": " ( regular price )", + "emoji": "star_struck.webp" + }, + "main_numerology_analysis": { + "title": "NUMEROLOGY ANALYSIS", + "discount": "{discount}% OFF", + "price": " ( was )", + "emoji": "input_numbers.webp" + }, + "main_tarot_reading": { + "title": "TAROT READING", + "discount": "{discount}% OFF", + "price": " ( was )", + "emoji": "sunset.webp" + }, + "main_palmistry_guide": { + "title": "PALMISTRY GUIDE", + "discount": "{discount}% OFF", + "price": " ( was )", + "emoji": "rised_hand.webp" + }, + "main_money_reading": { + "title": "MONEY READING", + "discount": "{discount}% OFF", + "price": " ( was )", + "emoji": "money.png" + }, + "main_skip_offer": { + "title": "SKIP OFFER", + "discount": "{discount}% OFF", + "price": " ( was )", + "subtitle": "You are missing out on both readings", + "emoji": "rised_hand.webp" + } + } + } } } diff --git a/public/check-mark-1.svg b/public/check-mark-1.svg new file mode 100644 index 0000000..94e3f61 --- /dev/null +++ b/public/check-mark-1.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/public/emoji/input_numbers.webp b/public/emoji/input_numbers.webp new file mode 100644 index 0000000..e340b5b Binary files /dev/null and b/public/emoji/input_numbers.webp differ diff --git a/public/emoji/money.png b/public/emoji/money.png new file mode 100644 index 0000000..b3750f0 Binary files /dev/null and b/public/emoji/money.png differ diff --git a/public/emoji/rised_hand.webp b/public/emoji/rised_hand.webp new file mode 100644 index 0000000..c4412bd Binary files /dev/null and b/public/emoji/rised_hand.webp differ diff --git a/public/emoji/smiling-face-with-hearts.webp b/public/emoji/smiling-face-with-hearts.webp new file mode 100644 index 0000000..49cbd68 Binary files /dev/null and b/public/emoji/smiling-face-with-hearts.webp differ diff --git a/public/emoji/star_struck.webp b/public/emoji/star_struck.webp new file mode 100644 index 0000000..a7246cd Binary files /dev/null and b/public/emoji/star_struck.webp differ diff --git a/public/emoji/sunset.webp b/public/emoji/sunset.webp new file mode 100644 index 0000000..3add8d8 Binary files /dev/null and b/public/emoji/sunset.webp differ diff --git a/public/paywall__astrologers-image.png b/public/paywall__astrologers-image.png new file mode 100644 index 0000000..921ccde Binary files /dev/null and b/public/paywall__astrologers-image.png differ diff --git a/public/paywall__spiritist-spiritualist.png b/public/paywall__spiritist-spiritualist.png new file mode 100644 index 0000000..32004d0 Binary files /dev/null and b/public/paywall__spiritist-spiritualist.png differ diff --git a/src/app/[locale]/(additional-purchases)/add-consultant/page.module.scss b/src/app/[locale]/(additional-purchases)/add-consultant/page.module.scss new file mode 100644 index 0000000..b81f29c --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/add-consultant/page.module.scss @@ -0,0 +1,14 @@ +.title.title { + color: #086de5; + margin-top: 24px; + margin-bottom: 6px; + line-height: 150%; +} + +.exclusiveOffer.exclusiveOffer { + width: 100%; + background-color: #e7f5fd; + padding: 8px 16px; + border-radius: 8px; + line-height: 125%; +} diff --git a/src/app/[locale]/(additional-purchases)/add-consultant/page.tsx b/src/app/[locale]/(additional-purchases)/add-consultant/page.tsx new file mode 100644 index 0000000..6299b34 --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/add-consultant/page.tsx @@ -0,0 +1,33 @@ +import { useTranslations } from "next-intl"; + +import { + AddConsultantButton, + Caution, + ConsultationTable, +} from "@/components/domains/additional-purchases"; +import { Typography } from "@/components/ui"; + +import styles from "./page.module.scss"; + +export default function AddConsultant() { + const t = useTranslations("AdditionalPurchases.add-consultant"); + + return ( + <> + + + {t("title")} + + + {t("exclusive_offer")} + + + + + ); +} diff --git a/src/app/[locale]/(additional-purchases)/add-guides/page.module.scss b/src/app/[locale]/(additional-purchases)/add-guides/page.module.scss new file mode 100644 index 0000000..c6db0c8 --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/add-guides/page.module.scss @@ -0,0 +1,18 @@ +.title { + font-size: 18px; + line-height: 135%; + margin-top: 16px; +} + +.subtitle { + color: #066fdf; + margin-block: 8px; + line-height: 135%; +} + +.description { + display: block; + margin: 20px auto; + font-size: 10px; + line-height: 125%; +} diff --git a/src/app/[locale]/(additional-purchases)/add-guides/page.tsx b/src/app/[locale]/(additional-purchases)/add-guides/page.tsx new file mode 100644 index 0000000..ddedf00 --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/add-guides/page.tsx @@ -0,0 +1,70 @@ +import { useTranslations } from "next-intl"; + +import { + AddGuidesButton, + Caution, + IOffer, + Offers, +} from "@/components/domains/additional-purchases"; +import { Typography } from "@/components/ui"; + +import styles from "./page.module.scss"; + +const PRODUCTS: (Omit & { key: string })[] = [ + { + id: "67ae7c05b29427c9ae695039", + key: "main.ultra.pack", + type: "one_time", + price: 4999, + oldPrice: 2499.5, + }, + { + id: "67ae7c05b29427c9ae69503c", + key: "main.numerology.analysis", + type: "one_time", + price: 1499, + oldPrice: 749.5, + }, + { + id: "67ae7c05b29427c9ae69503e", + key: "main.tarot.reading", + type: "one_time", + price: 1999, + oldPrice: 999.5, + }, + { + id: "6839ece6960824e7bba3e7bb", + key: "main.money.reading", + type: "one_time", + price: 1999, + oldPrice: 999.5, + }, + { + id: "main_skip_offer", + key: "main.skip.offer", + type: "one_time", + price: 0, + oldPrice: 0, + }, +]; + +export default function AddGuides() { + const t = useTranslations("AdditionalPurchases.add-guides"); + + return ( + <> + + + {t("title")} + + + {t("subtitle")} + + + + {t("description")} + + + + ); +} diff --git a/src/app/[locale]/(additional-purchases)/layout.module.scss b/src/app/[locale]/(additional-purchases)/layout.module.scss new file mode 100644 index 0000000..9f967e9 --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/layout.module.scss @@ -0,0 +1,7 @@ +.layout { + position: relative; + padding: 24px; + padding-bottom: 120px; + min-height: 100dvh; + height: fit-content; +} diff --git a/src/app/[locale]/(additional-purchases)/layout.tsx b/src/app/[locale]/(additional-purchases)/layout.tsx new file mode 100644 index 0000000..d817bfd --- /dev/null +++ b/src/app/[locale]/(additional-purchases)/layout.tsx @@ -0,0 +1,9 @@ +import styles from "./layout.module.scss"; + +export default function AdditionalPurchasesLayout({ + children, +}: { + children: React.ReactNode; +}) { + return
{children}
; +} diff --git a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss new file mode 100644 index 0000000..cf8be48 --- /dev/null +++ b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss @@ -0,0 +1,26 @@ +.container.container { + display: flex; + flex-direction: column; + -webkit-box-align: center; + align-items: center; + width: 100%; + height: fit-content; + max-width: 560px; + position: fixed; + bottom: 0dvh; + left: 50%; + transform: translateX(-50%); + margin-top: 0px; + padding-bottom: 20px; + padding-inline: 15px; + z-index: 5; +} + +.button.button { + padding-block: 16px; +} + +.skipButton.skipButton { + background-color: transparent; + text-decoration: underline; +} diff --git a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx new file mode 100644 index 0000000..3c9eadb --- /dev/null +++ b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx @@ -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 styles from "./AddConsultantButton.module.scss"; + +export default function AddConsultantButton() { + const router = useRouter(); + const t = useTranslations("AdditionalPurchases.add-consultant"); + + const handleSkipOffer = () => { + router.push(ROUTES.addGuides()); + }; + + return ( + + + + + ); +} diff --git a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss new file mode 100644 index 0000000..28a4f02 --- /dev/null +++ b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss @@ -0,0 +1,14 @@ +.container { + position: fixed; + bottom: calc(0dvh + 16px); + left: 50%; + transform: translateX(-50%); + width: 100%; + padding-inline: 24px; + max-width: 560px; + height: fit-content; +} + +.button { + padding-block: 16px; +} diff --git a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx new file mode 100644 index 0000000..56787e9 --- /dev/null +++ b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx @@ -0,0 +1,20 @@ +import { useTranslations } from "next-intl"; + +import { Button, Typography } from "@/components/ui"; +import { BlurComponent } from "@/components/widgets"; + +import styles from "./AddGuidesButton.module.scss"; + +export default function AddGuidesButton() { + const t = useTranslations("AdditionalPurchases.add-guides"); + + return ( + + + + ); +} diff --git a/src/components/domains/additional-purchases/Caution/Caution.module.scss b/src/components/domains/additional-purchases/Caution/Caution.module.scss new file mode 100644 index 0000000..e60ff52 --- /dev/null +++ b/src/components/domains/additional-purchases/Caution/Caution.module.scss @@ -0,0 +1,20 @@ +.container.container { + width: 100%; + padding: 20px; + background-color: #aaddff; + border-radius: 8px; + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + height: min-content; + margin-top: 12px; +} + +.title.title { + line-height: 135%; +} + +.text.text { + line-height: 125%; +} diff --git a/src/components/domains/additional-purchases/Caution/Caution.tsx b/src/components/domains/additional-purchases/Caution/Caution.tsx new file mode 100644 index 0000000..cfb0f12 --- /dev/null +++ b/src/components/domains/additional-purchases/Caution/Caution.tsx @@ -0,0 +1,37 @@ +import Image from "next/image"; +import { useTranslations } from "next-intl"; + +import { Typography } from "@/components/ui"; + +import styles from "./Caution.module.scss"; + +function Caution() { + const t = useTranslations("AdditionalPurchases.caution"); + + return ( +
+ Love +
+ + {t("title")} + + + {t("description")} + +
+
+ ); +} + +export default Caution; diff --git a/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.module.scss b/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.module.scss new file mode 100644 index 0000000..922d63d --- /dev/null +++ b/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.module.scss @@ -0,0 +1,83 @@ +.container { + margin: 12px auto 0; + display: flex; + flex-direction: column; + -webkit-box-align: center; + align-items: center; + position: relative; + border-radius: 16px; + width: 100%; + + & > .title { + font-size: 18px; + line-height: 135%; + margin-bottom: 16px; + margin-top: 0; + } +} + +.header { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-bottom: 12px; + + & > .textContainer { + & > .title { + color: #1b6acb; + line-height: 125%; + margin-bottom: 8px; + } + + & > .text { + font-size: 13px; + line-height: 125%; + } + } + + & > img { + height: 100px; + } +} + +.line { + width: 100%; + height: 1px; + background: rgb(203, 203, 203); +} + +.footer { + margin-top: 16px; + width: 100%; + + & > .oneTimePrice { + line-height: 24px; + } + + & > .oldPrice { + line-height: 24px; + margin-bottom: 16px; + + & > .save { + color: #1b6acb; + } + } +} + +.chooseContainer { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-top: 16px; + + & > .chooseText { + color: #066fdf; + line-height: 125%; + } +} diff --git a/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.tsx b/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.tsx new file mode 100644 index 0000000..4993763 --- /dev/null +++ b/src/components/domains/additional-purchases/ConsultationTable/ConsultationTable.tsx @@ -0,0 +1,89 @@ +import Image from "next/image"; +import { useTranslations } from "next-intl"; + +import { Typography } from "@/components/ui"; +import { getFormattedPrice } from "@/shared/utils/price"; +import { Currency } from "@/types"; + +import styles from "./ConsultationTable.module.scss"; + +export default function ConsultationTable() { + const t = useTranslations("AdditionalPurchases.add-consultant"); + const currency = Currency.USD; + + const price = getFormattedPrice(4985, currency); + + return ( +
+ + {t("your_unique_consultation")} + +
+
+ + {t("30-minute")} + + + {t("unlock_profound")} + +
+ spiritualist +
+
+
+ + {t.rich("one_time_price_offer", { + price: () => ( + + {price} + + ), + })} + + + {t("original_price", { + oldPrice: getFormattedPrice(9999, currency), + })} + + {t("save")} + + +
+
+ + {t("choose_from")} + + + astrologers +
+
+
+ ); +} diff --git a/src/components/domains/additional-purchases/Offer/Offer.module.scss b/src/components/domains/additional-purchases/Offer/Offer.module.scss new file mode 100644 index 0000000..bf3c98e --- /dev/null +++ b/src/components/domains/additional-purchases/Offer/Offer.module.scss @@ -0,0 +1,92 @@ +.container.container { + display: grid; + grid-template-columns: 24px 1fr auto; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: justify; + justify-content: space-between; + padding: 12px 16px; + margin-inline: auto; + max-width: 450px; + width: 100%; +} + +.container.active { + background: #066fdf; + border: 2px solid #066fdf; +} + +.mark { + border-radius: 50%; + width: 24px; + height: 24px; + display: flex; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + border: 1px solid #aaddff; + background: #fff; +} + +.textContainer { + display: flex; + flex-direction: column; + -webkit-box-flex: 1; + flex-grow: 1; + margin-left: 16px; + + & > * { + word-break: break-all; + } + + & > .title { + text-transform: uppercase; + line-height: 135%; + } + + & > .subtitle { + line-height: 135%; + margin-bottom: 5px; + } +} + +.priceContainer { + font-size: 12px; + font-weight: 600; + color: rgb(79, 79, 79); + display: flex; + align-items: center; + justify-content: flex-start; +} + +.description { + max-width: 140px; +} + +.oldPrice { + text-decoration: line-through; +} + +.discountContainer { + background: #a7ddff; + border-radius: 4px; + display: inline-block; + margin-left: 4px; + padding-inline: 4px; + + & > .discount { + color: #066fdf; + line-height: 170%; + } +} + +.emoji { + display: inline-block; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + width: 24px; + height: 24px; + margin-left: 3px; +} diff --git a/src/components/domains/additional-purchases/Offer/Offer.tsx b/src/components/domains/additional-purchases/Offer/Offer.tsx new file mode 100644 index 0000000..05f61bf --- /dev/null +++ b/src/components/domains/additional-purchases/Offer/Offer.tsx @@ -0,0 +1,146 @@ +import Image from "next/image"; +import { useTranslations } from "next-intl"; + +import { Card, Typography } from "@/components/ui"; +import { getFormattedPrice } from "@/shared/utils/price"; +import { Currency } from "@/types"; + +import styles from "./Offer.module.scss"; + +export interface IOffer { + id: string; + productKey: string; + type: "one_time" | string; + price: number; + oldPrice: number; +} + +interface OfferProps extends IOffer { + isActive: boolean; + className?: string; + onClick: () => void; +} + +export default function Offer(props: OfferProps) { + const { id, productKey, isActive, price, oldPrice, className, onClick } = + props; + + const t = useTranslations( + `AdditionalPurchases.add-guides.products.${productKey?.replaceAll(".", "_")}` + ); + + const currency = Currency.USD; + + const subtitle = t.has("subtitle") ? t("subtitle") : undefined; + + const discount = (((oldPrice || 0) - price) / (oldPrice || 0)) * 100; + + const emoji = t.has("emoji") ? t("emoji") : undefined; + + const typographyColor = isActive ? "white" : "default"; + + return ( + +
+ {/* TODO: add icon after merge with chat */} + {isActive && ( + Checkmark + )} +
+
+ + {t("title")} + + {!!subtitle?.length && id !== "main_skip_offer" && ( + + {subtitle} + + )} +
+ {id !== "main_skip_offer" && ( + + {t.rich("price", { + price: () => ( + + {getFormattedPrice(price, currency)} + + ), + oldPrice: () => ( + + {getFormattedPrice(oldPrice || 0, currency)} + + ), + })} + + )} + + {id === "main_skip_offer" && ( + + {subtitle} + + )} + + {id !== "ultra_pack" && ( +
+ + {t("discount", { + discount: discount || 0, + })} + +
+ )} +
+
+ +
+ ); +} diff --git a/src/components/domains/additional-purchases/Offers/Offers.module.scss b/src/components/domains/additional-purchases/Offers/Offers.module.scss new file mode 100644 index 0000000..41e3f46 --- /dev/null +++ b/src/components/domains/additional-purchases/Offers/Offers.module.scss @@ -0,0 +1,6 @@ +.container { + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; +} diff --git a/src/components/domains/additional-purchases/Offers/Offers.tsx b/src/components/domains/additional-purchases/Offers/Offers.tsx new file mode 100644 index 0000000..c888275 --- /dev/null +++ b/src/components/domains/additional-purchases/Offers/Offers.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useState } from "react"; + +import styles from "./Offers.module.scss"; + +import { IOffer, Offer } from ".."; + +interface OffersProps { + products: (Omit & { key: string })[]; +} + +export default function Offers({ products }: OffersProps) { + const [activeOffer, setActiveOffer] = useState(products[0].id); + + return ( +
+ {products.map(product => ( + setActiveOffer(product.id)} + /> + ))} +
+ ); +} diff --git a/src/components/domains/additional-purchases/index.ts b/src/components/domains/additional-purchases/index.ts new file mode 100644 index 0000000..4a8fba5 --- /dev/null +++ b/src/components/domains/additional-purchases/index.ts @@ -0,0 +1,6 @@ +export { default as AddConsultantButton } from "./AddConsultantButton/AddConsultantButton"; +export { default as AddGuidesButton } from "./AddGuidesButton/AddGuidesButton"; +export { default as Caution } from "./Caution/Caution"; +export { default as ConsultationTable } from "./ConsultationTable/ConsultationTable"; +export { type IOffer, default as Offer } from "./Offer/Offer"; +export { default as Offers } from "./Offers/Offers"; diff --git a/src/components/ui/Card/Card.tsx b/src/components/ui/Card/Card.tsx index bf16fa5..f8a4fd1 100644 --- a/src/components/ui/Card/Card.tsx +++ b/src/components/ui/Card/Card.tsx @@ -1,17 +1,12 @@ -import { ReactNode } from "react"; import clsx from "clsx"; import styles from "./Card.module.scss"; -type CardProps = { - children: ReactNode; - className?: string; - style?: React.CSSProperties; -}; +type CardProps = React.HTMLAttributes; -export default function Card({ children, className, style }: CardProps) { +export default function Card({ children, className, ...props }: CardProps) { return ( -
+
{children}
); diff --git a/src/components/widgets/BlurComponent/BlurComponent.module.scss b/src/components/widgets/BlurComponent/BlurComponent.module.scss index 0b5e7fd..ee40430 100644 --- a/src/components/widgets/BlurComponent/BlurComponent.module.scss +++ b/src/components/widgets/BlurComponent/BlurComponent.module.scss @@ -5,6 +5,11 @@ text-align: center; text-align: -webkit-center; + & > * { + position: relative; + z-index: 10; + } + .gradientBlur { position: absolute; z-index: 5; diff --git a/src/shared/constants/client-routes.ts b/src/shared/constants/client-routes.ts index e1e5de2..9b78da3 100644 --- a/src/shared/constants/client-routes.ts +++ b/src/shared/constants/client-routes.ts @@ -63,6 +63,10 @@ export const ROUTES = { // Chat chat: () => createRoute(["chat"]), + // Additional Purchases + addConsultant: () => createRoute(["add-consultant"]), + addGuides: () => createRoute(["add-guides"]), + // // Compatibility // compatibilities: () => createRoute(["compatibilities"]),