Browse Source
feat: implement question flow system with localized support and UI components
front-test-2
feat: implement question flow system with localized support and UI components
front-test-2
38 changed files with 607 additions and 311 deletions
-
28src/app/globals.css
-
249src/app/questions-list/[slug]/question-detail-client.tsx
-
72src/components/questions/conditional-questions.tsx
-
16src/components/questions/progress-helper.ts
-
160src/components/questions/question-renderer.tsx
-
30src/components/questions/question-section-flow.tsx
-
48src/components/questions/question-slider.tsx
-
14src/components/questions/question-snap-list.tsx
-
4src/components/questions/question-title.tsx
-
12src/components/sliders/slider-slide-four.tsx
-
26src/components/ui/notice-box.tsx
-
1src/data/question-data.ts
-
48src/data/questions/en.json
-
46src/data/questions/fa.json
-
4src/translations/locales/ar.json
-
4src/translations/locales/az.json
-
4src/translations/locales/bn.json
-
4src/translations/locales/da.json
-
4src/translations/locales/de.json
-
4src/translations/locales/en.json
-
4src/translations/locales/es.json
-
4src/translations/locales/fa.json
-
4src/translations/locales/fr.json
-
4src/translations/locales/gu.json
-
4src/translations/locales/ha.json
-
4src/translations/locales/he.json
-
4src/translations/locales/hi.json
-
4src/translations/locales/id.json
-
4src/translations/locales/ks.json
-
4src/translations/locales/pt.json
-
4src/translations/locales/ru.json
-
4src/translations/locales/sw.json
-
4src/translations/locales/tg.json
-
4src/translations/locales/tr.json
-
4src/translations/locales/ul.json
-
4src/translations/locales/ur.json
-
4src/translations/locales/uz.json
-
4src/translations/locales/zh.json
@ -0,0 +1,72 @@ |
|||
"use client"; |
|||
|
|||
import { useQuestionAnswers } from "./question-answer-storage"; |
|||
import type { QuestionField } from "@/data/question-data"; |
|||
import QuestionRenderer from "./question-renderer"; |
|||
|
|||
type ConditionalQuestionsProps = { |
|||
parentQuestion: QuestionField; |
|||
parentQuestionIndex: number; |
|||
rules: { |
|||
values: string[]; |
|||
subQuestions: QuestionField[]; |
|||
}[]; |
|||
dobQuestion?: QuestionField; |
|||
dobQuestionIndex?: number; |
|||
}; |
|||
|
|||
export function ConditionalQuestions({ |
|||
parentQuestion, |
|||
parentQuestionIndex, |
|||
rules, |
|||
dobQuestion, |
|||
dobQuestionIndex, |
|||
}: ConditionalQuestionsProps) { |
|||
const { getAnswerValue } = useQuestionAnswers(); |
|||
const parentAnswer = getAnswerValue(parentQuestion, parentQuestionIndex); |
|||
|
|||
// Find if there is a matching rule for the current parent answer
|
|||
const matchedRule = rules.find((rule) => { |
|||
return rule.values.includes(String(parentAnswer)); |
|||
}); |
|||
|
|||
if (!matchedRule) { |
|||
return ( |
|||
<QuestionRenderer |
|||
question={parentQuestion} |
|||
questionIndex={parentQuestionIndex} |
|||
dobQuestion={dobQuestion} |
|||
dobQuestionIndex={dobQuestionIndex} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
return ( |
|||
<div className="flex flex-col gap-5 w-full"> |
|||
<QuestionRenderer |
|||
question={parentQuestion} |
|||
questionIndex={parentQuestionIndex} |
|||
dobQuestion={dobQuestion} |
|||
dobQuestionIndex={dobQuestionIndex} |
|||
/> |
|||
<div className="flex flex-col gap-5 pl-4 border-l-2 border-[#F2465F]/20 mt-2 animate-in fade-in slide-in-from-top-4 duration-300"> |
|||
{matchedRule.subQuestions.map((subQuestion, index) => { |
|||
// Sub-question index is calculated uniquely to prevent conflicts
|
|||
const subIndex = parentQuestionIndex * 100 + index + 1; |
|||
return ( |
|||
<div key={subQuestion.title} className="w-full"> |
|||
<QuestionRenderer |
|||
question={subQuestion} |
|||
questionIndex={subIndex} |
|||
dobQuestion={dobQuestion} |
|||
dobQuestionIndex={dobQuestionIndex} |
|||
/> |
|||
</div> |
|||
); |
|||
})} |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default ConditionalQuestions; |
|||
@ -0,0 +1,160 @@ |
|||
"use client"; |
|||
|
|||
import type { QuestionField } from "@/data/question-data"; |
|||
import QuestionBirthplace from "@/components/questions/question-birthplace"; |
|||
import QuestionButton from "@/components/questions/question-button"; |
|||
import QuestionCheckbox from "@/components/questions/question-checkbox"; |
|||
import QuestionDate from "@/components/questions/question-date"; |
|||
import QuestionDropdown from "@/components/questions/question-dropdown"; |
|||
import QuestionFile from "@/components/questions/question-file"; |
|||
import QuestionNumber from "@/components/questions/question-number"; |
|||
import QuestionPhone from "@/components/questions/question-phone"; |
|||
import QuestionPhoto from "@/components/questions/question-photo"; |
|||
import QuestionRadio from "@/components/questions/question-radio"; |
|||
import QuestionSlider from "@/components/questions/question-slider"; |
|||
import QuestionText from "@/components/questions/question-text"; |
|||
|
|||
type QuestionRendererProps = { |
|||
question: QuestionField; |
|||
questionIndex: number; |
|||
disabled?: boolean; |
|||
dobQuestion?: QuestionField; |
|||
dobQuestionIndex?: number; |
|||
}; |
|||
|
|||
export function QuestionRenderer({ |
|||
question, |
|||
questionIndex, |
|||
disabled, |
|||
dobQuestion, |
|||
dobQuestionIndex, |
|||
}: QuestionRendererProps) { |
|||
const compactTextHeight = |
|||
question.title.toLowerCase().includes("email") || |
|||
question.title.includes("ایمیل") || |
|||
question.title.toLowerCase().includes("duration") || |
|||
question.title.includes("مدت") |
|||
? "h-[54px]" |
|||
: undefined; |
|||
|
|||
switch (question.type) { |
|||
case "birthplace": |
|||
return ( |
|||
<QuestionBirthplace |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "button": |
|||
return ( |
|||
<QuestionButton |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "checkbox": |
|||
return ( |
|||
<QuestionCheckbox |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "date": |
|||
return ( |
|||
<QuestionDate |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "dropdown": |
|||
return ( |
|||
<QuestionDropdown |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "file": |
|||
return ( |
|||
<QuestionFile |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "number": |
|||
if ( |
|||
question.title === "Age" && |
|||
dobQuestion && |
|||
dobQuestionIndex !== undefined |
|||
) { |
|||
return ( |
|||
<QuestionNumber |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
derivedFromQuestion={dobQuestion} |
|||
derivedFromQuestionIndex={dobQuestionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
} |
|||
|
|||
return ( |
|||
<QuestionNumber |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "phone": |
|||
return ( |
|||
<QuestionPhone |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "photo": |
|||
return ( |
|||
<QuestionPhoto |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "radio": |
|||
return ( |
|||
<QuestionRadio |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "slider": |
|||
case "scale": |
|||
return ( |
|||
<QuestionSlider |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
case "text": |
|||
return ( |
|||
<QuestionText |
|||
question={question} |
|||
questionIndex={questionIndex} |
|||
heightClassName={compactTextHeight} |
|||
disabled={disabled} |
|||
/> |
|||
); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
export default QuestionRenderer; |
|||
@ -0,0 +1,26 @@ |
|||
import type { ReactNode } from "react"; |
|||
|
|||
type NoticeBoxProps = { |
|||
children?: ReactNode; |
|||
text?: string; |
|||
className?: string; |
|||
}; |
|||
|
|||
export function NoticeBox({ children, text, className = "" }: NoticeBoxProps) { |
|||
return ( |
|||
<div |
|||
className={[ |
|||
"mt-3 bg-[#F14B46]/10 py-2.5 px-3.5 border border-[#F14B46] rounded-xl text-center", |
|||
className, |
|||
] |
|||
.filter(Boolean) |
|||
.join(" ")} |
|||
> |
|||
<p className="text-[#F14B46] group-12 font-semibold text-center leading-[1.6]"> |
|||
{text || children} |
|||
</p> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
export default NoticeBox; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue