From 687a40a31e5c051a975ae2d990b9bec6a49f45ad Mon Sep 17 00:00:00 2001 From: zaynom Date: Tue, 31 Dec 2024 00:08:14 +0100 Subject: [PATCH 1/2] feat: save and load survey progress in local storage - Save user progress (form data, section index, question index) to local storage. - Load saved progress as default values when the survey is reopened. --- src/components/survey/section.tsx | 192 ++++++++++++++------------ src/components/survey/survey-form.tsx | 17 ++- 2 files changed, 117 insertions(+), 92 deletions(-) diff --git a/src/components/survey/section.tsx b/src/components/survey/section.tsx index 69759cb..406a492 100644 --- a/src/components/survey/section.tsx +++ b/src/components/survey/section.tsx @@ -7,6 +7,7 @@ type SectionProps = { section: SurveyQuestionsYamlFile; next: () => void; setProgress: (n: number) => void; + questions: SurveyQuestionsYamlFile[]; }; export const ERRORS = { @@ -41,110 +42,121 @@ const normalizeAnswers = ( return convertedAnswers; }; -export default React.memo(({ section, next, setProgress }: SectionProps) => { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(ERRORS.none); - const { register, getValues } = useForm(); - const [QIndex, setQIndex] = useState(0); - const isLastQuestion = section.questions.length === QIndex + 1; - const isRequired = !!section.questions[QIndex].required; +export default React.memo( + ({ section, next, setProgress, questions }: SectionProps) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(ERRORS.none); + const savedAnswars = + localStorage.getItem("answars") || JSON.stringify(questions); + const { register, getValues } = useForm({ + defaultValues: JSON.parse(savedAnswars) + }); + const savedQIndex = parseInt(localStorage.getItem("savedQIndex") || "0"); + const [QIndex, setQIndex] = useState(savedQIndex); + const isLastQuestion = section.questions.length === QIndex + 1; + const isRequired = !!section.questions[QIndex].required; - const nextQuestion = async () => { - setError(ERRORS.none); - const name = `${section.label}-q-${QIndex}`; - const value = getValues(name); - // value === null default value for simple questions and false for multiple ones - if (isRequired && (value === null || value === false)) { - setError(ERRORS.required); - return; - } + const nextQuestion = async () => { + localStorage.setItem("answars", JSON.stringify(getValues())); + localStorage.setItem("savedQIndex", QIndex.toString()); + setError(ERRORS.none); + const name = `${section.label}-q-${QIndex}`; + const value = getValues(name); + // value === null default value for simple questions and false for multiple ones + if (isRequired && (value === null || value === false)) { + setError(ERRORS.required); + return; + } - if (isLastQuestion) { - await submitData(); - setProgress(1); - } else { - setQIndex((QIndex) => QIndex + 1); - setProgress(1); - } - scrollToSection("#steps"); - }; - const backToPreviousQ = () => { - if (QIndex > 0) { - setQIndex((QIndex) => QIndex - 1); - setProgress(-1); - } - }; + if (isLastQuestion) { + await submitData(); + setProgress(1); + } else { + setQIndex((QIndex) => QIndex + 1); + setProgress(1); + } + scrollToSection("#steps"); + }; + const backToPreviousQ = () => { + if (QIndex > 0) { + localStorage.setItem("answars", JSON.stringify(getValues())); + localStorage.setItem("savedQIndex", QIndex.toString()); + setQIndex((QIndex) => QIndex - 1); + setProgress(-1); + } + }; - const submitData = useCallback(async () => { - const answers = normalizeAnswers(getValues()); - setLoading(true); - const { error } = await submitAnswers({ - answers - }); - if (error) { - setError(ERRORS.submission); - setLoading(false); - } else { - next(); - setLoading(false); - } - }, []); + const submitData = useCallback(async () => { + const answers = normalizeAnswers(getValues()); + setLoading(true); + const { error } = await submitAnswers({ + answers + }); + if (error) { + setError(ERRORS.submission); + setLoading(false); + } else { + next(); + setLoading(false); + } + }, []); - // Add useEffect to clear error after 3 seconds - useEffect(() => { - if (error) { - const timer = setTimeout(() => { - setError(ERRORS.none); - }, 2000); + // Add useEffect to clear error after 3 seconds + useEffect(() => { + if (error) { + const timer = setTimeout(() => { + setError(ERRORS.none); + }, 2000); - return () => clearTimeout(timer); - } - }, [error]); + return () => clearTimeout(timer); + } + }, [error]); - return ( -
-
- {section.questions.map((q, i) => ( - - ))} -
-
-
- {QIndex > 0 && backToPreviousQ()} />} + return ( +
+
+ {section.questions.map((q, i) => ( + + ))}
-
- {isRequired ? null : ( +
+
+ {QIndex > 0 && backToPreviousQ()} />} +
+
+ {isRequired ? null : ( + + )} - )} - +
-
- ); -}); + ); + } +); const ErrorMessage = ({ error }: { error: string }) => { if (!error) return null; diff --git a/src/components/survey/survey-form.tsx b/src/components/survey/survey-form.tsx index 650eb70..8952577 100644 --- a/src/components/survey/survey-form.tsx +++ b/src/components/survey/survey-form.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import LoadingBar from "react-top-loading-bar"; import { Steps } from "./steps"; import Section from "./section"; @@ -9,9 +9,20 @@ type Props = { }; export const SurveyForm = ({ questions }: Props) => { - const [selectedSectionIndex, setSelectedSectionIndex] = useState(0); + const savedSectionIndex = parseInt( + localStorage.getItem("savedSelectedSectionIndex") || "0" + ); + const [selectedSectionIndex, setSelectedSectionIndex] = + useState(savedSectionIndex); const [progress, setPr] = useState(0); + useEffect(() => { + localStorage.setItem( + "savedSelectedSectionIndex", + selectedSectionIndex.toString() + ); + }, [selectedSectionIndex]); + const section = useMemo( () => questions[selectedSectionIndex], [questions, selectedSectionIndex] @@ -42,6 +53,7 @@ export const SurveyForm = ({ questions }: Props) => { const next = useCallback(() => { if (selectedSectionIndex + 1 < questions.length) { setSelectedSectionIndex((prv) => prv + 1); + localStorage.setItem("savedQIndex", "0"); } else { goToThanksPage(); } @@ -57,6 +69,7 @@ export const SurveyForm = ({ questions }: Props) => { next={next} key={section.label} setProgress={setProgress} + questions={questions} />
From 13f4cf1dca07743bdeb23a34d228e0a7c205d4c2 Mon Sep 17 00:00:00 2001 From: zaynom Date: Tue, 31 Dec 2024 00:11:39 +0100 Subject: [PATCH 2/2] fix: sync progress bar with local data - Update the progress bar value to match the locally saved data --- src/components/survey/section.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/survey/section.tsx b/src/components/survey/section.tsx index 406a492..636d55c 100644 --- a/src/components/survey/section.tsx +++ b/src/components/survey/section.tsx @@ -56,6 +56,17 @@ export default React.memo( const isLastQuestion = section.questions.length === QIndex + 1; const isRequired = !!section.questions[QIndex].required; + useEffect(() => { + const _savedAnswars = JSON.parse(localStorage.getItem("answars") || "{}"); + const keys = Object.keys(_savedAnswars); + const currentSectionLenght = keys.filter((key) => + key.startsWith(section.label) + ).length; + const progress = keys.length - (currentSectionLenght - QIndex); + + setProgress(progress); + }, []); + const nextQuestion = async () => { localStorage.setItem("answars", JSON.stringify(getValues())); localStorage.setItem("savedQIndex", QIndex.toString());