Browse Source

remove video

master
mortezaei 10 hours ago
parent
commit
28e8259b34
  1. 76
      src/app/intro/intro-client.test.tsx
  2. 35
      src/app/intro/intro-client.tsx
  3. 32
      src/app/request-accepted/request-accepted-client.tsx
  4. 3
      src/components/Componentes/slider-header.tsx
  5. 14
      src/components/Componentes/slider-page.test.tsx
  6. 21
      src/components/Componentes/slider-page.tsx
  7. 4
      src/components/Componentes/slider-slide-five.tsx
  8. 3
      src/components/Componentes/slider-slide-four.tsx
  9. 4
      src/components/Componentes/slider-slide-one.tsx
  10. 3
      src/components/Componentes/slider-slide-three.tsx
  11. 4
      src/components/Componentes/slider-slide-two.tsx
  12. 1
      src/components/Componentes/slider-slide.tsx
  13. 10
      src/config/features.ts

76
src/app/intro/intro-client.test.tsx

@ -0,0 +1,76 @@
import { render, screen, cleanup } from "@testing-library/react";
import { describe, it, expect, vi, afterEach } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import IntroClient from "./intro-client";
vi.mock("next/navigation", () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
back: vi.fn(),
}),
}));
vi.mock("@/translations/provider", () => ({
useI18n: () => ({
locale: "en",
dictionary: {
"heavenly marriage": "heavenly marriage",
"A Path to Heavenly Marriage": "A Path to Heavenly Marriage",
'We have come together with the goal of creating a secure and confidential path for "permanent marriage" among Muslims':
'We have come together with the goal of creating a secure and confidential path for "permanent marriage" among Muslims',
"user profiles": "user profiles",
matches: "matches",
marriages: "marriages",
Submit: "Submit",
video: "video",
},
}),
}));
vi.mock("@/hooks/marriage/use-profile-main", () => ({
useMarriageProfileQuery: () => ({
data: undefined,
refetch: vi.fn(),
}),
}));
vi.mock("@/hooks/marriage/use-marriage-config", () => ({
useMarriageConfigQuery: () => ({
data: {
intro_video_url: "https://example.com/intro.mp4",
intro_video_thumbnail_url: "/assets/images/Frame 2095586523.png",
},
}),
}));
vi.mock("@/hooks/use-habib-web-ready", () => ({
useHabibWebReady: vi.fn(),
}));
vi.mock("@/hooks/use-hardware-back-handler", () => ({
useHardwareBackHandler: vi.fn(),
}));
describe("IntroClient", () => {
afterEach(() => {
cleanup();
});
it("renders intro page without video card when SHOW_INTRO_VIDEOS is false", () => {
const queryClient = new QueryClient();
render(
<QueryClientProvider client={queryClient}>
<IntroClient />
</QueryClientProvider>,
);
expect(screen.getByText("A Path to Heavenly Marriage")).toBeInTheDocument();
expect(screen.getByText("120")).toBeInTheDocument();
expect(screen.getByText("user profiles")).toBeInTheDocument();
expect(screen.getByText("Submit")).toBeInTheDocument();
// Video thumbnail image should NOT be rendered
expect(screen.queryByAltText("video")).not.toBeInTheDocument();
});
});

35
src/app/intro/intro-client.tsx

@ -20,6 +20,7 @@ import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { useHabibWebReady } from "@/hooks/use-habib-web-ready"; import { useHabibWebReady } from "@/hooks/use-habib-web-ready";
import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler"; import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler";
import { authBridge } from "@/lib/auth-bridge"; import { authBridge } from "@/lib/auth-bridge";
import { SHOW_INTRO_VIDEOS } from "@/config/features";
import { getSubmitPath } from "@/lib/get-submit-path"; import { getSubmitPath } from "@/lib/get-submit-path";
import { localizePath } from "@/translations/config"; import { localizePath } from "@/translations/config";
import { useI18n } from "@/translations/provider"; import { useI18n } from "@/translations/provider";
@ -125,10 +126,27 @@ export default function IntroClient() {
return ( return (
<> <>
<div className="pt-[max(12px,calc(var(--safe-top)+4px))]">
<div
className={[
"pt-[max(12px,calc(var(--safe-top)+4px))]",
SHOW_INTRO_VIDEOS ? "" : "flex min-h-[100dvh] flex-col",
].join(" ")}
>
<PageHeader profile={profile} /> <PageHeader profile={profile} />
<main className="pb-[calc(90px+var(--safe-bottom))]">
<div className="flex flex-col items-center mt-16">
<main
className={[
"pb-[calc(90px+var(--safe-bottom))]",
SHOW_INTRO_VIDEOS
? ""
: "my-auto flex flex-1 flex-col justify-center",
].join(" ")}
>
<div
className={[
"flex flex-col items-center",
SHOW_INTRO_VIDEOS ? "mt-16" : "mt-0",
].join(" ")}
>
<Image <Image
src={"/assets/images/Group 1597880466.svg"} src={"/assets/images/Group 1597880466.svg"}
alt={t["heavenly marriage"]} alt={t["heavenly marriage"]}
@ -148,7 +166,12 @@ export default function IntroClient() {
} }
</p> </p>
</div> </div>
<div className="flex items-center justify-between mt-4">
<div
className={[
"flex items-center justify-between",
SHOW_INTRO_VIDEOS ? "mt-4" : "mt-8",
].join(" ")}
>
<div className="flex items-center gap-0.5 p-2 border border-[#FD6ABB]/10 rounded-2xl"> <div className="flex items-center gap-0.5 p-2 border border-[#FD6ABB]/10 rounded-2xl">
<IntroUsersIcon /> <IntroUsersIcon />
<div className="flex flex-col items-start justify-start -mb-2"> <div className="flex flex-col items-start justify-start -mb-2">
@ -177,6 +200,8 @@ export default function IntroClient() {
</div> </div>
</div> </div>
</div> </div>
{SHOW_INTRO_VIDEOS && (
<>
<div <div
className="mt-14 relative cursor-pointer group rounded-2xl overflow-hidden aspect-[344/221] max-w-[344px] w-full mx-auto" className="mt-14 relative cursor-pointer group rounded-2xl overflow-hidden aspect-[344/221] max-w-[344px] w-full mx-auto"
onClick={() => setIsPlayerOpen(true)} onClick={() => setIsPlayerOpen(true)}
@ -198,6 +223,8 @@ export default function IntroClient() {
onClose={() => setIsPlayerOpen(false)} onClose={() => setIsPlayerOpen(false)}
videoUrl={config?.intro_video_url} videoUrl={config?.intro_video_url}
/> />
</>
)}
<div className="pointer-events-none fixed bottom-0 left-1/2 z-20 w-full sm:max-w-[375px] -translate-x-1/2 bg-[#F5F5F5]"> <div className="pointer-events-none fixed bottom-0 left-1/2 z-20 w-full sm:max-w-[375px] -translate-x-1/2 bg-[#F5F5F5]">
<div <div
style={{ paddingBottom: "calc(20px + var(--safe-bottom))" }} style={{ paddingBottom: "calc(20px + var(--safe-bottom))" }}

32
src/app/request-accepted/request-accepted-client.tsx

@ -249,8 +249,13 @@ export default function RequestAcceptedClient() {
}, },
}, },
); );
const [isFetchingContact, setIsFetchingContact] = useState(false);
const isMalePaymentDone =
!isFemaleProfile &&
(caseStatus === "payment_done" || caseStatus === "contacted");
const contactInfoQuery = useMarriageContactInfoQuery(caseId, { const contactInfoQuery = useMarriageContactInfoQuery(caseId, {
enabled: false,
enabled: !!caseId && isMalePaymentDone,
}); });
if ((isLoading && !profile) || !profile) { if ((isLoading && !profile) || !profile) {
@ -300,7 +305,12 @@ export default function RequestAcceptedClient() {
} }
if (!contactInfoQuery.data) { if (!contactInfoQuery.data) {
try {
setIsFetchingContact(true);
await contactInfoQuery.refetch(); await contactInfoQuery.refetch();
} finally {
setIsFetchingContact(false);
}
} }
setIsContactInfoSheetOpen(true); setIsContactInfoSheetOpen(true);
@ -693,11 +703,17 @@ export default function RequestAcceptedClient() {
onClick={() => { onClick={() => {
void handleSecondaryAction(); void handleSecondaryAction();
}} }}
disabled={paymentMutation.isPending}
disabled={
paymentMutation.isPending ||
isFetchingContact ||
(isMalePaymentDone &&
contactInfoQuery.isFetching &&
!contactInfoQuery.data)
}
className={ className={
isFemaleProfile isFemaleProfile
? "flex-1 min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[13.5px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm transition-all cursor-pointer hover:opacity-95 active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
: "flex-1 max-w-[170px] min-h-[48px] appearance-none border-0 bg-transparent p-0 text-center cursor-pointer transition-transform active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
? "flex-1 min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[13.5px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm transition-all duration-150 cursor-pointer hover:opacity-95 active:scale-[0.96] disabled:opacity-50 disabled:cursor-not-allowed"
: "flex-1 max-w-[170px] min-h-[48px] appearance-none border-0 bg-transparent p-0 text-center cursor-pointer transition-transform duration-150 active:scale-[0.96] disabled:opacity-50 disabled:cursor-not-allowed"
} }
> >
{isFemaleProfile ? ( {isFemaleProfile ? (
@ -707,8 +723,12 @@ export default function RequestAcceptedClient() {
secondaryActionText secondaryActionText
) )
) : ( ) : (
<div className="w-full h-full min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm hover:opacity-95">
{paymentMutation.isPending ? (
<div className="w-full h-full min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm hover:opacity-95 active:shadow-inner">
{paymentMutation.isPending ||
isFetchingContact ||
(isMalePaymentDone &&
contactInfoQuery.isFetching &&
!contactInfoQuery.data) ? (
<LoadingThreeDot className="text-white" /> <LoadingThreeDot className="text-white" />
) : ( ) : (
secondaryActionText secondaryActionText

3
src/components/Componentes/slider-header.tsx

@ -1,4 +1,5 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { SHOW_INTRO_VIDEOS } from "@/config/features";
type SliderHeaderProps = { type SliderHeaderProps = {
index: number; index: number;
@ -10,7 +11,7 @@ type SliderHeaderProps = {
export function SliderHeader({ export function SliderHeader({
index, index,
title, title,
totalSteps = 5,
totalSteps = SHOW_INTRO_VIDEOS ? 5 : 4,
stepLabel, stepLabel,
}: SliderHeaderProps) { }: SliderHeaderProps) {
return ( return (

14
src/components/Componentes/slider-page.test.tsx

@ -76,10 +76,22 @@ describe('SliderPage', () => {
fireEvent.click(acceptBtn); fireEvent.click(acceptBtn);
} }
const dots = screen.getAllByRole('button', { name: /go to slide/i }); const dots = screen.getAllByRole('button', { name: /go to slide/i });
fireEvent.click(dots[4]); // Go directly to slide 5
fireEvent.click(dots[dots.length - 1]); // Go directly to final slide
return await screen.findByText('Finish'); return await screen.findByText('Finish');
}; };
it('renders 4 dots and skips video slide when SHOW_INTRO_VIDEOS is false', () => {
render(
<QueryClientProvider client={queryClient}>
<SliderPage />
</QueryClientProvider>
);
const dots = screen.getAllByRole('button', { name: /go to slide/i });
expect(dots).toHaveLength(4);
expect(screen.queryByText('Watch Video')).not.toBeInTheDocument();
});
it('a. Success: PATCH ok, GET ok -> replace', async () => { it('a. Success: PATCH ok, GET ok -> replace', async () => {
const finishBtn = await navigateToFinalSlide(); const finishBtn = await navigateToFinalSlide();
const initialRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length; const initialRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;

21
src/components/Componentes/slider-page.tsx

@ -14,13 +14,14 @@ import Button from "./button";
import ErrorToast from "./error-toast"; import ErrorToast from "./error-toast";
import NavigationButton from "./navigation-button"; import NavigationButton from "./navigation-button";
import type { GenderAnswer, RegistrationAnswer } from "./slider-slide"; import type { GenderAnswer, RegistrationAnswer } from "./slider-slide";
import { SHOW_INTRO_VIDEOS } from "@/config/features";
import { SliderSlideFive } from "./slider-slide-five"; import { SliderSlideFive } from "./slider-slide-five";
import { SliderSlideFour } from "./slider-slide-four"; import { SliderSlideFour } from "./slider-slide-four";
import { SliderSlideOne, SliderSlideOneActions } from "./slider-slide-one"; import { SliderSlideOne, SliderSlideOneActions } from "./slider-slide-one";
import { SliderSlideThree } from "./slider-slide-three"; import { SliderSlideThree } from "./slider-slide-three";
import { SliderSlideTwo } from "./slider-slide-two"; import { SliderSlideTwo } from "./slider-slide-two";
const FINAL_SLIDE_COUNT = 5;
const FINAL_SLIDE_COUNT = SHOW_INTRO_VIDEOS ? 5 : 4;
const SLIDER_ANSWERS_STORAGE_KEY = "marriage-slider-answers"; const SLIDER_ANSWERS_STORAGE_KEY = "marriage-slider-answers";
export type SliderPageProps = { export type SliderPageProps = {
@ -192,20 +193,30 @@ export default function SliderPage({ onClose }: SliderPageProps = {}) {
> >
<SliderSlideOne <SliderSlideOne
index={0} index={0}
totalSteps={displaySlideCount}
onScrollToEnd={() => setHasReadRules(true)} onScrollToEnd={() => setHasReadRules(true)}
/> />
<SliderSlideTwo index={1} />
{SHOW_INTRO_VIDEOS && (
<SliderSlideTwo index={1} totalSteps={displaySlideCount} />
)}
<SliderSlideThree <SliderSlideThree
index={2}
index={SHOW_INTRO_VIDEOS ? 2 : 1}
totalSteps={displaySlideCount}
selectedGender={selectedGender} selectedGender={selectedGender}
onGenderChange={setSelectedGender} onGenderChange={setSelectedGender}
/> />
<SliderSlideFour <SliderSlideFour
index={3}
index={SHOW_INTRO_VIDEOS ? 3 : 2}
totalSteps={displaySlideCount}
selectedRegistration={selectedRegistration} selectedRegistration={selectedRegistration}
onRegistrationChange={setSelectedRegistration} onRegistrationChange={setSelectedRegistration}
/> />
{hasFinalNotice ? <SliderSlideFive index={4} /> : null}
{hasFinalNotice ? (
<SliderSlideFive
index={SHOW_INTRO_VIDEOS ? 4 : 3}
totalSteps={displaySlideCount}
/>
) : null}
</div> </div>
</main> </main>

4
src/components/Componentes/slider-slide-five.tsx

@ -10,14 +10,14 @@ const NOTICE_ITEMS = [
"We provide a safe and respectful environment at every step.", "We provide a safe and respectful environment at every step.",
]; ];
export function SliderSlideFive({ index }: SliderSlideProps) {
export function SliderSlideFive({ index, totalSteps }: SliderSlideProps) {
return ( return (
<section <section
aria-label={`Slide ${index + 1}`} aria-label={`Slide ${index + 1}`}
className="flex h-full min-h-0 w-full shrink-0 flex-col" className="flex h-full min-h-0 w-full shrink-0 flex-col"
> >
<div className="min-h-0 flex-1 overflow-y-auto pb-28"> <div className="min-h-0 flex-1 overflow-y-auto pb-28">
<SliderHeader index={index} totalSteps={5} title="Final Notice" />
<SliderHeader index={index} totalSteps={totalSteps} title="Final Notice" />
<div className="mt-6 space-y-5"> <div className="mt-6 space-y-5">
<p className="text-[#36363C] text-sm font-semibold leading-5"> <p className="text-[#36363C] text-sm font-semibold leading-5">

3
src/components/Componentes/slider-slide-four.tsx

@ -11,6 +11,7 @@ type SliderSlideFourProps = SliderSlideProps & {
export function SliderSlideFour({ export function SliderSlideFour({
index, index,
totalSteps,
selectedRegistration, selectedRegistration,
onRegistrationChange, onRegistrationChange,
}: SliderSlideFourProps) { }: SliderSlideFourProps) {
@ -25,7 +26,7 @@ export function SliderSlideFour({
className="flex h-full min-h-0 w-full shrink-0 flex-col" className="flex h-full min-h-0 w-full shrink-0 flex-col"
> >
<div className="min-h-0 flex-1 overflow-y-auto pb-28"> <div className="min-h-0 flex-1 overflow-y-auto pb-28">
<SliderHeader index={index} title="Registration Type" />
<SliderHeader index={index} totalSteps={totalSteps} title="Registration Type" />
<NoticeBox className="mt-8" align="justify"> <NoticeBox className="mt-8" align="justify">
You can register both for yourself and for your loved ones, but You can register both for yourself and for your loved ones, but
obtaining the explicit consent and permission of the person you are obtaining the explicit consent and permission of the person you are

4
src/components/Componentes/slider-slide-one.tsx

@ -12,7 +12,7 @@ type SliderSlideOneProps = SliderSlideProps & {
onScrollToEnd?: () => void; onScrollToEnd?: () => void;
}; };
export function SliderSlideOne({ index, onScrollToEnd }: SliderSlideOneProps) {
export function SliderSlideOne({ index, totalSteps, onScrollToEnd }: SliderSlideOneProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
@ -59,7 +59,7 @@ export function SliderSlideOne({ index, onScrollToEnd }: SliderSlideOneProps) {
maskImage: CONTENT_MASK, maskImage: CONTENT_MASK,
}} }}
> >
<SliderHeader index={index} title="Terms & Conditions" />
<SliderHeader index={index} totalSteps={totalSteps} title="Terms & Conditions" />
<div className="bg-[#F14B46]/10 py-2.5 px-3.5 border border-[#F14B46] rounded-xl mt-4"> <div className="bg-[#F14B46]/10 py-2.5 px-3.5 border border-[#F14B46] rounded-xl mt-4">
<p className="text-[#F14B46] group-12 font-semibold text-center"> <p className="text-[#F14B46] group-12 font-semibold text-center">
Welcome to Habib Marriage. Please review our terms of service before Welcome to Habib Marriage. Please review our terms of service before

3
src/components/Componentes/slider-slide-three.tsx

@ -36,6 +36,7 @@ type SliderSlideThreeProps = SliderSlideProps & {
export function SliderSlideThree({ export function SliderSlideThree({
index, index,
totalSteps,
selectedGender, selectedGender,
onGenderChange, onGenderChange,
}: SliderSlideThreeProps) { }: SliderSlideThreeProps) {
@ -44,7 +45,7 @@ export function SliderSlideThree({
aria-label={`Slide ${index + 1}`} aria-label={`Slide ${index + 1}`}
className="flex h-full min-h-0 w-full shrink-0 flex-col justify-between" className="flex h-full min-h-0 w-full shrink-0 flex-col justify-between"
> >
<SliderHeader index={index} title="Select your gender" />
<SliderHeader index={index} totalSteps={totalSteps} title="Select your gender" />
<div className="flex w-full items-center justify-evenly"> <div className="flex w-full items-center justify-evenly">
{GENDER_OPTIONS.map((option) => { {GENDER_OPTIONS.map((option) => {

4
src/components/Componentes/slider-slide-two.tsx

@ -9,7 +9,7 @@ import type { SliderSlideProps } from "./slider-slide";
import VideoPlayer from "./video-player"; import VideoPlayer from "./video-player";
import NetworkImage from "./network-image"; import NetworkImage from "./network-image";
export function SliderSlideTwo({ index }: SliderSlideProps) {
export function SliderSlideTwo({ index, totalSteps }: SliderSlideProps) {
const [isPlayerOpen, setIsPlayerOpen] = useState(false); const [isPlayerOpen, setIsPlayerOpen] = useState(false);
const { data: config } = useMarriageConfigQuery(); const { data: config } = useMarriageConfigQuery();
const { dictionary: t } = useI18n(); const { dictionary: t } = useI18n();
@ -28,7 +28,7 @@ export function SliderSlideTwo({ index }: SliderSlideProps) {
> >
<div className="min-h-0 flex-1 overflow-y-auto overscroll-y-contain pb-[110px] sm:pb-[130px] flex flex-col items-center"> <div className="min-h-0 flex-1 overflow-y-auto overscroll-y-contain pb-[110px] sm:pb-[130px] flex flex-col items-center">
<div className="w-full"> <div className="w-full">
<SliderHeader index={index} title={t["Watch Video"] || "Watch Video"} />
<SliderHeader index={index} totalSteps={totalSteps} title={t["Watch Video"] || "Watch Video"} />
</div> </div>
<div <div

1
src/components/Componentes/slider-slide.tsx

@ -1,5 +1,6 @@
export type SliderSlideProps = { export type SliderSlideProps = {
index: number; index: number;
totalSteps?: number;
}; };
export type GenderAnswer = "man" | "woman"; export type GenderAnswer = "man" | "woman";

10
src/config/features.ts

@ -0,0 +1,10 @@
/**
* Feature flag to control the display of intro video content:
* 1. Intro page video preview box (below stats / submit).
* 2. Intro steps slider Step 2 (video watching step).
*
* When `false`:
* - The intro video box on the main intro page is hidden, and elements are centered.
* - Step 2 (Video) in the onboarding slider is removed, reducing the slider to 4 steps with 4 indicator dots.
*/
export const SHOW_INTRO_VIDEOS = false;
Loading…
Cancel
Save