Browse Source

feat: implement multi-language support and new question-related UI components and pages

front-test-2
ghorbani 4 weeks ago
parent
commit
f3a9e95529
  1. 2
      proxy.ts
  2. 8
      src/app/intro/page.tsx
  3. 53
      src/app/questions-list/page.tsx
  4. 84
      src/components/questions/required-steps-card.tsx
  5. 5
      src/data/question-data.ts
  6. 2
      src/translations/locales/ar.json
  7. 2
      src/translations/locales/az.json
  8. 2
      src/translations/locales/bn.json
  9. 2
      src/translations/locales/da.json
  10. 2
      src/translations/locales/de.json
  11. 2
      src/translations/locales/en.json
  12. 2
      src/translations/locales/es.json
  13. 2
      src/translations/locales/fa.json
  14. 2
      src/translations/locales/fr.json
  15. 2
      src/translations/locales/gu.json
  16. 2
      src/translations/locales/ha.json
  17. 2
      src/translations/locales/he.json
  18. 2
      src/translations/locales/hi.json
  19. 2
      src/translations/locales/id.json
  20. 2
      src/translations/locales/ks.json
  21. 2
      src/translations/locales/pt.json
  22. 2
      src/translations/locales/ru.json

2
proxy.ts

@ -1,5 +1,5 @@
import { type NextRequest, NextResponse } from "next/server";
import { defaultLocale, isLocale } from "@/i18n/config";
import { defaultLocale, isLocale } from "@/translations/config";
function getPreferredLocale(request: NextRequest) {
const acceptLanguage = request.headers.get("accept-language") ?? "";

8
src/app/intro/page.tsx

@ -15,6 +15,10 @@ import { useI18n } from "@/translations/provider";
const REDIRECT_SESSION_KEY = "redirect";
function getSubmitPath(profile: MarriageProfileResponse | undefined) {
const isMatchSubmitted =
typeof window !== "undefined" &&
localStorage.getItem("match_submitted") === "true";
const isInCase = profile?.status === "in_case";
const isFemaleAcceptedFlow =
isInCase &&
@ -28,9 +32,9 @@ function getSubmitPath(profile: MarriageProfileResponse | undefined) {
? "/request-sent"
: profile?.status === "pending_onboarding"
? "/terms"
: profile?.status === "pending_info"
: profile?.status === "pending_info" && !isMatchSubmitted
? "/questions-list"
: profile?.status === "waiting"
: profile?.status === "waiting" || isMatchSubmitted
? "/finding-match"
: profile?.status === "in_case" || profile?.status === "matched"
? "/new-match"

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

@ -36,6 +36,15 @@ export default function QuestionsListPage() {
});
const startMatchMutation = useStartMarriageMatchMutation({
onSuccess: () => {
if (typeof window !== "undefined") {
localStorage.setItem("match_submitted", "true");
}
router.push(localizePath("/finding-match", locale));
},
onError: () => {
if (typeof window !== "undefined") {
localStorage.setItem("match_submitted", "true");
}
router.push(localizePath("/finding-match", locale));
},
});
@ -73,43 +82,50 @@ export default function QuestionsListPage() {
return progressBySlug;
}, [sections, questionListItems, profile]);
const requiredQuestionListItems = useMemo(
() => questionListItems.filter((item) => Boolean(item.required)),
[questionListItems],
);
const allRequiredSectionsCompleted = useMemo(() => {
if (!sections?.length) {
if (requiredQuestionListItems.length === 0) {
return false;
}
return sections
.filter((section) => section.is_required)
.every((section) => {
const frontendSlug = toFrontendSlug(section.slug);
const progress = sectionProgressBySlug.get(frontendSlug) ?? Math.max(0, Math.min(100, Math.round(section.completion_percent)));
return requiredQuestionListItems.every((item) => {
const progress = sectionProgressBySlug.get(item.slug) ?? 0;
return progress >= 100;
});
}, [sections, sectionProgressBySlug]);
}, [requiredQuestionListItems, sectionProgressBySlug]);
const profileStatus = profile?.status;
const isProfileSuspended = profileStatus === "suspended";
const canStartMatch =
profileStatus === "pending_info" &&
(!profileStatus || profileStatus === "pending_info") &&
!isProfileSuspended &&
allRequiredSectionsCompleted;
const isStartMatchDisabled = startMatchMutation.isPending || !canStartMatch;
const hasIncompleteOptionalSections = useMemo(() => {
if (!sections?.length) {
return false;
}
return sections.some(
(section) => !section.is_required && (sectionProgressBySlug.get(toFrontendSlug(section.slug)) ?? section.completion_percent) < 100,
return questionListItems.some(
(item) => !item.required && (sectionProgressBySlug.get(item.slug) ?? 0) < 100,
);
}, [sections, sectionProgressBySlug]);
}, [questionListItems, sectionProgressBySlug]);
const handleStartMatch = () => {
if (typeof window !== "undefined") {
localStorage.setItem("match_submitted", "true");
}
if (!canStartMatch) {
router.push(localizePath("/finding-match", locale));
return;
}
startMatchMutation.mutate();
startMatchMutation.mutate(undefined, {
onSettled: () => {
router.push(localizePath("/finding-match", locale));
},
});
};
return (
@ -204,7 +220,10 @@ export default function QuestionsListPage() {
<div className="relative">
<div className="mt-4">
<RequiredStepsCard />
<RequiredStepsCard
items={questionListItems}
progressBySlug={sectionProgressBySlug}
/>
</div>
<section className="mt-5 space-y-3">

84
src/components/questions/required-steps-card.tsx

@ -4,6 +4,7 @@ import { IoAlert, IoCheckmark } from "react-icons/io5";
import {
getQuestionListItems,
isQuestionListItemVisibleForProfile,
type QuestionListItem,
} from "@/data/question-data";
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { useMarriageSectionsQuery } from "@/hooks/marriage/use-sections";
@ -12,6 +13,11 @@ import { useMemo } from "react";
import { toFrontendSlug } from "@/data/section-slug-map";
import { getStoredAge, getLocalSectionProgress } from "@/components/questions/progress-helper";
type RequiredStepsCardProps = {
items?: QuestionListItem[];
progressBySlug?: Map<string, number>;
};
type RequiredStep = {
slug: string;
required: boolean;
@ -28,62 +34,56 @@ function getRequiredStepStats(steps: RequiredStep[]) {
};
}
export default function RequiredStepsCard() {
export default function RequiredStepsCard({
items,
progressBySlug,
}: RequiredStepsCardProps = {}) {
const { dictionary: t, locale } = useI18n();
const { data: profile } = useMarriageProfileQuery();
const { data: sections } = useMarriageSectionsQuery();
const fallbackRequiredSteps: RequiredStep[] = getQuestionListItems(locale)
.filter((item) =>
const questionListItems = useMemo(
() =>
items ??
getQuestionListItems(locale).filter((item) =>
isQuestionListItemVisibleForProfile(item, {
gender: profile?.gender,
}),
)
.map((item) => ({
slug: item.slug,
required: Boolean(item.required),
progress: item.progress,
}));
const steps = useMemo(() => {
if (!sections) return fallbackRequiredSteps;
const visibleSlugs = new Set(
getQuestionListItems(locale)
.filter((item) =>
isQuestionListItemVisibleForProfile(item, {
gender: profile?.gender,
}),
)
.map((item) => item.slug)
),
[items, locale, profile?.gender],
);
const steps: RequiredStep[] = useMemo(() => {
const age = getStoredAge();
return sections
.map((section) => {
const frontendSlug = toFrontendSlug(section.slug);
// Check if the frontend equivalent of this section is required
const frontendItem = getQuestionListItems(locale).find(
(item) => item.slug === frontendSlug,
);
const localProgress = frontendItem ? getLocalSectionProgress(frontendItem, profile, age) : null;
const progress = localProgress !== null ? localProgress : Math.max(
0,
Math.min(100, Math.round(section.completion_percent)),
);
type SectionType = NonNullable<typeof sections>[number];
const sectionMap = new Map<string, SectionType>();
sections?.forEach((s) => {
sectionMap.set(toFrontendSlug(s.slug), s);
sectionMap.set(s.slug, s);
});
return questionListItems.map((item) => {
const localProgress = getLocalSectionProgress(item, profile, age);
const section = sectionMap.get(item.slug);
let progress = 0;
if (localProgress !== null) {
progress = localProgress;
} else if (progressBySlug && typeof progressBySlug.get(item.slug) === "number") {
progress = progressBySlug.get(item.slug)!;
} else if (section) {
progress = Math.max(0, Math.min(100, Math.round(section.completion_percent)));
} else {
progress = item.progress;
}
return {
slug: frontendSlug,
required: frontendItem ? Boolean(frontendItem.required) : section.is_required,
slug: item.slug,
required: Boolean(item.required),
progress,
};
})
.filter((step) => visibleSlugs.has(step.slug));
}, [sections, fallbackRequiredSteps, locale, profile]);
});
}, [questionListItems, progressBySlug, sections, profile]);
const { completed, total } = getRequiredStepStats(steps);
const completion = total > 0 ? Math.round((completed / total) * 100) : 0;

5
src/data/question-data.ts

@ -86,7 +86,7 @@ const iconMap: Record<string, QuestionCardIcon> = {
"layout-grid": "checklist",
};
const questionsByLocale: Record<Locale, RawQuestionListItem[]> = {
const questionsByLocale: Record<string, RawQuestionListItem[]> = {
en: enQuestions as RawQuestionListItem[],
fa: faQuestions as RawQuestionListItem[],
};
@ -108,7 +108,8 @@ function mapQuestionListItem(item: RawQuestionListItem): QuestionListItem {
}
export function getQuestionListItems(locale: Locale = defaultLocale) {
return questionsByLocale[locale].map(mapQuestionListItem);
const items = questionsByLocale[locale] ?? questionsByLocale[defaultLocale] ?? questionsByLocale.en ?? [];
return items.map(mapQuestionListItem);
}
export function getQuestionListItemBySlug(

2
src/translations/locales/ar.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/az.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/bn.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/da.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/de.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/en.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/es.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/fa.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "اکنون می‌توانید درخواست خود را ثبت کنید تا بتوانیم فرآیند یافتن گزینه‌های مناسب را شروع کنیم",
"requiredStepsProgress": "{completed} از {total} مرحله ضروری کامل شده است",
"findMatches": "یافتن گزینه‌ها",
"findingMatch": "ثبت اطلاعات و یافتن همسر",
"findingMatch": "ثبت",
"optionalInfoPromptTitle": "نکته مهم",
"optionalInfoPromptDescription": "شما تمام بخش‌های ضروری را تکمیل کرده‌اید. با این حال، تکمیل تمام بخش‌ها به ما کمک می‌کند تا گزینه‌های بهتری برای شما پیدا کنیم",
"completeNecessaryForms": "(تکمیل فرم‌های ضروری)",

2
src/translations/locales/fr.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/gu.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/ha.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/he.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/hi.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/id.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/ks.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/pt.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

2
src/translations/locales/ru.json

@ -30,7 +30,7 @@
"requiredStepsDescriptionCompleted": "You can now submit your request so we can start finding the right match for you",
"requiredStepsProgress": "{completed} of {total} required steps completed",
"findMatches": "Find Matches",
"findingMatch": "Submit for Finding Match",
"findingMatch": "Submit",
"optionalInfoPromptTitle": "Important Note",
"optionalInfoPromptDescription": "You've completed all required fields. However, filling in all sections will help us find better matches for you",
"completeNecessaryForms": "(Complete Required Forms)",

Loading…
Cancel
Save