From ae8486658c617fccdee3a3696d1abc453b2b4c34 Mon Sep 17 00:00:00 2001 From: gofnnp Date: Thu, 23 Oct 2025 18:50:58 +0400 Subject: [PATCH 01/17] home-edits new messages blur & gap --- src/app/[locale]/(core)/page.module.scss | 2 +- .../GlobalNewMessagesBanner.module.scss | 8 +++++--- .../GlobalNewMessagesBanner.tsx | 10 +++++----- .../domains/chat/ViewAll/ViewAll.module.scss | 4 ++++ src/components/domains/chat/ViewAll/ViewAll.tsx | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/app/[locale]/(core)/page.module.scss b/src/app/[locale]/(core)/page.module.scss index 7f581db..70eb179 100644 --- a/src/app/[locale]/(core)/page.module.scss +++ b/src/app/[locale]/(core)/page.module.scss @@ -1,6 +1,6 @@ .page { display: flex; flex-direction: column; - gap: 24px; + gap: 37px; position: relative; } diff --git a/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.module.scss b/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.module.scss index a57a294..96eb748 100644 --- a/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.module.scss +++ b/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.module.scss @@ -1,12 +1,14 @@ .container { width: 100%; - padding: 0 16px; + padding: 16px; display: flex; flex-direction: column; align-items: flex-end; gap: 16px; position: sticky; - top: 76px; + top: 60px; z-index: 100; - margin-bottom: clamp(16px, 2.5vw, 32px); + padding-bottom: clamp(16px, 2.5vw, 32px); + backdrop-filter: blur(12px); + background: #f8fafc8f; } diff --git a/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.tsx b/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.tsx index 6d99bf9..03a7a43 100644 --- a/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.tsx +++ b/src/components/domains/chat/GlobalNewMessagesBanner/GlobalNewMessagesBanner.tsx @@ -35,6 +35,11 @@ export default function GlobalNewMessagesBanner() { return (
+ {unreadChats.length > 1 && ( setHomeNewMessages({ isVisibleAll: !isVisibleAll })} /> )} -
); } diff --git a/src/components/domains/chat/ViewAll/ViewAll.module.scss b/src/components/domains/chat/ViewAll/ViewAll.module.scss index 50e2561..456f391 100644 --- a/src/components/domains/chat/ViewAll/ViewAll.module.scss +++ b/src/components/domains/chat/ViewAll/ViewAll.module.scss @@ -5,4 +5,8 @@ width: fit-content; height: fit-content; background-color: transparent; + + & > .text { + color: #6b7280; + } } diff --git a/src/components/domains/chat/ViewAll/ViewAll.tsx b/src/components/domains/chat/ViewAll/ViewAll.tsx index e844bbc..5b890e7 100644 --- a/src/components/domains/chat/ViewAll/ViewAll.tsx +++ b/src/components/domains/chat/ViewAll/ViewAll.tsx @@ -17,7 +17,7 @@ export default function ViewAll({ count, isAll, onClick }: ViewAllProps) { return ( From 111e5167d48cd95f986247cc72de882fc80a7c00 Mon Sep 17 00:00:00 2001 From: gofnnp Date: Sat, 25 Oct 2025 21:19:32 +0400 Subject: [PATCH 02/17] additional-purchases-v2 add video guides additional purchase --- messages/en.json | 52 ++++++- public/emoji/heart_from_hands.webp | Bin 0 -> 3318 bytes public/emoji/ring.webp | Bin 0 -> 3840 bytes public/emoji/rose.webp | Bin 0 -> 2910 bytes .../ap/[pageType]/page.tsx | 3 + .../Progress/Progress.module.scss | 98 +++++++++++++ .../Progress/Progress.tsx | 81 +++++++++++ .../VideoGuidesBanner/HeartIcon.svg | 3 + .../VideoGuidesBanner.module.scss | 45 ++++++ .../VideoGuidesBanner/VideoGuidesBanner.tsx | 27 ++++ .../VideoGuidesButton.module.scss | 45 ++++++ .../VideoGuidesButton/VideoGuidesButton.tsx | 88 ++++++++++++ .../VideoGuidesOffer.module.scss | 131 ++++++++++++++++++ .../VideoGuidesOffer/VideoGuidesOffer.tsx | 100 +++++++++++++ .../VideoGuidesOffers.module.scss | 7 + .../VideoGuidesOffers/VideoGuidesOffers.tsx | 81 +++++++++++ .../VideoGuidesPage.module.scss | 23 +++ .../VideoGuidesPage/VideoGuidesPage.tsx | 38 +++++ .../domains/additional-purchases/index.ts | 9 ++ 19 files changed, 830 insertions(+), 1 deletion(-) create mode 100644 public/emoji/heart_from_hands.webp create mode 100644 public/emoji/ring.webp create mode 100644 public/emoji/rose.webp create mode 100644 src/components/domains/additional-purchases/Progress/Progress.module.scss create mode 100644 src/components/domains/additional-purchases/Progress/Progress.tsx create mode 100644 src/components/domains/additional-purchases/VideoGuidesBanner/HeartIcon.svg create mode 100644 src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.module.scss create mode 100644 src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.tsx create mode 100644 src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.module.scss create mode 100644 src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.tsx create mode 100644 src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss create mode 100644 src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx create mode 100644 src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.module.scss create mode 100644 src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx create mode 100644 src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.module.scss create mode 100644 src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx diff --git a/messages/en.json b/messages/en.json index d9f7ffb..bcdcc8b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -295,6 +295,56 @@ "emoji": "rised_hand.webp" } } + }, + "video-guides": { + "banner": { + "title": "Amazing!", + "description": "Your journey begins now" + }, + "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": "Continue", + "skip_button": "Skip this offer and proceed further", + "copyright": "Β© 2025, Wit Lab LLC, California, US", + "products": { + "main_ultra_pack": { + "title": "Ultra Pack", + "subtitle": "3 in 1+2 secret bonus readings", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "star_struck.webp" + }, + "main_numerology_analysis": { + "title": "Relationship plan", + "subtitle": "Discover the future without losing yourself", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "ring.webp" + }, + "main_tarot_reading": { + "title": "Healthy compatibility", + "subtitle": "Balance between closeness and freedom", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "rose.webp" + }, + "main_palmistry_guide": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" + } + } + }, + "Progress": { + "items": { + "1": "Your Soulmate Portrait", + "2": "Video Guides", + "3": "Add Consultation", + "4": "Access Your Results" + } } }, "Chat": { @@ -641,4 +691,4 @@ "month": "{count, plural, zero {#-months} one {#-month} two {#-months} few {#-months} many {#-months} other {#-months}}", "year": "{count, plural, zero {#-years} one {#-year} two {#-years} few {#-years} many {#-years} other {#-years}}" } -} \ No newline at end of file +} diff --git a/public/emoji/heart_from_hands.webp b/public/emoji/heart_from_hands.webp new file mode 100644 index 0000000000000000000000000000000000000000..be923a0146f1716a9c3aa738984222e19614a8c1 GIT binary patch literal 3318 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 AVE_OC literal 0 HcmV?d00001 diff --git a/public/emoji/ring.webp b/public/emoji/ring.webp new file mode 100644 index 0000000000000000000000000000000000000000..98f9fb81befc9c3ab7e375daa81fbee00fefd217 GIT binary patch literal 3840 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 C(Q&T; literal 0 HcmV?d00001 diff --git a/public/emoji/rose.webp b/public/emoji/rose.webp new file mode 100644 index 0000000000000000000000000000000000000000..01d5b96fe257c6458dbd1d5aac74be81d4804dee GIT binary patch literal 2910 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 literal 0 HcmV?d00001 diff --git a/src/app/[locale]/(additional-purchases)/ap/[pageType]/page.tsx b/src/app/[locale]/(additional-purchases)/ap/[pageType]/page.tsx index 9e7d979..a74b02a 100644 --- a/src/app/[locale]/(additional-purchases)/ap/[pageType]/page.tsx +++ b/src/app/[locale]/(additional-purchases)/ap/[pageType]/page.tsx @@ -3,6 +3,7 @@ import { redirect } from "next/navigation"; import { AddConsultantPage, AddGuidesPage, + VideoGuidesPage, } from "@/components/domains/additional-purchases"; import { ROUTES } from "@/shared/constants/client-routes"; @@ -20,6 +21,8 @@ export default async function AdditionalProductPage({ return ; case "add_guides": return ; + case "video_guides": + return ; default: return redirect(ROUTES.home()); } diff --git a/src/components/domains/additional-purchases/Progress/Progress.module.scss b/src/components/domains/additional-purchases/Progress/Progress.module.scss new file mode 100644 index 0000000..00f1217 --- /dev/null +++ b/src/components/domains/additional-purchases/Progress/Progress.module.scss @@ -0,0 +1,98 @@ +.container { + position: relative; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + & > * { + z-index: 1; + } + + & > .item { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + + & > .marker { + width: 32px; + height: 32px; + border: 1px solid #e2e8f0; + border-radius: 50%; + background: f8fafc; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + background-color: #f8fafc; + + & > .number { + font-size: 12px; + font-weight: 500; + color: #9ca3af; + } + } + + & > .text { + font-size: 12px; + font-weight: 500; + line-height: 16px; + color: #9ca3af; + } + + &.active { + & > .marker { + position: relative; + box-shadow: 0px 2px 15px 0px #3b82f6f7; + background-color: #3b82f6; + border: 2px solid #ffffff; + + &::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 6px; + height: 6px; + border-radius: 50%; + background-color: #fff; + } + + & > .number { + display: none; + } + } + + & > .text { + color: #2866ed; + font-weight: 600; + } + } + + &.done { + & > .marker { + box-shadow: 0px 0px 0px 0px #3b82f626; + background-color: #2866ed; + border: none; + + & > .number { + display: none; + } + } + + & > .text { + color: #282828; + } + } + } + + .connector { + position: absolute; + top: 15px; + height: 2px; + z-index: 0; + } +} diff --git a/src/components/domains/additional-purchases/Progress/Progress.tsx b/src/components/domains/additional-purchases/Progress/Progress.tsx new file mode 100644 index 0000000..3679b54 --- /dev/null +++ b/src/components/domains/additional-purchases/Progress/Progress.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import clsx from "clsx"; + +import { Icon, IconName, Typography } from "@/components/ui"; +import { useDynamicSize } from "@/hooks/DOM/useDynamicSize"; + +import styles from "./Progress.module.scss"; + +interface IProgressProps { + activeItemIndex: number; +} + +export default function Progress({ activeItemIndex }: IProgressProps) { + const t = useTranslations("AdditionalPurchases.Progress"); + const { width: containerWidth, elementRef } = useDynamicSize({ + defaultWidth: 327, + }); + + const items = Object.values(t.raw("items") as Record); + + const firstChild = elementRef.current?.childNodes[0] as HTMLElement; + const lastChild = elementRef.current?.childNodes[ + items.length - 1 + ] as HTMLElement; + const leftIndent = + ((firstChild?.getBoundingClientRect().width || 100) - 32) / 2; + const rightIndent = + ((lastChild?.getBoundingClientRect().width || 76) - 32) / 2; + + return ( +
+ {items.map((item, index) => ( +
index && styles.done + )} + > +
+ {activeItemIndex > index && styles.done && ( + + )} + + {index + 1} + +
+ + {item} + +
+ ))} +
+
+ ); +} diff --git a/src/components/domains/additional-purchases/VideoGuidesBanner/HeartIcon.svg b/src/components/domains/additional-purchases/VideoGuidesBanner/HeartIcon.svg new file mode 100644 index 0000000..78e1716 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesBanner/HeartIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.module.scss b/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.module.scss new file mode 100644 index 0000000..5ecfd76 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.module.scss @@ -0,0 +1,45 @@ +.container { + width: 100%; + padding: 18px 20px 25px 25px; + background: linear-gradient( + 90deg, + rgba(78, 205, 196, 0.1) 0%, + rgba(102, 126, 234, 0.1) 100% + ); + border: 1px solid #4ecdc433; + border-radius: 32px; + margin-top: 34px; + display: grid; + grid-template-columns: 48px 1fr; + gap: 16px; + + & > .iconContainer { + width: 48px; + height: 48px; + border-radius: 50%; + background-color: #4ecdc433; + display: flex; + align-items: center; + justify-content: center; + } + + & > .textContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + + & > .title { + font-size: 18px; + font-weight: 700; + line-height: 28px; + color: #262626; + } + + & > .description { + font-size: 16px; + font-weight: 500; + line-height: 24px; + color: #525252; + } + } +} diff --git a/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.tsx b/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.tsx new file mode 100644 index 0000000..06e8d3a --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesBanner/VideoGuidesBanner.tsx @@ -0,0 +1,27 @@ +import { getTranslations } from "next-intl/server"; + +import { Typography } from "@/components/ui"; + +import HeartIcon from "./HeartIcon.svg"; + +import styles from "./VideoGuidesBanner.module.scss"; + +export default async function VideoGuidesBanner() { + const t = await getTranslations("AdditionalPurchases.video-guides.banner"); + + return ( +
+
+ +
+
+ + {t("title")} + + + {t("description")} + +
+
+ ); +} diff --git a/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.module.scss b/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.module.scss new file mode 100644 index 0000000..e2127ad --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.module.scss @@ -0,0 +1,45 @@ +.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: 20px; + background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); + border-radius: 16px; + box-shadow: + 0px 5px 14px 0px #3b82f666, + 0px 4px 6px 0px #3b82f61a; + + & > .text { + font-size: 19px; + font-weight: 500; + line-height: 125%; + } + } + + & > .skipButton { + padding: 0; + min-height: none; + margin-top: 13px; + + & > .text { + font-size: 16px; + line-height: 24px; + color: #1f2937; + text-decoration: underline; + } + } + + & > .copyright { + font-size: 12px; + line-height: 16px; + color: #9ca3af; + margin-top: 20px; + } +} diff --git a/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.tsx b/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.tsx new file mode 100644 index 0000000..9227b80 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesButton/VideoGuidesButton.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { Button, Spinner, Typography } from "@/components/ui"; +import { BlurComponent } from "@/components/widgets"; +import { useSingleCheckout } from "@/hooks/payment/useSingleCheckout"; +import { useToast } from "@/providers/toast-provider"; +import { ROUTES } from "@/shared/constants/client-routes"; + +import { useMultiPageNavigationContext } from ".."; +import { useProductSelection } from "../ProductSelectionProvider"; + +import styles from "./VideoGuidesButton.module.scss"; + +export default function VideoGuidesButton() { + const t = useTranslations("AdditionalPurchases.video-guides"); + const { addToast } = useToast(); + const { selectedProduct } = useProductSelection(); + const { navigation } = useMultiPageNavigationContext(); + + const { handleSingleCheckout, isLoading } = useSingleCheckout({ + onSuccess: () => { + navigation.goToNext(); + }, + onError: _error => { + addToast({ + variant: "error", + message: t("payment_error"), + duration: 5000, + }); + }, + returnUrl: new URL( + navigation.getNextPageUrl() || ROUTES.home(), + process.env.NEXT_PUBLIC_APP_URL || "" + ).toString(), + }); + + const handlePurchase = () => { + if (!selectedProduct) { + addToast({ + variant: "error", + message: t("select_product_error"), + duration: 5000, + }); + return; + } + + handleSingleCheckout({ + productId: selectedProduct.id, + key: selectedProduct.key, + }); + }; + + const handleSkipOffer = () => { + navigation.goToNext(); + }; + + return ( + + + + + {t("copyright")} + + + ); +} diff --git a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss new file mode 100644 index 0000000..67878d4 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss @@ -0,0 +1,131 @@ +.container.container { + position: relative; + width: 100%; + padding: 11px 18px 12px; + display: flex; + flex-direction: column; + box-shadow: 0px 0px 30px 0px #0000001f; + gap: 4px; + + & > .content { + display: grid; + grid-template-columns: 40px 1fr 20px; + gap: 12px; + + & > .emojiContainer { + width: 40px; + height: 40px; + border: 1px solid #cedfff; + background: linear-gradient(0deg, #e2ebff, #e2ebff); + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + + & > .emoji { + width: 30px; + height: 30px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + } + + & > .textContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + + & > .title { + font-size: 16px; + font-weight: 700; + line-height: 24px; + color: #262626; + } + + & > .subtitle { + font-size: 14px; + font-weight: 500; + line-height: 16px; + color: #737373; + } + } + + & > .checmarkContainer { + width: 20px; + height: 20px; + border: 2px solid #d4d4d4; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-top: auto; + } + } + + & > .footer { + display: flex; + align-items: flex-start; + justify-content: space-between; + + & > .price { + font-size: 14px; + line-height: 20px; + color: #737373; + + & > .currentPrice { + font-size: 14px; + line-height: 20px; + color: #737373; + } + + & > .oldPrice { + font-size: 14px; + line-height: 20px; + color: #9ca3af; + text-decoration: line-through; + } + } + + & > .discount { + display: block; + padding: 6px 8px; + background: #ff6b6b1a; + border-radius: 9999; + font-size: 12px; + font-weight: 700; + color: #ff6b6b; + margin-right: 19px; + } + } + + &.active { + outline: 2px solid #9fbdff; + + & > .content { + & > .checmarkContainer { + background-color: #2866ed; + border: none; + } + } + } + + &.main_ultra_pack { + & > .footer { + & > .discount { + position: absolute; + top: -15px; + right: 49px; + background: #ff3737; + box-shadow: 0px 1px 11.98px 0px #ff44448c; + border: 2px solid #ffffff4d; + padding: 8px 12px; + color: #fff; + font-size: 14px; + font-weight: 800; + letter-spacing: 0.5px; + margin-right: 0; + } + } + } +} diff --git a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx new file mode 100644 index 0000000..7974e46 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx @@ -0,0 +1,100 @@ +import { useTranslations } from "next-intl"; +import clsx from "clsx"; + +import { Card, Icon, IconName, Typography } from "@/components/ui"; +import { IFunnelPaymentVariant } from "@/entities/session/funnel/types"; +import { getFormattedPrice } from "@/shared/utils/price"; +import { Currency } from "@/types"; + +import styles from "./VideoGuidesOffer.module.scss"; + +interface VideoGuidesOfferProps { + offer: IFunnelPaymentVariant; + isActive: boolean; + className?: string; + onClick: () => void; +} + +export default function VideoGuidesOffer(props: VideoGuidesOfferProps) { + const { offer, isActive, className, onClick } = props; + + const { key, price, oldPrice } = offer; + + const productKey = key.replaceAll(".", "_"); + + const t = useTranslations( + `AdditionalPurchases.video-guides.products.${productKey}` + ); + + const currency = Currency.USD; + + const subtitle = t.has("subtitle") ? t("subtitle") : undefined; + + const discount = Math.ceil( + (((oldPrice || 0) - price) / (oldPrice || 0)) * 100 + ); + + const emoji = t.has("emoji") ? t("emoji") : undefined; + + return ( + +
+
+ +
+
+ + {t("title")} + + {subtitle && ( + + {subtitle} + + )} +
+
+ +
+
+
+ + {t.rich("price", { + price: () => ( + + {getFormattedPrice(price, currency)} + + ), + oldPrice: () => ( + + {getFormattedPrice(oldPrice || 0, currency)} + + ), + })} + + + {t("discount", { + discount: discount || 0, + })} + +
+
+ ); +} diff --git a/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.module.scss b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.module.scss new file mode 100644 index 0000000..f1812c0 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.module.scss @@ -0,0 +1,7 @@ +.container { + width: 100%; + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 37px; +} diff --git a/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx new file mode 100644 index 0000000..867e083 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +import { Skeleton } from "@/components/ui"; +import { IFunnelPaymentVariant } from "@/entities/session/funnel/types"; + +import { useMultiPageNavigationContext, VideoGuidesOffer } from ".."; +import { useProductSelection } from "../ProductSelectionProvider"; + +import styles from "./VideoGuidesOffers.module.scss"; + +export default function VideoGuidesOffers() { + const { navigation } = useMultiPageNavigationContext(); + const data = navigation.currentItem; + + const offers = useMemo(() => { + return [ + { + id: "1", + key: "main_ultra_pack", + type: "sdv", + price: 1939, + oldPrice: 3499, + }, + { + id: "2", + key: "main_numerology_analysis", + type: "sdv", + price: 938, + oldPrice: 1999, + }, + { + id: "3", + key: "main_tarot_reading", + type: "sdv", + price: 937, + oldPrice: 1999, + }, + { + id: "4", + key: "main_palmistry_guide", + type: "sdv", + price: 936, + oldPrice: 1999, + }, + ]; + return data?.variants ?? []; + }, [data]); + const [activeOffer, setActiveOffer] = useState(""); + const { setSelectedProduct } = useProductSelection(); + + useEffect(() => { + if (offers[0]) { + setActiveOffer(offers[0]?.id); + setSelectedProduct(offers[0]); + } + }, [offers, setSelectedProduct]); + + const handleOfferClick = (offer: IFunnelPaymentVariant) => { + setActiveOffer(offer.id); + setSelectedProduct(offer); + }; + + return ( +
+ {offers.map(offer => ( + handleOfferClick(offer)} + /> + ))} +
+ ); +} + +export function VideoGuidesOffersSkeleton() { + return ; +} diff --git a/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.module.scss b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.module.scss new file mode 100644 index 0000000..e8a36d9 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.module.scss @@ -0,0 +1,23 @@ +.title { + font-size: 25px; + font-weight: 600; + line-height: 28px; + margin-top: 32px; + color: #000000; + max-width: 281px; + margin-inline: auto; +} + +.subtitle { + font-size: 16px; + font-weight: 500; + line-height: 24px; + color: #737373; +} + +.description { + font-size: 12px; + line-height: 16px; + color: #6b7280; + margin-top: 12px; +} diff --git a/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx new file mode 100644 index 0000000..b1db972 --- /dev/null +++ b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx @@ -0,0 +1,38 @@ +import { Suspense } from "react"; +import { useTranslations } from "next-intl"; + +import { + ProductSelectionProvider, + Progress, + VideoGuidesBanner, + VideoGuidesButton, + VideoGuidesOffers, + VideoGuidesOffersSkeleton, +} from "@/components/domains/additional-purchases"; +import { Typography } from "@/components/ui"; + +import styles from "./VideoGuidesPage.module.scss"; + +export default function VideoGuidesPage() { + const t = useTranslations("AdditionalPurchases.video-guides"); + + return ( + + + + + {t("title")} + + + {t("subtitle")} + + }> + + + + {t("description")} + + + + ); +} diff --git a/src/components/domains/additional-purchases/index.ts b/src/components/domains/additional-purchases/index.ts index 360f109..d71789b 100644 --- a/src/components/domains/additional-purchases/index.ts +++ b/src/components/domains/additional-purchases/index.ts @@ -14,3 +14,12 @@ export { ProductSelectionProvider, useProductSelection, } from "./ProductSelectionProvider"; +export { default as Progress } from "./Progress/Progress"; +export { default as VideoGuidesBanner } from "./VideoGuidesBanner/VideoGuidesBanner"; +export { default as VideoGuidesButton } from "./VideoGuidesButton/VideoGuidesButton"; +export { default as VideoGuidesOffer } from "./VideoGuidesOffer/VideoGuidesOffer"; +export { + default as VideoGuidesOffers, + VideoGuidesOffersSkeleton, +} from "./VideoGuidesOffers/VideoGuidesOffers"; +export { default as VideoGuidesPage } from "./VideoGuidesPage/VideoGuidesPage"; From 042bb250575c90e7912d56d981ef21b6e102d3e7 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Mon, 27 Oct 2025 23:13:45 +0100 Subject: [PATCH 03/17] video guide --- messages/de.json | 58 +++++++++++++++++- messages/en.json | 30 ++++++---- messages/es.json | 58 +++++++++++++++++- .../AddConsultantButton.module.scss | 59 ++++++++++++------- .../AddConsultantButton.tsx | 8 ++- .../AddConsultantPage/AddConsultantPage.tsx | 16 ++++- .../AddGuidesButton.module.scss | 24 +++++++- .../AddGuidesButton/AddGuidesButton.tsx | 5 +- .../AddGuidesPage/AddGuidesPage.tsx | 16 ++++- .../AdditionalPurchaseBanner.module.scss | 45 ++++++++++++++ .../AdditionalPurchaseBanner.tsx | 29 +++++++++ .../AdditionalPurchaseBanner/HeartIcon.svg | 3 + .../Progress/Progress.module.scss | 6 +- .../Progress/Progress.tsx | 15 +++-- .../VideoGuidesOffer/VideoGuidesOffer.tsx | 2 +- .../VideoGuidesOffers/VideoGuidesOffers.tsx | 47 +++++---------- .../VideoGuidesPage/VideoGuidesPage.tsx | 18 +++++- .../domains/additional-purchases/index.ts | 1 + src/entities/session/funnel/types.ts | 1 + .../multiPages/useMultiPageNavigation.ts | 3 + 20 files changed, 355 insertions(+), 89 deletions(-) create mode 100644 src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.module.scss create mode 100644 src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.tsx create mode 100644 src/components/domains/additional-purchases/AdditionalPurchaseBanner/HeartIcon.svg diff --git a/messages/de.json b/messages/de.json index b55f3f2..4c19c40 100644 --- a/messages/de.json +++ b/messages/de.json @@ -222,6 +222,13 @@ "required_field": "This field is required" }, "AdditionalPurchases": { + "banner": { + "title": "Amazing!", + "description": "Your journey begins now" + }, + "Progress": { + "final_step": "Access Your Results" + }, "caution": { "title": "Caution!", "description": "To prevent double charges please don`t close the page and don`t go back." @@ -238,7 +245,8 @@ "save": "Save {discount}%", "get_my_consultation": "Get my consultation", "skip_this_offer": "Skip this offer", - "payment_error": "Something went wrong. Please try again later." + "payment_error": "Something went wrong. Please try again later.", + "copyright": "Β© 2025, Wit Lab LLC, California, US" }, "add-guides": { "title": "Choose your sign-up offer πŸ”₯", @@ -248,6 +256,7 @@ "payment_error": "Something went wrong. Please try again later.", "select_product_error": "Please select a product", "skip_offer": "Skip offer", + "copyright": "Β© 2025, Wit Lab LLC, California, US", "products": { "main_ultra_pack": { "title": "ULTRA PACK", @@ -288,6 +297,53 @@ "emoji": "rised_hand.webp" } } + }, + "video-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": "Continue", + "skip_button": "Skip this offer and proceed further", + "copyright": "Β© 2025, Wit Lab LLC, California, US", + "products": { + "main_ultra_pack": { + "title": "Ultra Pack", + "subtitle": "3 in 1+2 secret bonus readings", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "star_struck.webp" + }, + "main_numerology_analysis": { + "title": "Relationship plan", + "subtitle": "Discover the future without losing yourself", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "ring.webp" + }, + "main_tarot_reading": { + "title": "Healthy compatibility", + "subtitle": "Balance between closeness and freedom", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "rose.webp" + }, + "main_palmistry_guide": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" + }, + "main_money_reading": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" + } + }, + "payment_error": "Something went wrong. Please try again later.", + "select_product_error": "Please select a product" } }, "Chat": { diff --git a/messages/en.json b/messages/en.json index bcdcc8b..94d7dd2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -229,6 +229,13 @@ "required_field": "This field is required" }, "AdditionalPurchases": { + "banner": { + "title": "Amazing!", + "description": "Your journey begins now" + }, + "Progress": { + "final_step": "Access Your Results" + }, "caution": { "title": "Caution!", "description": "To prevent double charges please don`t close the page and don`t go back." @@ -245,7 +252,8 @@ "save": "Save {discount}%", "get_my_consultation": "Get my consultation", "skip_this_offer": "Skip this offer", - "payment_error": "Something went wrong. Please try again later." + "payment_error": "Something went wrong. Please try again later.", + "copyright": "Β© 2025, Wit Lab LLC, California, US" }, "add-guides": { "title": "Choose your sign-up offer πŸ”₯", @@ -255,6 +263,7 @@ "payment_error": "Something went wrong. Please try again later.", "select_product_error": "Please select a product", "skip_offer": "Skip offer", + "copyright": "Β© 2025, Wit Lab LLC, California, US", "products": { "main_ultra_pack": { "title": "ULTRA PACK", @@ -297,10 +306,6 @@ } }, "video-guides": { - "banner": { - "title": "Amazing!", - "description": "Your journey begins now" - }, "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.", @@ -335,16 +340,15 @@ "discount": "{discount}% OFF", "price": "Now ", "emoji": "heart_from_hands.webp" + }, + "main_money_reading": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" } } - }, - "Progress": { - "items": { - "1": "Your Soulmate Portrait", - "2": "Video Guides", - "3": "Add Consultation", - "4": "Access Your Results" - } } }, "Chat": { diff --git a/messages/es.json b/messages/es.json index 4dccde1..fefa091 100644 --- a/messages/es.json +++ b/messages/es.json @@ -222,6 +222,13 @@ "required_field": "This field is required" }, "AdditionalPurchases": { + "banner": { + "title": "Amazing!", + "description": "Your journey begins now" + }, + "Progress": { + "final_step": "Access Your Results" + }, "caution": { "title": "Caution!", "description": "To prevent double charges please don`t close the page and don`t go back." @@ -238,7 +245,8 @@ "save": "Save {discount}%", "get_my_consultation": "Get my consultation", "skip_this_offer": "Skip this offer", - "payment_error": "Something went wrong. Please try again later." + "payment_error": "Something went wrong. Please try again later.", + "copyright": "Β© 2025, Wit Lab LLC, California, US" }, "add-guides": { "title": "Choose your sign-up offer πŸ”₯", @@ -248,6 +256,7 @@ "payment_error": "Something went wrong. Please try again later.", "select_product_error": "Please select a product", "skip_offer": "Skip offer", + "copyright": "Β© 2025, Wit Lab LLC, California, US", "products": { "main_ultra_pack": { "title": "ULTRA PACK", @@ -288,6 +297,53 @@ "emoji": "rised_hand.webp" } } + }, + "video-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": "Continue", + "skip_button": "Skip this offer and proceed further", + "copyright": "Β© 2025, Wit Lab LLC, California, US", + "products": { + "main_ultra_pack": { + "title": "Ultra Pack", + "subtitle": "3 in 1+2 secret bonus readings", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "star_struck.webp" + }, + "main_numerology_analysis": { + "title": "Relationship plan", + "subtitle": "Discover the future without losing yourself", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "ring.webp" + }, + "main_tarot_reading": { + "title": "Healthy compatibility", + "subtitle": "Balance between closeness and freedom", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "rose.webp" + }, + "main_palmistry_guide": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" + }, + "main_money_reading": { + "title": "How to talk about feelings", + "subtitle": "Express your emotions and be understood", + "discount": "{discount}% OFF", + "price": "Now ", + "emoji": "heart_from_hands.webp" + } + }, + "payment_error": "Something went wrong. Please try again later.", + "select_product_error": "Please select a product" } }, "Chat": { diff --git a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss index cf8be48..e2127ad 100644 --- a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss +++ b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.module.scss @@ -1,26 +1,45 @@ -.container.container { - display: flex; - flex-direction: column; - -webkit-box-align: center; - align-items: center; - width: 100%; - height: fit-content; - max-width: 560px; +.container { position: fixed; - bottom: 0dvh; + bottom: calc(0dvh + 16px); left: 50%; transform: translateX(-50%); - margin-top: 0px; - padding-bottom: 20px; - padding-inline: 15px; - z-index: 5; -} + width: 100%; + padding-inline: 24px; + max-width: 560px; + height: fit-content; -.button.button { - padding-block: 16px; -} + & > .button { + padding-block: 20px; + background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); + border-radius: 16px; + box-shadow: + 0px 5px 14px 0px #3b82f666, + 0px 4px 6px 0px #3b82f61a; -.skipButton.skipButton { - background-color: transparent; - text-decoration: underline; + & > .text { + font-size: 19px; + font-weight: 500; + line-height: 125%; + } + } + + & > .skipButton { + padding: 0; + min-height: none; + margin-top: 13px; + + & > .text { + font-size: 16px; + line-height: 24px; + color: #1f2937; + text-decoration: underline; + } + } + + & > .copyright { + font-size: 12px; + line-height: 16px; + color: #9ca3af; + margin-top: 20px; + } } diff --git a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx index 94d3f24..c73c6f5 100644 --- a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx +++ b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx @@ -67,20 +67,24 @@ export default function AddConsultantButton() { {isLoading ? ( ) : ( - + {t("get_my_consultation")} )} + + {t("copyright")} + ); } diff --git a/src/components/domains/additional-purchases/AddConsultantPage/AddConsultantPage.tsx b/src/components/domains/additional-purchases/AddConsultantPage/AddConsultantPage.tsx index 1b7eeb8..80b5f61 100644 --- a/src/components/domains/additional-purchases/AddConsultantPage/AddConsultantPage.tsx +++ b/src/components/domains/additional-purchases/AddConsultantPage/AddConsultantPage.tsx @@ -1,9 +1,12 @@ +"use client"; + import { useTranslations } from "next-intl"; import { AddConsultantButton, - Caution, ConsultationTable, + Progress, + useMultiPageNavigationContext, } from "@/components/domains/additional-purchases"; import { Card, Typography } from "@/components/ui"; @@ -11,10 +14,19 @@ import styles from "./AddConsultantPage.module.scss"; export default function AddConsultantPage() { const t = useTranslations("AdditionalPurchases.add-consultant"); + const { navigation } = useMultiPageNavigationContext(); + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ названия всСх страниц для прогрСсса + const progressItems = navigation.data.map((item: any) => { + return item.title || item.type || ""; + }); return ( <> - + {t("title")} diff --git a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss index 28a4f02..465d567 100644 --- a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss +++ b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.module.scss @@ -7,8 +7,26 @@ padding-inline: 24px; max-width: 560px; height: fit-content; -} -.button { - padding-block: 16px; + & > .button { + padding-block: 20px; + background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); + border-radius: 16px; + box-shadow: + 0px 5px 14px 0px #3b82f666, + 0px 4px 6px 0px #3b82f61a; + + & > .text { + font-size: 19px; + font-weight: 500; + line-height: 125%; + } + } + + & > .copyright { + font-size: 12px; + line-height: 16px; + color: #9ca3af; + margin-top: 20px; + } } diff --git a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx index 3e0a305..2cd60c1 100644 --- a/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx +++ b/src/components/domains/additional-purchases/AddGuidesButton/AddGuidesButton.tsx @@ -68,11 +68,14 @@ export default function AddGuidesButton() { {isLoading ? ( ) : ( - + {isSkipOffer ? t("skip_offer") : t("button")} )} + + {t("copyright")} + ); } diff --git a/src/components/domains/additional-purchases/AddGuidesPage/AddGuidesPage.tsx b/src/components/domains/additional-purchases/AddGuidesPage/AddGuidesPage.tsx index c44f400..ee4e5df 100644 --- a/src/components/domains/additional-purchases/AddGuidesPage/AddGuidesPage.tsx +++ b/src/components/domains/additional-purchases/AddGuidesPage/AddGuidesPage.tsx @@ -1,12 +1,15 @@ +"use client"; + import { Suspense } from "react"; import { useTranslations } from "next-intl"; import { AddGuidesButton, - Caution, Offers, OffersSkeleton, ProductSelectionProvider, + Progress, + useMultiPageNavigationContext, } from "@/components/domains/additional-purchases"; import { Typography } from "@/components/ui"; @@ -14,10 +17,19 @@ import styles from "./AddGuidesPage.module.scss"; export default function AddGuidesPage() { const t = useTranslations("AdditionalPurchases.add-guides"); + const { navigation } = useMultiPageNavigationContext(); + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ названия всСх страниц для прогрСсса + const progressItems = navigation.data.map((item: any) => { + return item.title || item.type || ""; + }); return ( - + {t("title")} diff --git a/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.module.scss b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.module.scss new file mode 100644 index 0000000..5ecfd76 --- /dev/null +++ b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.module.scss @@ -0,0 +1,45 @@ +.container { + width: 100%; + padding: 18px 20px 25px 25px; + background: linear-gradient( + 90deg, + rgba(78, 205, 196, 0.1) 0%, + rgba(102, 126, 234, 0.1) 100% + ); + border: 1px solid #4ecdc433; + border-radius: 32px; + margin-top: 34px; + display: grid; + grid-template-columns: 48px 1fr; + gap: 16px; + + & > .iconContainer { + width: 48px; + height: 48px; + border-radius: 50%; + background-color: #4ecdc433; + display: flex; + align-items: center; + justify-content: center; + } + + & > .textContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + + & > .title { + font-size: 18px; + font-weight: 700; + line-height: 28px; + color: #262626; + } + + & > .description { + font-size: 16px; + font-weight: 500; + line-height: 24px; + color: #525252; + } + } +} diff --git a/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.tsx b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.tsx new file mode 100644 index 0000000..11b98d3 --- /dev/null +++ b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/AdditionalPurchaseBanner.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { Typography } from "@/components/ui"; + +import HeartIcon from "./HeartIcon.svg"; + +import styles from "./AdditionalPurchaseBanner.module.scss"; + +export default function AdditionalPurchaseBanner() { + const t = useTranslations("AdditionalPurchases.banner"); + + return ( +
+
+ +
+
+ + {t("title")} + + + {t("description")} + +
+
+ ); +} diff --git a/src/components/domains/additional-purchases/AdditionalPurchaseBanner/HeartIcon.svg b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/HeartIcon.svg new file mode 100644 index 0000000..78e1716 --- /dev/null +++ b/src/components/domains/additional-purchases/AdditionalPurchaseBanner/HeartIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/domains/additional-purchases/Progress/Progress.module.scss b/src/components/domains/additional-purchases/Progress/Progress.module.scss index 00f1217..a57cad1 100644 --- a/src/components/domains/additional-purchases/Progress/Progress.module.scss +++ b/src/components/domains/additional-purchases/Progress/Progress.module.scss @@ -19,7 +19,7 @@ & > .marker { width: 32px; height: 32px; - border: 1px solid #e2e8f0; + border: 2px solid #e2e8f0; border-radius: 50%; background: f8fafc; display: flex; @@ -76,7 +76,7 @@ & > .marker { box-shadow: 0px 0px 0px 0px #3b82f626; background-color: #2866ed; - border: none; + border: 2px solid #2866ed; & > .number { display: none; @@ -91,7 +91,7 @@ .connector { position: absolute; - top: 15px; + top: 16px; height: 2px; z-index: 0; } diff --git a/src/components/domains/additional-purchases/Progress/Progress.tsx b/src/components/domains/additional-purchases/Progress/Progress.tsx index 3679b54..9aa5b18 100644 --- a/src/components/domains/additional-purchases/Progress/Progress.tsx +++ b/src/components/domains/additional-purchases/Progress/Progress.tsx @@ -9,20 +9,23 @@ import { useDynamicSize } from "@/hooks/DOM/useDynamicSize"; import styles from "./Progress.module.scss"; interface IProgressProps { + items: string[]; activeItemIndex: number; } -export default function Progress({ activeItemIndex }: IProgressProps) { +export default function Progress({ items, activeItemIndex }: IProgressProps) { const t = useTranslations("AdditionalPurchases.Progress"); const { width: containerWidth, elementRef } = useDynamicSize({ defaultWidth: 327, }); - const items = Object.values(t.raw("items") as Record); + // ВсСгда добавляСм Ρ„ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡƒΠ½ΠΊΡ‚ Π² ΠΊΠΎΠ½Π΅Ρ† + const finalStep = t("final_step"); + const allItems = [...items, finalStep]; const firstChild = elementRef.current?.childNodes[0] as HTMLElement; const lastChild = elementRef.current?.childNodes[ - items.length - 1 + allItems.length - 1 ] as HTMLElement; const leftIndent = ((firstChild?.getBoundingClientRect().width || 100) - 32) / 2; @@ -31,7 +34,7 @@ export default function Progress({ activeItemIndex }: IProgressProps) { return (
- {items.map((item, index) => ( + {allItems.map((item, index) => (
{ - return [ - { - id: "1", - key: "main_ultra_pack", - type: "sdv", - price: 1939, - oldPrice: 3499, - }, - { - id: "2", - key: "main_numerology_analysis", - type: "sdv", - price: 938, - oldPrice: 1999, - }, - { - id: "3", - key: "main_tarot_reading", - type: "sdv", - price: 937, - oldPrice: 1999, - }, - { - id: "4", - key: "main_palmistry_guide", - type: "sdv", - price: 936, - oldPrice: 1999, - }, - ]; - return data?.variants ?? []; + // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Π΄Π°Π½Π½Ρ‹Π΅ с сСрвСра ΠΈ добавляСм oldPrice Π½Π° основС Π·Π°ΡˆΠΈΡ‚ΠΎΠ³ΠΎ ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚Π° скидки + const serverOffers = data?.variants ?? []; + return serverOffers.map((offer: IFunnelPaymentVariant, index: number) => { + // ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ Ρ‚ΠΎΠ²Π°Ρ€ ΠΈΠΌΠ΅Π΅Ρ‚ скидку 50%, ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅ - 45% + const discountPercent = index === 0 ? FIRST_PRODUCT_DISCOUNT : OTHER_PRODUCTS_DISCOUNT; + // РассчитываСм oldPrice: Ссли price это Ρ†Π΅Π½Π° со скидкой X%, Ρ‚ΠΎ oldPrice = price / (1 - X/100) + const oldPrice = Math.round(offer.price / (1 - discountPercent / 100)); + return { + ...offer, + oldPrice, + }; + }); }, [data]); const [activeOffer, setActiveOffer] = useState(""); const { setSelectedProduct } = useProductSelection(); diff --git a/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx index b1db972..338358f 100644 --- a/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx +++ b/src/components/domains/additional-purchases/VideoGuidesPage/VideoGuidesPage.tsx @@ -1,13 +1,16 @@ +"use client"; + import { Suspense } from "react"; import { useTranslations } from "next-intl"; import { + AdditionalPurchaseBanner, ProductSelectionProvider, Progress, - VideoGuidesBanner, VideoGuidesButton, VideoGuidesOffers, VideoGuidesOffersSkeleton, + useMultiPageNavigationContext, } from "@/components/domains/additional-purchases"; import { Typography } from "@/components/ui"; @@ -15,11 +18,20 @@ import styles from "./VideoGuidesPage.module.scss"; export default function VideoGuidesPage() { const t = useTranslations("AdditionalPurchases.video-guides"); + const { navigation } = useMultiPageNavigationContext(); + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ названия всСх страниц для прогрСсса + const progressItems = navigation.data.map((item: any) => { + return item.title || item.type || ""; + }); return ( - - + + {t("title")} diff --git a/src/components/domains/additional-purchases/index.ts b/src/components/domains/additional-purchases/index.ts index d71789b..2bd3916 100644 --- a/src/components/domains/additional-purchases/index.ts +++ b/src/components/domains/additional-purchases/index.ts @@ -2,6 +2,7 @@ export { default as AddConsultantButton } from "./AddConsultantButton/AddConsult export { default as AddConsultantPage } from "./AddConsultantPage/AddConsultantPage"; export { default as AddGuidesButton } from "./AddGuidesButton/AddGuidesButton"; export { default as AddGuidesPage } from "./AddGuidesPage/AddGuidesPage"; +export { default as AdditionalPurchaseBanner } from "./AdditionalPurchaseBanner/AdditionalPurchaseBanner"; export { default as Caution } from "./Caution/Caution"; export { default as ConsultationTable } from "./ConsultationTable/ConsultationTable"; export { diff --git a/src/entities/session/funnel/types.ts b/src/entities/session/funnel/types.ts index 9231dcd..3735dda 100644 --- a/src/entities/session/funnel/types.ts +++ b/src/entities/session/funnel/types.ts @@ -35,6 +35,7 @@ export const FunnelPaymentPlacementSchema = z.object({ variants: z.array(FunnelPaymentVariantSchema).optional(), paymentUrl: z.string().optional(), type: z.string().optional(), + title: z.string().optional(), }); export const FunnelSchema = z.object({ diff --git a/src/hooks/multiPages/useMultiPageNavigation.ts b/src/hooks/multiPages/useMultiPageNavigation.ts index c1680b8..fcb5919 100644 --- a/src/hooks/multiPages/useMultiPageNavigation.ts +++ b/src/hooks/multiPages/useMultiPageNavigation.ts @@ -16,6 +16,7 @@ interface PageNavigationOptions { } interface PageNavigationReturn { + data: T[]; currentItem: T | undefined; currentIndex: number; isFirst: boolean; @@ -124,6 +125,7 @@ export function useMultiPageNavigation({ return useMemo( () => ({ + data, currentItem, nextItem, currentIndex, @@ -141,6 +143,7 @@ export function useMultiPageNavigation({ totalPages, }), [ + data, currentItem, nextItem, currentIndex, From 95e05cbabb74a2791fa533e8761325d11d8ea4f7 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Tue, 28 Oct 2025 02:06:45 +0100 Subject: [PATCH 04/17] add video guide to dashboard --- messages/de.json | 38 +-- messages/en.json | 47 +--- messages/es.json | 38 +-- src/app/[locale]/(core)/page.tsx | 5 + .../VideoGuidesOffer.module.scss | 38 +-- .../VideoGuidesOffer/VideoGuidesOffer.tsx | 54 ++-- .../VideoGuidesOffers/VideoGuidesOffers.tsx | 3 +- .../VideoGuideCard/VideoGuideCard.module.scss | 236 ++++++++++++++++++ .../cards/VideoGuideCard/VideoGuideCard.tsx | 102 ++++++++ .../dashboard/cards/VideoGuideCard/index.ts | 1 + .../domains/dashboard/cards/index.ts | 1 + .../AdvisersSection/AdvisersSection.tsx | 7 +- .../CompatibilitySection.tsx | 7 +- .../MeditationSection/MeditationSection.tsx | 7 +- .../sections/PalmSection/PalmSection.tsx | 7 +- .../VideoGuidesSection.module.scss | 12 + .../VideoGuidesSection/VideoGuidesSection.tsx | 38 +++ .../sections/VideoGuidesSection/index.ts | 1 + .../domains/dashboard/sections/index.ts | 1 + src/entities/dashboard/loaders.ts | 11 +- src/entities/dashboard/types.ts | 25 +- src/entities/session/funnel/types.ts | 3 + 22 files changed, 510 insertions(+), 172 deletions(-) create mode 100644 src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.module.scss create mode 100644 src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.tsx create mode 100644 src/components/domains/dashboard/cards/VideoGuideCard/index.ts create mode 100644 src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss create mode 100644 src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx create mode 100644 src/components/domains/dashboard/sections/VideoGuidesSection/index.ts diff --git a/messages/de.json b/messages/de.json index 4c19c40..49c3b33 100644 --- a/messages/de.json +++ b/messages/de.json @@ -305,43 +305,7 @@ "button": "Continue", "skip_button": "Skip this offer and proceed further", "copyright": "Β© 2025, Wit Lab LLC, California, US", - "products": { - "main_ultra_pack": { - "title": "Ultra Pack", - "subtitle": "3 in 1+2 secret bonus readings", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "star_struck.webp" - }, - "main_numerology_analysis": { - "title": "Relationship plan", - "subtitle": "Discover the future without losing yourself", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "ring.webp" - }, - "main_tarot_reading": { - "title": "Healthy compatibility", - "subtitle": "Balance between closeness and freedom", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "rose.webp" - }, - "main_palmistry_guide": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - }, - "main_money_reading": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - } - }, + "now": "Now", "payment_error": "Something went wrong. Please try again later.", "select_product_error": "Please select a product" } diff --git a/messages/en.json b/messages/en.json index 94d7dd2..4ad28d3 100644 --- a/messages/en.json +++ b/messages/en.json @@ -312,43 +312,16 @@ "button": "Continue", "skip_button": "Skip this offer and proceed further", "copyright": "Β© 2025, Wit Lab LLC, California, US", - "products": { - "main_ultra_pack": { - "title": "Ultra Pack", - "subtitle": "3 in 1+2 secret bonus readings", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "star_struck.webp" - }, - "main_numerology_analysis": { - "title": "Relationship plan", - "subtitle": "Discover the future without losing yourself", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "ring.webp" - }, - "main_tarot_reading": { - "title": "Healthy compatibility", - "subtitle": "Balance between closeness and freedom", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "rose.webp" - }, - "main_palmistry_guide": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - }, - "main_money_reading": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - } - } + "now": "Now" + } + }, + "Dashboard": { + "adviser": { + "title": "Talk to an Astrologer" + }, + "videoGuides": { + "now": "Now", + "purchaseFor": "Buy for {price}" } }, "Chat": { diff --git a/messages/es.json b/messages/es.json index fefa091..614f564 100644 --- a/messages/es.json +++ b/messages/es.json @@ -305,43 +305,7 @@ "button": "Continue", "skip_button": "Skip this offer and proceed further", "copyright": "Β© 2025, Wit Lab LLC, California, US", - "products": { - "main_ultra_pack": { - "title": "Ultra Pack", - "subtitle": "3 in 1+2 secret bonus readings", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "star_struck.webp" - }, - "main_numerology_analysis": { - "title": "Relationship plan", - "subtitle": "Discover the future without losing yourself", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "ring.webp" - }, - "main_tarot_reading": { - "title": "Healthy compatibility", - "subtitle": "Balance between closeness and freedom", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "rose.webp" - }, - "main_palmistry_guide": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - }, - "main_money_reading": { - "title": "How to talk about feelings", - "subtitle": "Express your emotions and be understood", - "discount": "{discount}% OFF", - "price": "Now ", - "emoji": "heart_from_hands.webp" - } - }, + "now": "Now", "payment_error": "Something went wrong. Please try again later.", "select_product_error": "Please select a product" } diff --git a/src/app/[locale]/(core)/page.tsx b/src/app/[locale]/(core)/page.tsx index 4f4e81f..898b6e8 100644 --- a/src/app/[locale]/(core)/page.tsx +++ b/src/app/[locale]/(core)/page.tsx @@ -11,6 +11,7 @@ import { PalmSection, PalmSectionSkeleton, PortraitsSection, + VideoGuidesSection, } from "@/components/domains/dashboard"; import { loadChatsList } from "@/entities/chats/loaders"; import { @@ -19,6 +20,7 @@ import { loadMeditations, loadPalms, loadPortraits, + loadVideoGuides, } from "@/entities/dashboard/loaders"; import styles from "./page.module.scss"; @@ -26,11 +28,14 @@ import styles from "./page.module.scss"; export default async function Home() { const chatsPromise = loadChatsList(); const portraits = await loadPortraits(); + const videoGuides = await loadVideoGuides(); return (
+ + }> diff --git a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss index 67878d4..aa1012a 100644 --- a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss +++ b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.module.scss @@ -7,6 +7,25 @@ box-shadow: 0px 0px 30px 0px #0000001f; gap: 4px; + & > .topBadge { + position: absolute; + top: -15px; + right: 49px; + background: #ff3737; + box-shadow: 0px 1px 11.98px 0px #ff44448c; + border: 2px solid #ffffff4d; + padding: 8px 12px; + border-radius: 9999px; + z-index: 10; + + & > .topBadgeText { + color: #fff; + font-size: 14px; + font-weight: 800; + letter-spacing: 0.5px; + } + } + & > .content { display: grid; grid-template-columns: 40px 1fr 20px; @@ -109,23 +128,4 @@ } } } - - &.main_ultra_pack { - & > .footer { - & > .discount { - position: absolute; - top: -15px; - right: 49px; - background: #ff3737; - box-shadow: 0px 1px 11.98px 0px #ff44448c; - border: 2px solid #ffffff4d; - padding: 8px 12px; - color: #fff; - font-size: 14px; - font-weight: 800; - letter-spacing: 0.5px; - margin-right: 0; - } - } - } } diff --git a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx index 2ec300c..9ff7d27 100644 --- a/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx +++ b/src/components/domains/additional-purchases/VideoGuidesOffer/VideoGuidesOffer.tsx @@ -11,30 +11,25 @@ import styles from "./VideoGuidesOffer.module.scss"; interface VideoGuidesOfferProps { offer: IFunnelPaymentVariant; isActive: boolean; + isFirstOffer?: boolean; className?: string; onClick: () => void; } export default function VideoGuidesOffer(props: VideoGuidesOfferProps) { - const { offer, isActive, className, onClick } = props; + const { offer, isActive, isFirstOffer, className, onClick } = props; - const { key, price, oldPrice } = offer; + const { key, name, description, emoji, price, oldPrice } = offer; const productKey = key.replaceAll(".", "_"); - const t = useTranslations( - `AdditionalPurchases.video-guides.products.${productKey}` - ); - const currency = Currency.USD; - const subtitle = t.has("subtitle") ? t("subtitle") : undefined; - const discount = Math.round( (((oldPrice || 0) - price) / (oldPrice || 0)) * 100 ); - const emoji = t.has("emoji") ? t("emoji") : undefined; + const t = useTranslations("AdditionalPurchases.video-guides"); return ( + {isFirstOffer && ( +
+ {discount}% OFF +
+ )}
- {t("title")} + {name} - {subtitle && ( + {description && ( - {subtitle} + {description} )}
@@ -76,24 +76,20 @@ export default function VideoGuidesOffer(props: VideoGuidesOfferProps) {
- {t.rich("price", { - price: () => ( - - {getFormattedPrice(price, currency)} - - ), - oldPrice: () => ( - - {getFormattedPrice(oldPrice || 0, currency)} - - ), - })} - - - {t("discount", { - discount: discount || 0, - })} + {t("now")}{" "} + + {getFormattedPrice(price, currency)} + + {" "} + + {getFormattedPrice(oldPrice || 0, currency)} + + {!isFirstOffer && ( + + {discount}% OFF + + )}
); diff --git a/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx index 3b972d0..7bc3d3c 100644 --- a/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx +++ b/src/components/domains/additional-purchases/VideoGuidesOffers/VideoGuidesOffers.tsx @@ -49,11 +49,12 @@ export default function VideoGuidesOffers() { return (
- {offers.map(offer => ( + {offers.map((offer, index) => ( handleOfferClick(offer)} /> ))} diff --git a/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.module.scss b/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.module.scss new file mode 100644 index 0000000..96a3e87 --- /dev/null +++ b/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.module.scss @@ -0,0 +1,236 @@ +.container.container { + display: flex; + min-width: 260px; + min-height: 280px; + flex-direction: column; + align-items: flex-start; + border-radius: 24px; + border: 0 solid #E5E7EB; + background: rgba(0, 0, 0, 0); + box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.10), 0 10px 15px 0 rgba(0, 0, 0, 0.10); + cursor: pointer; + overflow: hidden; + padding: 0; + transition: transform 0.2s ease, box-shadow 0.2s ease; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.12), 0 12px 18px 0 rgba(0, 0, 0, 0.12); + } +} + +// Image section +.image { + display: flex; + min-height: 160px; + padding: 16px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + align-self: stretch; + border: 0 solid #E5E7EB; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + inset: 0; + background: lightgray 50% / cover no-repeat; + z-index: 0; + } + + .imageContent { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + pointer-events: none; + z-index: 0; + } + + .playIcon { + position: relative; + z-index: 1; + width: 64px; + height: 65px; + + svg { + width: 64px; + height: 65px; + filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.25)); + } + } +} + +// Content section +.content { + display: flex; + padding: 16px; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + gap: 24px; + align-self: stretch; + background: #FFF; + flex: 1; + + .purchased & { + gap: 6px; + } +} + +// Top section +.top { + display: flex; + align-items: flex-start; + gap: 14px; + align-self: stretch; + + .text { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 3px; + flex: 1 0 0; + } + + .arrowButton { + display: flex; + width: 40px; + height: 40px; + padding: 12px 0; + justify-content: center; + align-items: center; + border-radius: 9999px; + border: 0 solid #E5E7EB; + background: #F5F5F7; + cursor: pointer; + transition: opacity 0.2s ease; + + svg { + width: 8px; + height: 14px; + flex-shrink: 0; + } + + &:hover { + opacity: 0.8; + } + } +} + +.title { + align-self: stretch; + color: #1D1D1F; + font-family: Inter, sans-serif; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: 28px; + text-align: left; +} + +.subtitle { + align-self: stretch; + color: #6B7280; + font-family: Inter, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 20px; + text-align: left; +} + +// Bottom section +.bottom { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + gap: 8px; + align-self: stretch; +} + +.bottomText { + display: flex; + height: 24px; + justify-content: space-between; + align-items: center; + align-self: stretch; +} + +.duration { + display: flex; + width: 49px; + flex-direction: column; + justify-content: center; + align-self: stretch; + color: #6B7280; + font-family: Inter, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 20px; +} + +.discountBadge { + display: flex; + padding: 6px 10px; + justify-content: center; + align-items: center; + gap: 10px; + align-self: stretch; + border-radius: 9999px; + border: 0 solid #E5E7EB; + background: rgba(255, 107, 107, 0.10); +} + +.discountText { + color: #FF6B6B; + text-align: center; + font-family: Inter, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: normal; + + .oldPrice { + color: #8B8B8B; + font-family: Inter, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: normal; + text-decoration-line: line-through; + } +} + +.buyButton.buyButton { + display: flex; + padding: 8px 10px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 12px; + border: 0 solid #E5E7EB; + background: #2563EB; + cursor: pointer; + transition: opacity 0.2s ease; + width: auto; + + color: #FFF; + text-align: center; + font-family: Inter, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + + &:hover { + opacity: 0.9; + } +} diff --git a/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.tsx b/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.tsx new file mode 100644 index 0000000..2361c9a --- /dev/null +++ b/src/components/domains/dashboard/cards/VideoGuideCard/VideoGuideCard.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import clsx from "clsx"; + +import { Button, Card, Typography } from "@/components/ui"; +import { getFormattedPrice } from "@/shared/utils/price"; +import { Currency } from "@/types"; + +import styles from "./VideoGuideCard.module.scss"; + +interface VideoGuideCardProps { + name: string; + description: string; + imageUrl: string; + duration: string; + price: number; + oldPrice: number; + discount: number; + isPurchased: boolean; + className?: string; +} + +export default function VideoGuideCard(props: VideoGuideCardProps) { + const { name, description, imageUrl, duration, price, oldPrice, discount, isPurchased, className } = props; + + const tCommon = useTranslations("Dashboard.videoGuides"); + + const currency = Currency.USD; + + const handleClick = () => { + // TODO: Implement navigation or purchase logic + console.log("Video guide clicked", name); + }; + + return ( + + {/* Image with Play Icon */} +
+ {name} +
+ + + + + + + + + + + + + + + + + +
+
+ + {/* Content */} +
+ {/* Top Section */} +
+
+ + {name} + + + {description} + +
+ +
+ + {/* Bottom Section */} +
+
+ {duration} + {!isPurchased && ( +
+ + {discount}% OFF {getFormattedPrice(oldPrice, currency)} + +
+ )} +
+ {!isPurchased && ( + + )} +
+
+
+ ); +} diff --git a/src/components/domains/dashboard/cards/VideoGuideCard/index.ts b/src/components/domains/dashboard/cards/VideoGuideCard/index.ts new file mode 100644 index 0000000..e1d5d96 --- /dev/null +++ b/src/components/domains/dashboard/cards/VideoGuideCard/index.ts @@ -0,0 +1 @@ +export { default as VideoGuideCard } from "./VideoGuideCard"; diff --git a/src/components/domains/dashboard/cards/index.ts b/src/components/domains/dashboard/cards/index.ts index 65540f0..1a27805 100644 --- a/src/components/domains/dashboard/cards/index.ts +++ b/src/components/domains/dashboard/cards/index.ts @@ -3,3 +3,4 @@ export { default as CompatibilityCard } from "./CompatibilityCard/CompatibilityC export { default as MeditationCard } from "./MeditationCard/MeditationCard"; export { default as PalmCard } from "./PalmCard/PalmCard"; export { default as PortraitCard } from "./PortraitCard/PortraitCard"; +export { default as VideoGuideCard } from "./VideoGuideCard/VideoGuideCard"; diff --git a/src/components/domains/dashboard/sections/AdvisersSection/AdvisersSection.tsx b/src/components/domains/dashboard/sections/AdvisersSection/AdvisersSection.tsx index addacd3..b083fa3 100644 --- a/src/components/domains/dashboard/sections/AdvisersSection/AdvisersSection.tsx +++ b/src/components/domains/dashboard/sections/AdvisersSection/AdvisersSection.tsx @@ -31,7 +31,12 @@ export default function AdvisersSection({ }: AdvisersSectionProps) { const assistants = use(promiseAssistants); const chats = use(promiseChats); - const columns = getOptimalColumns(assistants?.length || 0); + + if (!assistants || assistants.length === 0) { + return null; + } + + const columns = getOptimalColumns(assistants.length); return (
diff --git a/src/components/domains/dashboard/sections/CompatibilitySection/CompatibilitySection.tsx b/src/components/domains/dashboard/sections/CompatibilitySection/CompatibilitySection.tsx index b0455c8..5289ff1 100644 --- a/src/components/domains/dashboard/sections/CompatibilitySection/CompatibilitySection.tsx +++ b/src/components/domains/dashboard/sections/CompatibilitySection/CompatibilitySection.tsx @@ -22,7 +22,12 @@ export default function CompatibilitySection({ gridDisplayMode = "horizontal", }: CompatibilitySectionProps) { const compatibilities = use(promise); - const columns = Math.ceil(compatibilities?.length / 2); + + if (!compatibilities || compatibilities.length === 0) { + return null; + } + + const columns = Math.ceil(compatibilities.length / 2); return (
diff --git a/src/components/domains/dashboard/sections/MeditationSection/MeditationSection.tsx b/src/components/domains/dashboard/sections/MeditationSection/MeditationSection.tsx index 45d9638..8e3de49 100644 --- a/src/components/domains/dashboard/sections/MeditationSection/MeditationSection.tsx +++ b/src/components/domains/dashboard/sections/MeditationSection/MeditationSection.tsx @@ -20,7 +20,12 @@ export default function MeditationSection({ gridDisplayMode = "horizontal", }: MeditationSectionProps) { const meditations = use(promise); - const columns = meditations?.length; + + if (!meditations || meditations.length === 0) { + return null; + } + + const columns = meditations.length; return (
diff --git a/src/components/domains/dashboard/sections/PalmSection/PalmSection.tsx b/src/components/domains/dashboard/sections/PalmSection/PalmSection.tsx index bffcc40..d6fa71a 100644 --- a/src/components/domains/dashboard/sections/PalmSection/PalmSection.tsx +++ b/src/components/domains/dashboard/sections/PalmSection/PalmSection.tsx @@ -15,7 +15,12 @@ export default function PalmSection({ promise: Promise; }) { const palms = use(promise); - const columns = palms?.length; + + if (!palms || palms.length === 0) { + return null; + } + + const columns = palms.length; return (
diff --git a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss new file mode 100644 index 0000000..d00ff19 --- /dev/null +++ b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss @@ -0,0 +1,12 @@ +.sectionContent.sectionContent { + overflow-x: scroll; + -webkit-overflow-scrolling: touch; + width: calc(100% + 32px); + padding: 20px 16px 24px 16px; + padding-right: 0; + margin: -20px -16px -24px -16px; +} + +.grid { + padding-right: 16px; +} diff --git a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx new file mode 100644 index 0000000..a4426b1 --- /dev/null +++ b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx @@ -0,0 +1,38 @@ +import { Grid, Section } from "@/components/ui"; +import { VideoGuide } from "@/entities/dashboard/types"; + +import { VideoGuideCard } from "../../cards"; + +import styles from "./VideoGuidesSection.module.scss"; + +interface VideoGuidesSectionProps { + videoGuides: VideoGuide[]; +} + +export default function VideoGuidesSection({ videoGuides }: VideoGuidesSectionProps) { + if (!videoGuides || videoGuides.length === 0) { + return null; + } + + const columns = videoGuides.length; + + return ( +
+ + {videoGuides.map(videoGuide => ( + + ))} + +
+ ); +} diff --git a/src/components/domains/dashboard/sections/VideoGuidesSection/index.ts b/src/components/domains/dashboard/sections/VideoGuidesSection/index.ts new file mode 100644 index 0000000..444247a --- /dev/null +++ b/src/components/domains/dashboard/sections/VideoGuidesSection/index.ts @@ -0,0 +1 @@ +export { default as VideoGuidesSection } from "./VideoGuidesSection"; diff --git a/src/components/domains/dashboard/sections/index.ts b/src/components/domains/dashboard/sections/index.ts index 989e957..bc600d1 100644 --- a/src/components/domains/dashboard/sections/index.ts +++ b/src/components/domains/dashboard/sections/index.ts @@ -20,3 +20,4 @@ export { PalmSectionSkeleton, } from "./PalmSection/PalmSection"; export { default as PortraitsSection } from "./PortraitsSection/PortraitsSection"; +export { default as VideoGuidesSection } from "./VideoGuidesSection/VideoGuidesSection"; diff --git a/src/entities/dashboard/loaders.ts b/src/entities/dashboard/loaders.ts index c1d5fa0..af0a7e2 100644 --- a/src/entities/dashboard/loaders.ts +++ b/src/entities/dashboard/loaders.ts @@ -5,15 +5,18 @@ import { getDashboard } from "./api"; export const loadDashboard = cache(getDashboard); export const loadAssistants = cache(() => - loadDashboard().then(d => d.assistants) + loadDashboard().then(d => d.assistants || []) ); export const loadCompatibility = cache(() => - loadDashboard().then(d => d.compatibilityActions) + loadDashboard().then(d => d.compatibilityActions || []) ); export const loadMeditations = cache(() => - loadDashboard().then(d => d.meditations) + loadDashboard().then(d => d.meditations || []) ); -export const loadPalms = cache(() => loadDashboard().then(d => d.palmActions)); +export const loadPalms = cache(() => loadDashboard().then(d => d.palmActions || [])); export const loadPortraits = cache(() => loadDashboard().then(d => d.partnerPortraits || []) ); +export const loadVideoGuides = cache(() => + loadDashboard().then(d => d.videoGuides || []) +); diff --git a/src/entities/dashboard/types.ts b/src/entities/dashboard/types.ts index 29a784e..ddb413e 100644 --- a/src/entities/dashboard/types.ts +++ b/src/entities/dashboard/types.ts @@ -61,12 +61,29 @@ export const PartnerPortraitSchema = z.object({ }); export type PartnerPortrait = z.infer; +/* ---------- Video Guide ---------- */ +export const VideoGuideSchema = z.object({ + id: z.string(), + key: z.string(), + type: z.string(), + name: z.string(), + description: z.string(), + imageUrl: z.string(), + duration: z.string(), + price: z.number(), + oldPrice: z.number(), + discount: z.number(), + isPurchased: z.boolean(), +}); +export type VideoGuide = z.infer; + /* ---------- Π˜Ρ‚ΠΎΠ³ΠΎΠ²Ρ‹ΠΉ ΠΎΡ‚Π²Π΅Ρ‚ /dashboard ---------- */ export const DashboardSchema = z.object({ - assistants: z.array(AssistantSchema), - compatibilityActions: z.array(ActionSchema), - palmActions: z.array(ActionSchema), - meditations: z.array(ActionSchema), + assistants: z.array(AssistantSchema).optional(), + compatibilityActions: z.array(ActionSchema).optional(), + palmActions: z.array(ActionSchema).optional(), + meditations: z.array(ActionSchema).optional(), partnerPortraits: z.array(PartnerPortraitSchema).optional(), + videoGuides: z.array(VideoGuideSchema).optional(), }); export type DashboardData = z.infer; diff --git a/src/entities/session/funnel/types.ts b/src/entities/session/funnel/types.ts index 3735dda..8bc3cf6 100644 --- a/src/entities/session/funnel/types.ts +++ b/src/entities/session/funnel/types.ts @@ -17,6 +17,9 @@ export const FunnelPaymentVariantSchema = z.object({ id: z.string(), key: z.string(), type: z.string(), + name: z.string().optional(), + description: z.string().optional(), + emoji: z.string().optional(), price: z.number(), oldPrice: z.number().optional(), trialPrice: z.number().optional(), From 1adac2836bc4c0d3e409ea728bf16f0d724a7dcd Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Tue, 28 Oct 2025 05:58:51 +0100 Subject: [PATCH 05/17] add video --- src/app/[locale]/(core)/page.tsx | 3 + .../(core)/video-guides/[id]/layout.tsx | 7 + .../(core)/video-guides/[id]/loading.tsx | 22 +++ .../(core)/video-guides/[id]/page.tsx | 38 +++++ .../AddConsultantButton.tsx | 24 ++- .../AddGuidesButton/AddGuidesButton.tsx | 22 ++- .../Progress/Progress.module.scss | 52 +++++- .../Progress/Progress.tsx | 154 +++++++++++++----- .../VideoGuidesButton/VideoGuidesButton.tsx | 22 ++- .../GlobalNewMessagesBanner.tsx | 5 +- .../VideoGuideCard/VideoGuideCard.module.scss | 35 +++- .../cards/VideoGuideCard/VideoGuideCard.tsx | 73 ++++++--- .../VideoGuidesSection.module.scss | 8 + .../VideoGuidesSection/VideoGuidesSection.tsx | 60 ++++++- .../PortraitView/PortraitView.module.scss | 2 + .../VideoGuideView/VideoGuideView.module.scss | 96 +++++++++++ .../VideoGuideView/VideoGuideView.tsx | 76 +++++++++ src/components/domains/video-guides/index.ts | 1 + src/components/layout/Header/Header.tsx | 5 +- .../layout/NavigationBar/NavigationBar.tsx | 5 +- src/entities/dashboard/actions.ts | 38 +++++ src/entities/dashboard/api.ts | 2 +- src/entities/dashboard/loaders.ts | 38 ++--- src/entities/dashboard/types.ts | 1 + src/hooks/payment/useSingleCheckout.ts | 17 +- src/hooks/video-guides/index.ts | 1 + .../video-guides/useVideoGuidePurchase.ts | 117 +++++++++++++ src/shared/constants/api-routes.ts | 2 + 28 files changed, 802 insertions(+), 124 deletions(-) create mode 100644 src/app/[locale]/(core)/video-guides/[id]/layout.tsx create mode 100644 src/app/[locale]/(core)/video-guides/[id]/loading.tsx create mode 100644 src/app/[locale]/(core)/video-guides/[id]/page.tsx create mode 100644 src/components/domains/video-guides/VideoGuideView/VideoGuideView.module.scss create mode 100644 src/components/domains/video-guides/VideoGuideView/VideoGuideView.tsx create mode 100644 src/components/domains/video-guides/index.ts create mode 100644 src/entities/dashboard/actions.ts create mode 100644 src/hooks/video-guides/index.ts create mode 100644 src/hooks/video-guides/useVideoGuidePurchase.ts diff --git a/src/app/[locale]/(core)/page.tsx b/src/app/[locale]/(core)/page.tsx index 898b6e8..b701641 100644 --- a/src/app/[locale]/(core)/page.tsx +++ b/src/app/[locale]/(core)/page.tsx @@ -25,6 +25,9 @@ import { import styles from "./page.module.scss"; +// Force dynamic to always get fresh data +export const dynamic = "force-dynamic"; + export default async function Home() { const chatsPromise = loadChatsList(); const portraits = await loadPortraits(); diff --git a/src/app/[locale]/(core)/video-guides/[id]/layout.tsx b/src/app/[locale]/(core)/video-guides/[id]/layout.tsx new file mode 100644 index 0000000..ef8c04f --- /dev/null +++ b/src/app/[locale]/(core)/video-guides/[id]/layout.tsx @@ -0,0 +1,7 @@ +export default function VideoGuideLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/[locale]/(core)/video-guides/[id]/loading.tsx b/src/app/[locale]/(core)/video-guides/[id]/loading.tsx new file mode 100644 index 0000000..ef6ee0e --- /dev/null +++ b/src/app/[locale]/(core)/video-guides/[id]/loading.tsx @@ -0,0 +1,22 @@ +import { Spinner } from "@/components/ui"; + +export default function Loading() { + return ( +
+ +
+ ); +} diff --git a/src/app/[locale]/(core)/video-guides/[id]/page.tsx b/src/app/[locale]/(core)/video-guides/[id]/page.tsx new file mode 100644 index 0000000..dfc43ea --- /dev/null +++ b/src/app/[locale]/(core)/video-guides/[id]/page.tsx @@ -0,0 +1,38 @@ +import { notFound } from "next/navigation"; + +import { VideoGuideView } from "@/components/domains/video-guides"; +import { DashboardData, DashboardSchema } from "@/entities/dashboard/types"; +import { http } from "@/shared/api/httpClient"; +import { API_ROUTES } from "@/shared/constants/api-routes"; + +// Force dynamic to always get fresh data +export const dynamic = "force-dynamic"; + +export default async function VideoGuidePage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + + // Get fresh dashboard data without cache + const dashboard = await http.get(API_ROUTES.dashboard(), { + cache: "no-store", + schema: DashboardSchema, + }); + + const videoGuide = dashboard.videoGuides?.find(v => v.id === id); + + if (!videoGuide || !videoGuide.isPurchased || !videoGuide.videoLink) { + notFound(); + } + + return ( + + ); +} diff --git a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx index c73c6f5..88b1459 100644 --- a/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx +++ b/src/components/domains/additional-purchases/AddConsultantButton/AddConsultantButton.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState } from "react"; import { useTranslations } from "next-intl"; import { Button, Spinner, Typography } from "@/components/ui"; @@ -17,12 +18,22 @@ export default function AddConsultantButton() { const { addToast } = useToast(); const { navigation } = useMultiPageNavigationContext(); const data = navigation.currentItem; + const [isNavigating, setIsNavigating] = useState(false); const product = data?.variants?.[0]; const { handleSingleCheckout, isLoading } = useSingleCheckout({ - onSuccess: () => { - navigation.goToNext(); + onSuccess: async () => { + // УстанавливаСм Ρ„Π»Π°Π³ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½Ρ‹Π΅ наТатия + setIsNavigating(true); + + // ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ Π½Π° ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΡƒΡŽ страницу ΠΈΠ»ΠΈ Π½Π° Π³Π»Π°Π²Π½ΡƒΡŽ + if (navigation.hasNext) { + await navigation.goToNext(); + } else { + // Если это послСдний экран - ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ Π½Π° Π΄Π°ΡˆΠ±ΠΎΡ€Π΄ + window.location.href = ROUTES.home(); + } }, onError: _error => { addToast({ @@ -57,14 +68,17 @@ export default function AddConsultantButton() { navigation.goToNext(); }; + // Π‘Π»ΠΎΠΊΠΈΡ€ΡƒΠ΅ΠΌ ΠΊΠ½ΠΎΠΏΠΊΡƒ Π²ΠΎ врСмя Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π˜Π›Π˜ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ + const isButtonDisabled = isLoading || isNavigating || !product; + return (
- + {isPurchased && ( + + )}
{/* Bottom Section */}
-
- {duration} - {!isPurchased && ( -
- - {discount}% OFF {getFormattedPrice(oldPrice, currency)} - + {!isPurchased ? ( + <> +
+ {duration} +
+ + {discount}% OFF {getFormattedPrice(oldPrice, currency)} + +
- )} -
- {!isPurchased && ( - + + + ) : ( + {duration} )}
diff --git a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss index d00ff19..1cd8a02 100644 --- a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss +++ b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.module.scss @@ -9,4 +9,12 @@ .grid { padding-right: 16px; + grid-auto-rows: 1fr; + + a, > div { + text-decoration: none; + color: inherit; + display: block; + height: 100%; + } } diff --git a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx index a4426b1..5fad724 100644 --- a/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx +++ b/src/components/domains/dashboard/sections/VideoGuidesSection/VideoGuidesSection.tsx @@ -1,5 +1,10 @@ +"use client"; + +import Link from "next/link"; + import { Grid, Section } from "@/components/ui"; import { VideoGuide } from "@/entities/dashboard/types"; +import { useVideoGuidePurchase } from "@/hooks/video-guides/useVideoGuidePurchase"; import { VideoGuideCard } from "../../cards"; @@ -9,6 +14,50 @@ interface VideoGuidesSectionProps { videoGuides: VideoGuide[]; } +function VideoGuideCardWrapper({ videoGuide }: { videoGuide: VideoGuide }) { + const { handlePurchase, isCheckoutLoading, isProcessingPurchase } = useVideoGuidePurchase({ + videoGuideId: videoGuide.id, + productId: videoGuide.id, + productKey: videoGuide.key, + }); + + // Для ΠΊΡƒΠΏΠ»Π΅Π½Π½Ρ‹Ρ… Π²ΠΈΠ΄Π΅ΠΎ - ссылка Π½Π° страницу просмотра + const href = videoGuide.isPurchased && videoGuide.videoLink + ? `/video-guides/${videoGuide.id}` + : '#'; + + const isClickable = videoGuide.isPurchased && videoGuide.videoLink; + + const cardElement = ( + + ); + + if (isClickable) { + return ( + + {cardElement} + + ); + } + + return
{cardElement}
; +} + export default function VideoGuidesSection({ videoGuides }: VideoGuidesSectionProps) { if (!videoGuides || videoGuides.length === 0) { return null; @@ -20,16 +69,9 @@ export default function VideoGuidesSection({ videoGuides }: VideoGuidesSectionPr
{videoGuides.map(videoGuide => ( - ))} diff --git a/src/components/domains/portraits/PortraitView/PortraitView.module.scss b/src/components/domains/portraits/PortraitView/PortraitView.module.scss index 677f344..e36f756 100644 --- a/src/components/domains/portraits/PortraitView/PortraitView.module.scss +++ b/src/components/domains/portraits/PortraitView/PortraitView.module.scss @@ -7,6 +7,8 @@ display: flex; flex-direction: column; overflow: hidden; + z-index: 1000; + background: var(--background); } .header { diff --git a/src/components/domains/video-guides/VideoGuideView/VideoGuideView.module.scss b/src/components/domains/video-guides/VideoGuideView/VideoGuideView.module.scss new file mode 100644 index 0000000..943df36 --- /dev/null +++ b/src/components/domains/video-guides/VideoGuideView/VideoGuideView.module.scss @@ -0,0 +1,96 @@ +.container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 1000; + background: var(--background); +} + +.header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + background: var(--background); + position: relative; + flex-shrink: 0; +} + +.backButton { + width: 40px; + height: 40px; + border-radius: 50%; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; + border: none; + cursor: pointer; + flex-shrink: 0; + transition: background 0.2s ease; + + &:hover { + background: #e0e0e0; + } + + &:active { + background: #d0d0d0; + } +} + +.title { + flex: 1; + text-align: center; + padding-right: 40px; // Compensate for back button width +} + +.contentWrapper { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 24px; + overflow-y: auto; + gap: 32px; +} + +.videoContainer { + position: relative; + width: 100%; + max-width: 800px; + aspect-ratio: 16 / 9; + border-radius: 24px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.videoInner { + position: relative; + width: 100%; + height: 100%; +} + +.video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} + +.descriptionWrapper { + width: 100%; + max-width: 800px; + padding: 0; +} + +.description { + color: #646464; + line-height: 1.6; +} diff --git a/src/components/domains/video-guides/VideoGuideView/VideoGuideView.tsx b/src/components/domains/video-guides/VideoGuideView/VideoGuideView.tsx new file mode 100644 index 0000000..b858029 --- /dev/null +++ b/src/components/domains/video-guides/VideoGuideView/VideoGuideView.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { Icon, IconName, Typography } from "@/components/ui"; + +import styles from "./VideoGuideView.module.scss"; + +interface VideoGuideViewProps { + id: string; + name: string; + description: string; + videoLink: string; +} + +export default function VideoGuideView({ name, description, videoLink }: VideoGuideViewProps) { + const router = useRouter(); + + // Extract video ID from various YouTube URL formats + const getYouTubeVideoId = (url: string): string | null => { + const patterns = [ + /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, + /^([a-zA-Z0-9_-]{11})$/ // Direct video ID + ]; + + for (const pattern of patterns) { + const match = url.match(pattern); + if (match) return match[1]; + } + return null; + }; + + const videoId = getYouTubeVideoId(videoLink); + const embedUrl = videoId + ? `https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1` + : videoLink; + + return ( +
+ {/* Header with back button and title */} +
+ + + {name} + +
+ + {/* Video and Description */} +
+ {/* Video Player */} +
+
+