Browse Source
feat: implement marriage onboarding progress tracking with new types, helpers, and UI components
master
feat: implement marriage onboarding progress tracking with new types, helpers, and UI components
master
9 changed files with 154 additions and 34 deletions
-
2public/openapi.json
-
55src/app/questions-list/page.tsx
-
89src/components/questions/progress-helper.ts
-
4src/components/questions/question-answer-storage.tsx
-
15src/components/questions/required-steps-card.tsx
-
8src/data/section-slug-map.ts
-
1src/hooks/marriage/types.ts
-
6src/i18n/locales/en/questions.json
-
6src/i18n/locales/fa/questions.json
2
public/openapi.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,89 @@ |
|||||
|
import { |
||||
|
type QuestionListItem, |
||||
|
isQuestionVisibleForProfile, |
||||
|
isQuestionRequiredForProfile, |
||||
|
} from "@/data/question-data"; |
||||
|
import { hasQuestionAnswerValue } from "./question-answer-storage"; |
||||
|
|
||||
|
export function getStoredAge(): number | null { |
||||
|
try { |
||||
|
if (typeof window === "undefined") return null; |
||||
|
const rawValue = window.localStorage.getItem("marriage:sections:personal_info:answers"); |
||||
|
if (!rawValue) return null; |
||||
|
const storedAnswers = JSON.parse(rawValue); |
||||
|
const ageField = storedAnswers.fields?.find((f: any) => f.label === "Age"); |
||||
|
if (ageField && ageField.value !== undefined && ageField.value !== null) { |
||||
|
const num = Number(ageField.value); |
||||
|
if (Number.isFinite(num)) return num; |
||||
|
} |
||||
|
const dobField = storedAnswers.fields?.find((f: any) => f.label === "Date of Birth"); |
||||
|
if (dobField && dobField.value) { |
||||
|
const dob = new Date(dobField.value); |
||||
|
if (!Number.isNaN(dob.getTime())) { |
||||
|
const today = new Date(); |
||||
|
let age = today.getFullYear() - dob.getFullYear(); |
||||
|
const m = today.getMonth() - dob.getMonth(); |
||||
|
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) { |
||||
|
age--; |
||||
|
} |
||||
|
return age >= 0 ? age : null; |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { } |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
export function getLocalSectionProgress(item: QuestionListItem, profile: any, age: number | null): number | null { |
||||
|
try { |
||||
|
if (typeof window === "undefined") return null; |
||||
|
const storageKey = `marriage:sections:${item.slug}:answers`; |
||||
|
const rawValue = window.localStorage.getItem(storageKey); |
||||
|
if (!rawValue) { |
||||
|
return null; |
||||
|
} |
||||
|
const storedValue = JSON.parse(rawValue); |
||||
|
if (!storedValue || !Array.isArray(storedValue.fields) || storedValue.fields.length === 0) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
const profileContext = { |
||||
|
age, |
||||
|
gender: profile?.gender, |
||||
|
}; |
||||
|
|
||||
|
const visibleQuestions = item.questions |
||||
|
.filter((question) => isQuestionVisibleForProfile(question, profileContext)) |
||||
|
.map((question) => ({ |
||||
|
...question, |
||||
|
required: isQuestionRequiredForProfile(question, profileContext), |
||||
|
})); |
||||
|
|
||||
|
const requiredQuestions = visibleQuestions.filter((q) => q.required); |
||||
|
if (requiredQuestions.length === 0) { |
||||
|
if (visibleQuestions.length === 0) { |
||||
|
return 100; |
||||
|
} |
||||
|
const answeredCount = visibleQuestions.filter((q, index) => { |
||||
|
const slugifyTitle = (title: string) => title.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, ""); |
||||
|
const fieldKey = `q${index + 1}_${slugifyTitle(q.title)}`; |
||||
|
const field = storedValue.fields.find((f: any) => f.key === fieldKey || f.label === q.title); |
||||
|
return field && hasQuestionAnswerValue(field.value); |
||||
|
}).length; |
||||
|
return Math.round((answeredCount / visibleQuestions.length) * 100); |
||||
|
} |
||||
|
|
||||
|
const answeredRequiredCount = requiredQuestions.filter((q) => { |
||||
|
const originalIndex = item.questions.findIndex((origQ) => origQ.title === q.title); |
||||
|
if (originalIndex === -1) return false; |
||||
|
|
||||
|
const slugifyTitle = (title: string) => title.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, ""); |
||||
|
const fieldKey = `q${originalIndex + 1}_${slugifyTitle(q.title)}`; |
||||
|
const field = storedValue.fields.find((f: any) => f.key === fieldKey || f.label === q.title); |
||||
|
return field && hasQuestionAnswerValue(field.value); |
||||
|
}).length; |
||||
|
|
||||
|
return Math.round((answeredRequiredCount / requiredQuestions.length) * 100); |
||||
|
} catch (e) { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue