Browse Source

remove video

master
mortezaei 10 hours ago
parent
commit
28e8259b34
  1. 76
      src/app/intro/intro-client.test.tsx
  2. 75
      src/app/intro/intro-client.tsx
  3. 34
      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();
});
});

75
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 { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler";
import { authBridge } from "@/lib/auth-bridge";
import { SHOW_INTRO_VIDEOS } from "@/config/features";
import { getSubmitPath } from "@/lib/get-submit-path";
import { localizePath } from "@/translations/config";
import { useI18n } from "@/translations/provider";
@ -125,10 +126,27 @@ export default function IntroClient() {
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} />
<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
src={"/assets/images/Group 1597880466.svg"}
alt={t["heavenly marriage"]}
@ -148,7 +166,12 @@ export default function IntroClient() {
}
</p>
</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">
<IntroUsersIcon />
<div className="flex flex-col items-start justify-start -mb-2">
@ -177,27 +200,31 @@ export default function IntroClient() {
</div>
</div>
</div>
<div
className="mt-14 relative cursor-pointer group rounded-2xl overflow-hidden aspect-[344/221] max-w-[344px] w-full mx-auto"
onClick={() => setIsPlayerOpen(true)}
>
<NetworkImage
src={config?.intro_video_thumbnail_url}
fallbackSrc="/assets/images/Frame 2095586523.png"
alt={t["video"]}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
priority
/>
<div className="absolute inset-0 bg-black/10 transition-colors duration-300 group-hover:bg-black/20" />
<IntroPlayButtonIcon className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-transform duration-300 group-hover:scale-110 active:scale-95" />
</div>
{SHOW_INTRO_VIDEOS && (
<>
<div
className="mt-14 relative cursor-pointer group rounded-2xl overflow-hidden aspect-[344/221] max-w-[344px] w-full mx-auto"
onClick={() => setIsPlayerOpen(true)}
>
<NetworkImage
src={config?.intro_video_thumbnail_url}
fallbackSrc="/assets/images/Frame 2095586523.png"
alt={t["video"]}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
priority
/>
<div className="absolute inset-0 bg-black/10 transition-colors duration-300 group-hover:bg-black/20" />
<IntroPlayButtonIcon className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-transform duration-300 group-hover:scale-110 active:scale-95" />
</div>
<VideoPlayer
isOpen={isPlayerOpen}
onClose={() => setIsPlayerOpen(false)}
videoUrl={config?.intro_video_url}
/>
<VideoPlayer
isOpen={isPlayerOpen}
onClose={() => setIsPlayerOpen(false)}
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
style={{ paddingBottom: "calc(20px + var(--safe-bottom))" }}

34
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, {
enabled: false,
enabled: !!caseId && isMalePaymentDone,
});
if ((isLoading && !profile) || !profile) {
@ -300,7 +305,12 @@ export default function RequestAcceptedClient() {
}
if (!contactInfoQuery.data) {
await contactInfoQuery.refetch();
try {
setIsFetchingContact(true);
await contactInfoQuery.refetch();
} finally {
setIsFetchingContact(false);
}
}
setIsContactInfoSheetOpen(true);
@ -693,11 +703,17 @@ export default function RequestAcceptedClient() {
onClick={() => {
void handleSecondaryAction();
}}
disabled={paymentMutation.isPending}
disabled={
paymentMutation.isPending ||
isFetchingContact ||
(isMalePaymentDone &&
contactInfoQuery.isFetching &&
!contactInfoQuery.data)
}
className={
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 ? (
@ -707,8 +723,12 @@ export default function RequestAcceptedClient() {
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" />
) : (
secondaryActionText

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

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

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

@ -76,10 +76,22 @@ describe('SliderPage', () => {
fireEvent.click(acceptBtn);
}
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');
};
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 () => {
const finishBtn = await navigateToFinalSlide();
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 NavigationButton from "./navigation-button";
import type { GenderAnswer, RegistrationAnswer } from "./slider-slide";
import { SHOW_INTRO_VIDEOS } from "@/config/features";
import { SliderSlideFive } from "./slider-slide-five";
import { SliderSlideFour } from "./slider-slide-four";
import { SliderSlideOne, SliderSlideOneActions } from "./slider-slide-one";
import { SliderSlideThree } from "./slider-slide-three";
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";
export type SliderPageProps = {
@ -192,20 +193,30 @@ export default function SliderPage({ onClose }: SliderPageProps = {}) {
>
<SliderSlideOne
index={0}
totalSteps={displaySlideCount}
onScrollToEnd={() => setHasReadRules(true)}
/>
<SliderSlideTwo index={1} />
{SHOW_INTRO_VIDEOS && (
<SliderSlideTwo index={1} totalSteps={displaySlideCount} />
)}
<SliderSlideThree
index={2}
index={SHOW_INTRO_VIDEOS ? 2 : 1}
totalSteps={displaySlideCount}
selectedGender={selectedGender}
onGenderChange={setSelectedGender}
/>
<SliderSlideFour
index={3}
index={SHOW_INTRO_VIDEOS ? 3 : 2}
totalSteps={displaySlideCount}
selectedRegistration={selectedRegistration}
onRegistrationChange={setSelectedRegistration}
/>
{hasFinalNotice ? <SliderSlideFive index={4} /> : null}
{hasFinalNotice ? (
<SliderSlideFive
index={SHOW_INTRO_VIDEOS ? 4 : 3}
totalSteps={displaySlideCount}
/>
) : null}
</div>
</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.",
];
export function SliderSlideFive({ index }: SliderSlideProps) {
export function SliderSlideFive({ index, totalSteps }: SliderSlideProps) {
return (
<section
aria-label={`Slide ${index + 1}`}
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">
<SliderHeader index={index} totalSteps={5} title="Final Notice" />
<SliderHeader index={index} totalSteps={totalSteps} title="Final Notice" />
<div className="mt-6 space-y-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({
index,
totalSteps,
selectedRegistration,
onRegistrationChange,
}: SliderSlideFourProps) {
@ -25,7 +26,7 @@ export function SliderSlideFour({
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">
<SliderHeader index={index} title="Registration Type" />
<SliderHeader index={index} totalSteps={totalSteps} title="Registration Type" />
<NoticeBox className="mt-8" align="justify">
You can register both for yourself and for your loved ones, but
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;
};
export function SliderSlideOne({ index, onScrollToEnd }: SliderSlideOneProps) {
export function SliderSlideOne({ index, totalSteps, onScrollToEnd }: SliderSlideOneProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -59,7 +59,7 @@ export function SliderSlideOne({ index, onScrollToEnd }: SliderSlideOneProps) {
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">
<p className="text-[#F14B46] group-12 font-semibold text-center">
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({
index,
totalSteps,
selectedGender,
onGenderChange,
}: SliderSlideThreeProps) {
@ -44,7 +45,7 @@ export function SliderSlideThree({
aria-label={`Slide ${index + 1}`}
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">
{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 NetworkImage from "./network-image";
export function SliderSlideTwo({ index }: SliderSlideProps) {
export function SliderSlideTwo({ index, totalSteps }: SliderSlideProps) {
const [isPlayerOpen, setIsPlayerOpen] = useState(false);
const { data: config } = useMarriageConfigQuery();
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="w-full">
<SliderHeader index={index} title={t["Watch Video"] || "Watch Video"} />
<SliderHeader index={index} totalSteps={totalSteps} title={t["Watch Video"] || "Watch Video"} />
</div>
<div

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

@ -1,5 +1,6 @@
export type SliderSlideProps = {
index: number;
totalSteps?: number;
};
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