Browse Source

feat: implement question navigation system and multi-language support

front-test-2
ghorbani 2 weeks ago
parent
commit
110b192c82
  1. 4
      src/app/questions-list/page.tsx
  2. 100
      src/app/terms/page.test.tsx
  3. 59
      src/app/terms/page.tsx
  4. 24
      src/components/Componentes/question-date.tsx
  5. 18
      src/components/Componentes/question-exit-navigation-button.tsx
  6. 12
      src/components/Componentes/question-section-flow.tsx
  7. 265
      src/components/Componentes/slider-page.test.tsx
  8. 85
      src/components/Componentes/slider-page.tsx
  9. 88
      src/lib/get-submit-path.test.ts
  10. 5
      src/translations/locales/ar.json
  11. 5
      src/translations/locales/az.json
  12. 5
      src/translations/locales/bn.json
  13. 5
      src/translations/locales/da.json
  14. 5
      src/translations/locales/de.json
  15. 6
      src/translations/locales/en.json
  16. 5
      src/translations/locales/es.json
  17. 6
      src/translations/locales/fa.json
  18. 5
      src/translations/locales/fr.json
  19. 5
      src/translations/locales/gu.json
  20. 5
      src/translations/locales/ha.json
  21. 5
      src/translations/locales/he.json
  22. 5
      src/translations/locales/hi.json
  23. 5
      src/translations/locales/id.json
  24. 5
      src/translations/locales/ks.json
  25. 5
      src/translations/locales/pt.json
  26. 5
      src/translations/locales/ru.json
  27. 5
      src/translations/locales/sw.json
  28. 5
      src/translations/locales/tg.json
  29. 5
      src/translations/locales/tr.json
  30. 5
      src/translations/locales/ul.json
  31. 5
      src/translations/locales/ur.json
  32. 5
      src/translations/locales/uz.json
  33. 5
      src/translations/locales/zh.json

4
src/app/questions-list/page.tsx

@ -79,7 +79,9 @@ export default function QuestionsListPage() {
// Add fallback for combined section from the schema adapter which attaches it to questionListItems directly
questionListItems.forEach((item) => {
progressBySlug.set(item.slug, item.progress);
if (!progressBySlug.has(item.slug)) {
progressBySlug.set(item.slug, item.progress);
}
});
return progressBySlug;

100
src/app/terms/page.test.tsx

@ -0,0 +1,100 @@
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import TermsRoute from './page';
const mockUseMarriageProfileQuery = vi.fn();
const mockReplace = vi.fn();
vi.mock('@/hooks/marriage/use-profile-main', () => ({
useMarriageProfileQuery: () => mockUseMarriageProfileQuery(),
}));
vi.mock('next/navigation', () => ({
useRouter: () => ({
replace: mockReplace,
}),
}));
vi.mock('@/translations/provider', () => ({
useI18n: () => ({
locale: 'en',
dictionary: {
"Failed to load profile. Please try again.": "Failed to load profile. Please try again.",
"Retry": "Retry",
},
}),
}));
vi.mock('@/translations/config', () => ({
localizePath: (path: string) => path,
}));
vi.mock('@/components/Componentes/slider-page', () => ({
default: () => <div data-testid="slider-page">SliderPage</div>,
}));
describe('TermsRoute Guard', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
cleanup();
});
it('renders loading state when isLoading is true', () => {
mockUseMarriageProfileQuery.mockReturnValue({ isLoading: true });
render(<TermsRoute />);
expect(screen.queryByTestId('slider-page')).not.toBeInTheDocument();
// The spinner should be visible, maybe just check for a div. We can check slider is absent.
});
it('renders Error/Retry when isError is true', () => {
const mockRefetch = vi.fn();
mockUseMarriageProfileQuery.mockReturnValue({ isError: true, refetch: mockRefetch });
render(<TermsRoute />);
expect(screen.getByText('Failed to load profile. Please try again.')).toBeInTheDocument();
const retryBtn = screen.getByText('Retry');
fireEvent.click(retryBtn);
expect(mockRefetch).toHaveBeenCalled();
expect(screen.queryByTestId('slider-page')).not.toBeInTheDocument();
});
it('pending_onboarding => SliderPage renders', () => {
mockUseMarriageProfileQuery.mockReturnValue({
isLoading: false,
data: { status: 'pending_onboarding' }
});
render(<TermsRoute />);
expect(screen.getByTestId('slider-page')).toBeInTheDocument();
expect(mockReplace).not.toHaveBeenCalled();
});
it('pending_info => redirects to Questions and SliderPage is NOT rendered', async () => {
mockUseMarriageProfileQuery.mockReturnValue({
isLoading: false,
data: { status: 'pending_info' }
});
render(<TermsRoute />);
expect(screen.queryByTestId('slider-page')).not.toBeInTheDocument();
await waitFor(() => {
expect(mockReplace).toHaveBeenCalled(); // Should redirect to /questions-list or /questions-list/personal_info
});
});
it('waiting => redirects to Finding/Waiting Page and SliderPage is NOT rendered', async () => {
mockUseMarriageProfileQuery.mockReturnValue({
isLoading: false,
data: { status: 'waiting' }
});
render(<TermsRoute />);
expect(screen.queryByTestId('slider-page')).not.toBeInTheDocument();
await waitFor(() => {
expect(mockReplace).toHaveBeenCalledWith('/finding-match');
});
});
});

59
src/app/terms/page.tsx

@ -1,5 +1,64 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { getSubmitPath } from "@/lib/get-submit-path";
import SliderPage from "@/components/Componentes/slider-page";
import Button from "@/components/Componentes/button";
import { useI18n } from "@/translations/provider";
import { localizePath } from "@/translations/config";
export default function TermsRoute() {
const { data: profile, isLoading, isError, refetch } = useMarriageProfileQuery();
const router = useRouter();
const { locale, dictionary: t } = useI18n();
useEffect(() => {
if (!isLoading && profile) {
if (profile.status !== "pending_onboarding") {
const target = getSubmitPath(profile);
if (target !== "/terms") {
router.replace(localizePath(target, locale));
}
}
}
}, [profile, isLoading, router, locale]);
if (isLoading) {
return (
<div className="flex h-[100dvh] items-center justify-center bg-[#F5F5F5]">
<div className="size-8 animate-spin rounded-full border-4 border-[#F14B46] border-t-transparent" />
</div>
);
}
if (isError) {
return (
<div className="flex h-[100dvh] flex-col items-center justify-center p-4 bg-[#F5F5F5]">
<p className="mb-4 text-center font-medium text-[#F14B46]">
{/* @ts-expect-error - missing key */}
{t["Failed to load profile. Please try again."] || "Failed to load profile. Please try again."}
</p>
<div className="w-full max-w-[200px]">
<Button onClick={() => refetch()}>
{/* @ts-expect-error - missing key */}
{t["Retry"] || "Retry"}
</Button>
</div>
</div>
);
}
if (profile && profile.status !== "pending_onboarding") {
// If target is /terms but status is not pending_onboarding, we should still not render SliderPage
// to prevent showing Onboarding to users who already finished it but have an unknown status.
return (
<div className="flex h-[100dvh] items-center justify-center bg-[#F5F5F5]">
<div className="size-8 animate-spin rounded-full border-4 border-[#F14B46] border-t-transparent" />
</div>
);
}
return <SliderPage />;
}

24
src/components/Componentes/question-date.tsx

@ -12,18 +12,18 @@ type QuestionDateProps = {
};
const MONTHS = [
{ value: "01", label: "01 - January" },
{ value: "02", label: "02 - February" },
{ value: "03", label: "03 - March" },
{ value: "04", label: "04 - April" },
{ value: "05", label: "05 - May" },
{ value: "06", label: "06 - June" },
{ value: "07", label: "07 - July" },
{ value: "08", label: "08 - August" },
{ value: "09", label: "09 - September" },
{ value: "10", label: "10 - October" },
{ value: "11", label: "11 - November" },
{ value: "12", label: "12 - December" },
{ value: "01", label: "January" },
{ value: "02", label: "February" },
{ value: "03", label: "March" },
{ value: "04", label: "April" },
{ value: "05", label: "May" },
{ value: "06", label: "June" },
{ value: "07", label: "July" },
{ value: "08", label: "August" },
{ value: "09", label: "September" },
{ value: "10", label: "October" },
{ value: "11", label: "November" },
{ value: "12", label: "December" },
];
const DAYS = Array.from({ length: 31 }, (_, i) => {

18
src/components/Componentes/question-exit-navigation-button.tsx

@ -25,28 +25,32 @@ export function QuestionExitNavigationButton({
const { locale } = useI18n();
const { flushAnswers } = useQuestionAnswers();
const [isNavigating, setIsNavigating] = useState(false);
return (
<NavigationButton
{...props}
onClick={(event) => {
disabled={props.disabled || isNavigating}
onClick={async (event) => {
props.onClick?.(event);
if (event.defaultPrevented) {
if (event.defaultPrevented || isNavigating) {
return;
}
event.preventDefault();
setIsNavigating(true);
try {
markFirstEntryCompleted();
void flushAnswers({ force: true });
await flushAnswers({ force: true });
} catch {
// ignore
} finally {
triggerSilentReload(queryClient);
const target = localizePath(exitHref || "/questions-list", locale);
router.push(target);
}
triggerSilentReload(queryClient);
const target = localizePath(exitHref || "/questions-list", locale);
router.push(target);
}}
/>
);

12
src/components/Componentes/question-section-flow.tsx

@ -54,7 +54,7 @@ function SectionFlowContent({
void flushAnswers({ force: true });
}, [flushAnswers]);
const handleSubmit = useCallback(() => {
const handleSubmit = useCallback(async () => {
if (isSubmitting) {
return;
}
@ -62,14 +62,14 @@ function SectionFlowContent({
try {
markFirstEntryCompleted();
void flushAnswers({ force: true });
await flushAnswers({ force: true });
} catch {
// ignore
} finally {
triggerSilentReload(queryClient);
const target = localizePath(exitHref || "/questions-list", locale);
router.push(target);
}
triggerSilentReload(queryClient);
const target = localizePath(exitHref || "/questions-list", locale);
router.push(target);
}, [exitHref, flushAnswers, locale, router, isSubmitting, queryClient]);
const markOptionalQuestionsPassed = useCallback(

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

@ -0,0 +1,265 @@
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import SliderPage from './slider-page';
const mockMutateAsync = vi.fn();
const mockGetMarriageProfile = vi.fn();
const mockSetQueryData = vi.fn();
const mockReplace = vi.fn();
const mockRemoveItem = vi.spyOn(Storage.prototype, 'removeItem');
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
vi.mock('@/hooks/marriage/use-profile-basic', () => ({
useUpdateMarriageProfileBasicMutation: () => ({
mutateAsync: mockMutateAsync,
isPending: false,
}),
}));
vi.mock('@/hooks/marriage/use-profile-main', () => ({
getMarriageProfile: (...args: any[]) => mockGetMarriageProfile(...args),
useMarriageProfileQuery: () => ({ data: undefined, refetch: vi.fn() }),
}));
vi.mock('@/hooks/marriage/query-keys', () => ({
marriageQueryKeys: {
profile: () => ['marriage', 'profile'],
},
}));
vi.mock('next/navigation', () => ({
useRouter: () => ({
replace: mockReplace,
back: vi.fn(),
}),
}));
vi.mock('@/translations/provider', () => ({
useI18n: () => ({
locale: 'en',
dictionary: {
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again.",
"Accept & Continue": "Accept & Continue",
},
}),
}));
vi.mock('@/translations/config', () => ({
localizePath: (path: string, locale: string) => path,
}));
describe('SliderPage', () => {
let queryClient: QueryClient;
beforeEach(() => {
vi.resetAllMocks();
queryClient = new QueryClient();
vi.spyOn(queryClient, 'setQueryData');
});
afterEach(() => {
vi.useRealTimers();
cleanup();
});
const navigateToFinalSlide = async () => {
render(
<QueryClientProvider client={queryClient}>
<SliderPage />
</QueryClientProvider>
);
const dots = screen.getAllByRole('button', { name: /go to slide/i });
fireEvent.click(dots[4]); // Go directly to slide 5
return await screen.findByText('Finish');
};
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;
let resolveGet: any;
mockMutateAsync.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
mockGetMarriageProfile.mockReturnValueOnce(new Promise(resolve => {
resolveGet = resolve;
}));
fireEvent.click(finishBtn);
// Wait for PATCH to resolve and GET to be called
await waitFor(() => {
expect(mockMutateAsync).toHaveBeenCalled();
expect(mockGetMarriageProfile).toHaveBeenCalled();
});
// Assert navigation and localStorage clear did not happen before GET resolves
expect(mockReplace).not.toHaveBeenCalled();
const currentRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
expect(currentRemoveCount).toBe(initialRemoveCount);
// Resolve GET
resolveGet({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
await waitFor(() => {
expect(queryClient.setQueryData).toHaveBeenCalled();
const finalRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
expect(finalRemoveCount).toBe(initialRemoveCount + 1);
expect(mockReplace).toHaveBeenCalledWith('/questions-list/personal_info');
});
});
it('b. PATCH failure: no nav, Error UI, Retry works', async () => {
const finishBtn = await navigateToFinalSlide();
const initialRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
mockMutateAsync.mockRejectedValueOnce(new Error('Network Error'));
fireEvent.click(finishBtn);
await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent('Failed to update profile basic details. Please try again.');
});
expect(mockReplace).not.toHaveBeenCalled();
const currentRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
expect(currentRemoveCount).toBe(initialRemoveCount);
// Retry succeeds
mockMutateAsync.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
mockGetMarriageProfile.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
const retryBtn = screen.getByText('Finish');
fireEvent.click(retryBtn);
await waitFor(() => {
expect(mockReplace).toHaveBeenCalledWith('/questions-list/personal_info');
});
const finalRemoveCount = mockRemoveItem.mock.calls.filter(call => call[0] === 'marriage-slider-answers').length;
expect(finalRemoveCount).toBe(initialRemoveCount + 1);
});
it('c. Slow request: Promise pending -> no redirect', async () => {
const finishBtn = await navigateToFinalSlide();
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date'] });
let resolvePatch: any;
mockMutateAsync.mockReturnValueOnce(new Promise(resolve => {
resolvePatch = resolve;
}));
mockGetMarriageProfile.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
fireEvent.click(finishBtn);
await vi.advanceTimersByTimeAsync(6000);
expect(mockReplace).not.toHaveBeenCalled();
resolvePatch({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
vi.useRealTimers();
await waitFor(() => {
expect(mockReplace).toHaveBeenCalledWith('/questions-list/personal_info');
});
});
it('d. Double-check failure: GET returns pending_onboarding -> Error UI', async () => {
const finishBtn = await navigateToFinalSlide();
mockMutateAsync.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
mockGetMarriageProfile.mockResolvedValueOnce({
status: 'pending_onboarding',
gender: 'female',
is_registering_for_self: true
});
fireEvent.click(finishBtn);
await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent('Failed to update profile basic details. Please try again.');
});
expect(mockReplace).not.toHaveBeenCalled();
});
it('e. Double click: only 1 request', async () => {
const finishBtn = await navigateToFinalSlide();
let resolvePatch: any;
mockMutateAsync.mockReturnValueOnce(new Promise(resolve => {
resolvePatch = resolve;
}));
mockGetMarriageProfile.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
fireEvent.click(finishBtn);
fireEvent.click(finishBtn);
fireEvent.click(finishBtn);
expect(mockMutateAsync).toHaveBeenCalledTimes(1);
resolvePatch({ status: 'pending_info', gender: 'female', is_registering_for_self: true });
await waitFor(() => {
expect(mockReplace).toHaveBeenCalled();
});
});
it('f. Double-check failure: GET returns waiting -> Error UI', async () => {
const finishBtn = await navigateToFinalSlide();
mockMutateAsync.mockResolvedValueOnce({
status: 'pending_info',
gender: 'female',
is_registering_for_self: true
});
mockGetMarriageProfile.mockResolvedValueOnce({
status: 'waiting',
gender: 'female',
is_registering_for_self: true
});
fireEvent.click(finishBtn);
await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent('Failed to update profile basic details. Please try again.');
});
expect(mockReplace).not.toHaveBeenCalled();
});
});

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

@ -3,6 +3,7 @@
import { useRouter } from "next/navigation";
import type { TouchEvent } from "react";
import { useEffect, useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { useUpdateMarriageProfileBasicMutation } from "@/hooks/marriage/use-profile-basic";
import { localizePath } from "@/translations/config";
import { useI18n } from "@/translations/provider";
@ -29,6 +30,10 @@ export default function SliderPage() {
const [hasReadRules, setHasReadRules] = useState(false);
const [touchStartX, setTouchStartX] = useState<number | null>(null);
const updateProfileBasicMutation = useUpdateMarriageProfileBasicMutation();
const queryClient = useQueryClient();
const [submitError, setSubmitError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const { dictionary: t } = useI18n();
const hasFinalNotice = true;
const maxSlideIndex = FINAL_SLIDE_COUNT - 1;
const displaySlideCount = FINAL_SLIDE_COUNT;
@ -54,36 +59,51 @@ export default function SliderPage() {
};
const completeSlider = async () => {
const navigateAway = () => {
if (typeof window !== "undefined") {
window.localStorage.removeItem(SLIDER_ANSWERS_STORAGE_KEY);
// Use hard navigation instead of router.push() to ensure the
// destination page loads with a fresh auth state. SPA (soft)
// navigation sometimes leaves stale query-cache / token state
// that causes the profile query to return 401 and the page to
// stay stuck on the loading spinner.
window.location.href = localizePath(
"/questions-list/personal_info",
locale,
);
return;
}
router.push(localizePath("/questions-list/personal_info", locale));
};
if (isSubmitting) return;
setIsSubmitting(true);
setSubmitError(null);
// Safety timeout — navigate after 5 seconds even if the request hangs
const timeout = setTimeout(navigateAway, 5000);
const genderPayload = selectedGender === "man" ? "male" : "female";
const isRegisteringPayload = selectedRegistration === "self";
try {
await updateProfileBasicMutation.mutateAsync({
gender: selectedGender === "man" ? "male" : "female",
is_registering_for_self: selectedRegistration === "self",
const patchResponse = await updateProfileBasicMutation.mutateAsync({
gender: genderPayload,
is_registering_for_self: isRegisteringPayload,
});
if (
patchResponse.status === "pending_onboarding" ||
patchResponse.gender !== genderPayload ||
patchResponse.is_registering_for_self !== isRegisteringPayload
) {
throw new Error("Invalid PATCH response");
}
const { getMarriageProfile } = await import("@/hooks/marriage/use-profile-main");
const freshProfile = await getMarriageProfile();
if (
freshProfile.status !== "pending_info" ||
freshProfile.gender !== genderPayload ||
freshProfile.is_registering_for_self !== isRegisteringPayload
) {
throw new Error("Invalid GET double-check response");
}
const { marriageQueryKeys } = await import("@/hooks/marriage/query-keys");
queryClient.setQueryData(marriageQueryKeys.profile(), freshProfile);
if (typeof window !== "undefined") {
window.localStorage.removeItem(SLIDER_ANSWERS_STORAGE_KEY);
}
router.replace(localizePath("/questions-list/personal_info", locale));
} catch (error) {
console.warn("Failed to update profile basic details:", error);
console.error("Failed to complete onboarding:", error);
setSubmitError(t["Failed to update profile basic details. Please try again."] || "Failed to update profile basic details. Please try again.");
} finally {
clearTimeout(timeout);
navigateAway();
setIsSubmitting(false);
}
};
@ -184,11 +204,18 @@ export default function SliderPage() {
disabled={!hasReadRules}
/>
) : activeSlide === maxSlideIndex ? (
<SliderFinalActions
onBack={goToPreviousSlide}
isFinishing={updateProfileBasicMutation.isPending}
onFinish={completeSlider}
/>
<div className="flex flex-col gap-3">
{submitError && (
<div role="alert" aria-live="assertive" className="text-sm text-red-500 bg-red-50 p-3 rounded-xl border border-red-200">
{submitError}
</div>
)}
<SliderFinalActions
onBack={goToPreviousSlide}
isFinishing={isSubmitting}
onFinish={completeSlider}
/>
</div>
) : (
<SliderStepActions
onBack={goToPreviousSlide}

88
src/lib/get-submit-path.test.ts

@ -0,0 +1,88 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getSubmitPath } from './get-submit-path';
import * as firstEntryHelper from './first-entry-helper';
import * as matchStartGrace from './match-start-grace';
import type { MarriageProfileResponse } from '@/hooks/marriage/types';
vi.mock('./first-entry-helper', () => ({
isFirstEntryCompleted: vi.fn(),
}));
vi.mock('./match-start-grace', () => ({
clearLegacyMatchSubmittedFlag: vi.fn(),
isWithinMatchStartGrace: vi.fn(),
}));
describe('getSubmitPath', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(firstEntryHelper.isFirstEntryCompleted).mockReturnValue(true);
vi.mocked(matchStartGrace.isWithinMatchStartGrace).mockReturnValue(false);
});
const baseProfile: MarriageProfileResponse = {
status: 'pending_onboarding',
id: 1,
gender: 'male',
is_registering_for_self: true,
};
it('pending_onboarding returns /terms', () => {
expect(getSubmitPath({ ...baseProfile, status: 'pending_onboarding' })).toBe('/terms');
});
it('waiting returns /finding-match', () => {
expect(getSubmitPath({ ...baseProfile, status: 'waiting' })).toBe('/finding-match');
});
it('pending_info returns questions path', () => {
expect(getSubmitPath({ ...baseProfile, status: 'pending_info' })).toBe('/questions-list');
vi.mocked(firstEntryHelper.isFirstEntryCompleted).mockReturnValue(false);
expect(getSubmitPath({ ...baseProfile, status: 'pending_info' })).toBe('/questions-list/personal_info');
});
it('matched returns /request-accepted', () => {
expect(getSubmitPath({ ...baseProfile, status: 'matched' })).toBe('/request-accepted');
});
describe('active_case priority', () => {
it('returns /new-match when status="pending_info" but active_case is introduced and action pending', () => {
const profileWithCase = {
...baseProfile,
status: 'pending_info' as const,
active_case: {
status: 'introduced' as const,
my_action: 'pending' as const,
},
};
// Type assertion added because the mock profile object is lacking many properties of the full MarriageProfileResponse,
// but it contains everything needed for the logic being tested.
expect(getSubmitPath(profileWithCase as any)).toBe('/new-match');
});
it('returns /request-accepted when status="pending_info" but active_case is payment_done', () => {
const profileWithCase = {
...baseProfile,
status: 'pending_info' as const,
active_case: {
status: 'payment_done' as const,
my_action: 'pending' as const,
},
};
expect(getSubmitPath(profileWithCase as any)).toBe('/request-accepted');
});
it('returns /request-sent when status="waiting" but active_case is male_accepted and action done', () => {
const profileWithCase = {
...baseProfile,
status: 'waiting' as const,
active_case: {
status: 'male_accepted' as const,
my_action: 'done' as const,
},
};
expect(getSubmitPath(profileWithCase as any)).toBe('/request-sent');
});
});
});

5
src/translations/locales/ar.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/az.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/bn.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/da.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/de.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

6
src/translations/locales/en.json

@ -760,7 +760,6 @@
"⚠️ This section is completely confidential and is only used for matching and review by advisors.": "⚠️ This section is completely confidential and is only used for matching and review by advisors.",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Marriage advisors": "Marriage advisors",
"Dr. Hasti Masoudi": "Dr. Hasti Masoudi",
"Specialist in clinical psychology": "Specialist in clinical psychology",
"Personal Development": "Personal Development",
"Family Counselor": "Family Counselor",
@ -804,5 +803,6 @@
"Improving Mutual Understanding": "Improving Mutual Understanding",
"Helps individuals better understand their own needs as well as their partner’s motivations, preferences, and emotional priorities.": "Helps individuals better understand their own needs as well as their partner’s motivations, preferences, and emotional priorities.",
"Supporting More Suitable Match Recommendations": "Supporting More Suitable Match Recommendations",
"Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched.": "Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched."
}
"Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched.": "Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched.",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/es.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

6
src/translations/locales/fa.json

@ -760,7 +760,6 @@
"⚠️ This section is completely confidential and is only used for matching and review by advisors.": "⚠️ این بخش کاملاً محرمانه است و فقط برای مچینگ و بررسی کارشناسان استفاده میشود.",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Marriage advisors": "مشاوران ازدواج",
"Dr. Hasti Masoudi": "دکتر هستی مسعودی",
"Specialist in clinical psychology": "متخصص روانشناسی بالینی",
"Personal Development": "رشد فردی",
"Family Counselor": "مشاور خانواده",
@ -804,5 +803,6 @@
"Improving Mutual Understanding": "بهبود درک متقابل",
"Helps individuals better understand their own needs as well as their partner’s motivations, preferences, and emotional priorities.": "به افراد کمک می‌کند تا نیازهای خود و همچنین انگیزه‌ها، ترجیحات و اولویت‌های عاطفی شریک زندگی خود را بهتر درک کنند.",
"Supporting More Suitable Match Recommendations": "پشتیبانی از پیشنهادهای همسریابی مناسب‌تر",
"Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched.": "ترکیب ارزیابی نیازها و شخصیت با مصاحبه‌ها و سایر معیارهای ازدواج می‌تواند به شخصی‌سازی و هماهنگی بیشتر پیشنهادهای شریک زندگی کمک کند."
}
"Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched.": "ترکیب ارزیابی نیازها و شخصیت با مصاحبه‌ها و سایر معیارهای ازدواج می‌تواند به شخصی‌سازی و هماهنگی بیشتر پیشنهادهای شریک زندگی کمک کند.",
"Failed to update profile basic details. Please try again.": "به‌روزرسانی اطلاعات اولیه پروفایل ناموفق بود. لطفاً دوباره تلاش کنید."
}

5
src/translations/locales/fr.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/gu.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/ha.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/he.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/hi.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/id.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/ks.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/pt.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/ru.json

@ -758,5 +758,6 @@
"{completed} of {total} required steps completed": "Выполнено {completed} из {total} требуемых шагов",
"{days} days remaining of your subscription.": "{days} дней до конца вашей подписки.",
"⚠️ This section is completely confidential and is only used for matching and review by advisors.": "⚠️ Этот раздел полностью конфиденциальен и используется только консультантами для сопоставления и проверки.",
"Please select the reason for cancellation:": "Пожалуйста, выберите причину отмены:"
}
"Please select the reason for cancellation:": "Пожалуйста, выберите причину отмены:",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/sw.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/tg.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/tr.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/ul.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/ur.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/uz.json

@ -279,5 +279,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}

5
src/translations/locales/zh.json

@ -753,5 +753,6 @@
"We are in the acquaintance/proposal process and nothing is finalized yet": "We are in the acquaintance/proposal process and nothing is finalized yet",
"Please select the reason for cancellation:": "Please select the reason for cancellation:",
"Swipe to continue": "Swipe to continue",
"Swipe to confirm cancellation": "Swipe to confirm cancellation"
}
"Swipe to confirm cancellation": "Swipe to confirm cancellation",
"Failed to update profile basic details. Please try again.": "Failed to update profile basic details. Please try again."
}
Loading…
Cancel
Save