fixes some validations

This commit is contained in:
pandeymangg
2025-11-14 11:29:44 +05:30
parent e6e010e801
commit 4d84468269
16 changed files with 63 additions and 103 deletions

View File

@@ -45,7 +45,7 @@ interface QuestionFormInputProps {
updateQuestion?: (questionIdx: number, data: Partial<TSurveyElement>) => void;
updateSurvey?: (data: Partial<TSurveyEndScreenCard> | Partial<TSurveyRedirectUrlCard>) => void;
updateChoice?: (choiceIdx: number, data: Partial<TSurveyQuestionChoice>) => void;
updateMatrixLabel?: (index: number, type: "row" | "column", data: Partial<TSurveyElement>) => void;
updateMatrixLabel?: (index: number, type: "row" | "column", matrixLabel: TI18nString) => void;
isInvalid: boolean;
selectedLanguageCode: string;
setSelectedLanguageCode: (languageCode: string) => void;

View File

@@ -78,19 +78,47 @@ export function BlockConditional({
e.preventDefault();
}
// First, programmatically trigger validation on all question forms
// Validate all forms and check for custom validation rules
let firstInvalidForm: HTMLFormElement | null = null;
for (const element of block.elements) {
const form = elementFormRefs.current.get(element.id);
if (form) {
// Check if form is valid using native validation
// Check HTML5 validity first
if (!form.checkValidity()) {
if (!firstInvalidForm) {
firstInvalidForm = form;
}
form.reportValidity();
} else if (element.required) {
continue;
}
// Custom validation for ranking questions
if (element.type === TSurveyElementTypeEnum.Ranking) {
const response = value[element.id];
const rankingElement = element;
// Check if ranking is incomplete
const hasIncompleteRanking =
(rankingElement.required &&
(!Array.isArray(response) || response.length !== rankingElement.choices.length)) ||
(!rankingElement.required &&
Array.isArray(response) &&
response.length > 0 &&
response.length < rankingElement.choices.length);
if (hasIncompleteRanking) {
// Trigger the ranking form's submit to show the error message
form.requestSubmit();
if (!firstInvalidForm) {
firstInvalidForm = form;
}
continue;
}
}
// For other element types, check if required fields are empty
if (element.required) {
const response = value[element.id];
const isEmpty =
response === undefined ||
@@ -100,12 +128,11 @@ export function BlockConditional({
(typeof response === "object" && !Array.isArray(response) && Object.keys(response).length === 0);
if (isEmpty) {
form.requestSubmit();
if (!firstInvalidForm) {
firstInvalidForm = form;
}
// Dispatch a submit event to trigger the form's onSubmit handler
form.requestSubmit();
return;
continue;
}
}
}
@@ -148,11 +175,7 @@ export function BlockConditional({
const isFirstElement = index === 0;
return (
<div
key={element.id}
className={cn(
index < block.elements.length - 1 ? "fb-border-b fb-border-slate-200 fb-pb-4" : ""
)}>
<div key={element.id} className={cn(index < block.elements.length - 1 ? "fb-pb-4" : "")}>
<ElementConditional
element={element}
value={value[element.id]}

View File

@@ -87,7 +87,14 @@ export function ElementConditional({
choices: TSurveyQuestionChoice[]
): string[] => {
return value
.map((label) => choices.find((choice) => getLocalizedValue(choice.label, languageCode) === label)?.id)
.map((entry) => {
// First check if entry is already a valid choice ID
if (choices.some((c) => c.id === entry)) {
return entry;
}
// Otherwise, treat it as a localized label and find the choice by label
return choices.find((choice) => getLocalizedValue(choice.label, languageCode) === entry)?.id;
})
.filter((id): id is TSurveyQuestionChoice["id"] => id !== undefined);
};
@@ -110,7 +117,6 @@ export function ElementConditional({
question={element}
value={typeof value === "string" ? value : ""}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -125,7 +131,6 @@ export function ElementConditional({
question={element}
value={typeof value === "string" ? value : undefined}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -140,7 +145,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? value : []}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -155,7 +159,6 @@ export function ElementConditional({
question={element}
value={typeof value === "number" ? value : undefined}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -189,7 +192,6 @@ export function ElementConditional({
question={element}
value={typeof value === "number" ? value : undefined}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -204,7 +206,6 @@ export function ElementConditional({
question={element}
value={typeof value === "string" ? value : ""}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -219,7 +220,6 @@ export function ElementConditional({
question={element}
value={typeof value === "string" ? value : ""}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -233,7 +233,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? value : []}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -249,7 +248,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? value : []}
onChange={onChange}
onSubmit={onSubmit}
onFileUpload={onFileUpload}
languageCode={languageCode}
ttc={ttc}
@@ -276,7 +274,6 @@ export function ElementConditional({
question={element}
value={typeof value === "object" && !Array.isArray(value) ? value : {}}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -288,7 +285,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? value : undefined}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -302,7 +298,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? getResponseValueForRankingQuestion(value, element.choices) : []}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}
@@ -315,7 +310,6 @@ export function ElementConditional({
question={element}
value={Array.isArray(value) ? value : undefined}
onChange={onChange}
onSubmit={onSubmit}
languageCode={languageCode}
ttc={ttc}
setTtc={setTtc}

View File

@@ -15,7 +15,6 @@ interface AddressQuestionProps {
question: TSurveyAddressElement;
value?: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -29,7 +28,6 @@ export function AddressQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -95,12 +93,6 @@ export function AddressQuestion({
e.preventDefault();
const updatedTtc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtc);
const containsAllEmptyStrings = safeValue.length === 6 && safeValue.every((item) => item.trim() === "");
if (containsAllEmptyStrings) {
onSubmit({ [question.id]: [] }, updatedTtc);
} else {
onSubmit({ [question.id]: safeValue }, updatedTtc);
}
};
const addressRef = useCallback(

View File

@@ -12,7 +12,6 @@ interface ConsentQuestionProps {
question: TSurveyConsentElement;
value: string;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -26,7 +25,6 @@ export function ConsentQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -59,7 +57,6 @@ export function ConsentQuestion({
e.preventDefault();
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value }, updatedTtcObj);
}}>
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}
<Headline

View File

@@ -14,7 +14,6 @@ interface ContactInfoQuestionProps {
question: TSurveyContactInfoElement;
value?: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
autoFocus?: boolean;
languageCode: string;
ttc: TResponseTtc;
@@ -29,7 +28,6 @@ export function ContactInfoQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -90,12 +88,6 @@ export function ContactInfoQuestion({
e.preventDefault();
const updatedTtc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtc);
const containsAllEmptyStrings = safeValue.length === 5 && safeValue.every((item) => item.trim() === "");
if (containsAllEmptyStrings) {
onSubmit({ [question.id]: [] }, updatedTtc);
} else {
onSubmit({ [question.id]: safeValue }, updatedTtc);
}
};
const contactInfoRef = useCallback(

View File

@@ -17,7 +17,6 @@ interface DateQuestionProps {
question: TSurveyDateElement;
value: string;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
autoFocus?: boolean;
languageCode: string;
ttc: TResponseTtc;
@@ -79,7 +78,6 @@ function CalendarCheckIcon() {
export function DateQuestion({
question,
value,
onSubmit,
onChange,
languageCode,
setTtc,
@@ -139,7 +137,6 @@ export function DateQuestion({
}
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value }, updatedTtcObj);
}}
className="fb-w-full">
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}

View File

@@ -16,7 +16,6 @@ interface FileUploadQuestionProps {
question: TSurveyFileUploadElement;
value: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
onFileUpload: (file: TJsFileUploadParams["file"], config?: TUploadFileConfig) => Promise<string>;
surveyId: string;
languageCode: string;
@@ -31,7 +30,6 @@ export function FileUploadQuestion({
question,
value,
onChange,
onSubmit,
surveyId,
onFileUpload,
languageCode,
@@ -54,15 +52,9 @@ export function FileUploadQuestion({
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
if (question.required) {
if (value && value.length > 0) {
onSubmit({ [question.id]: value }, updatedTtcObj);
} else {
if (!(value && value.length > 0)) {
alert(t("errors.please_upload_a_file"));
}
} else if (value) {
onSubmit({ [question.id]: value }, updatedTtcObj);
} else {
onSubmit({ [question.id]: "skipped" }, updatedTtcObj);
}
}}
className="fb-w-full">

View File

@@ -14,7 +14,6 @@ interface MatrixQuestionProps {
question: TSurveyMatrixElement;
value: Record<string, string>;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -26,7 +25,6 @@ export function MatrixQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -85,9 +83,8 @@ export function MatrixQuestion({
e.preventDefault();
const updatedTtc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtc);
onSubmit({ [question.id]: value }, updatedTtc);
},
[ttc, question.id, startTime, value, onSubmit, setTtc]
[ttc, question.id, startTime, setTtc]
);
const columnsHeaders = useMemo(

View File

@@ -13,7 +13,6 @@ interface MultipleChoiceMultiProps {
question: TSurveyMultipleChoiceElement;
value: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -27,7 +26,6 @@ export function MultipleChoiceMultiQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -165,7 +163,6 @@ export function MultipleChoiceMultiQuestion({
onChange({ [question.id]: newValue });
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: newValue }, updatedTtcObj);
}}
className="fb-w-full">
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}

View File

@@ -13,7 +13,6 @@ interface MultipleChoiceSingleProps {
question: TSurveyMultipleChoiceElement;
value?: string;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -27,7 +26,6 @@ export function MultipleChoiceSingleQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -109,7 +107,6 @@ export function MultipleChoiceSingleQuestion({
e.preventDefault();
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value }, updatedTtcObj);
}}
className="fb-w-full">
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}

View File

@@ -13,7 +13,6 @@ interface NPSQuestionProps {
question: TSurveyNPSElement;
value?: number;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -27,7 +26,6 @@ export function NPSQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -45,14 +43,6 @@ export function NPSQuestion({
onChange({ [question.id]: number });
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
setTimeout(() => {
onSubmit(
{
[question.id]: number,
},
updatedTtcObj
);
}, 250);
};
const getNPSOptionColor = (idx: number) => {
@@ -69,7 +59,6 @@ export function NPSQuestion({
e.preventDefault();
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value ?? "" }, updatedTtcObj);
}}>
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}
<Headline

View File

@@ -15,7 +15,6 @@ interface OpenTextQuestionProps {
question: TSurveyOpenTextElement;
value: string;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
autoFocus?: boolean;
languageCode: string;
ttc: TResponseTtc;
@@ -30,7 +29,6 @@ export function OpenTextQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -95,7 +93,6 @@ export function OpenTextQuestion({
const updatedTtc = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtc);
onSubmit({ [question.id]: value }, updatedTtc);
};
const computedDir = !value ? dir : "auto";

View File

@@ -16,7 +16,6 @@ interface PictureSelectionProps {
question: TSurveyPictureSelectionElement;
value: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -30,7 +29,6 @@ export function PictureSelectionQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -100,7 +98,6 @@ export function PictureSelectionQuestion({
e.preventDefault();
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value }, updatedTtcObj);
}}
className="fb-w-full">
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}

View File

@@ -19,7 +19,6 @@ interface RankingQuestionProps {
question: TSurveyRankingElement;
value: string[];
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -32,7 +31,6 @@ export function RankingQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -83,9 +81,16 @@ export function RankingQuestion({
setLocalValue(newLocalValue);
// Immediately update parent state with the new ranking
const sortedLabels = newLocalValue
.map((id) => question.choices.find((c) => c.id === id))
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
.map((item) => getLocalizedValue(item.label, languageCode));
onChange({ [question.id]: sortedLabels });
setError(null);
},
[localValue]
[localValue, question.choices, question.id, languageCode, onChange]
);
const handleMove = useCallback(
@@ -100,9 +105,16 @@ export function RankingQuestion({
newLocalValue.splice(newIndex, 0, movedItem);
setLocalValue(newLocalValue);
// Immediately update parent state with the new ranking
const sortedLabels = newLocalValue
.map((id) => question.choices.find((c) => c.id === id))
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
.map((item) => getLocalizedValue(item.label, languageCode));
onChange({ [question.id]: sortedLabels });
setError(null);
},
[localValue]
[localValue, question.choices, question.id, languageCode, onChange]
);
const handleSubmit = (e: Event) => {
@@ -129,10 +141,7 @@ export function RankingQuestion({
onChange({
[question.id]: sortedItems.map((item) => getLocalizedValue(item.label, languageCode)),
});
onSubmit(
{ [question.id]: sortedItems.map((item) => getLocalizedValue(item.label, languageCode)) },
updatedTtcObj
);
// Note: onSubmit prop is () => {} in multi-element blocks, called by block instead
};
return (

View File

@@ -26,7 +26,6 @@ interface RatingQuestionProps {
question: TSurveyRatingElement;
value?: number;
onChange: (responseData: TResponseData) => void;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
@@ -40,7 +39,6 @@ export function RatingQuestion({
question,
value,
onChange,
onSubmit,
languageCode,
ttc,
setTtc,
@@ -58,14 +56,7 @@ export function RatingQuestion({
onChange({ [question.id]: number });
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
setTimeout(() => {
onSubmit(
{
[question.id]: number,
},
updatedTtcObj
);
}, 250);
// Note: onSubmit prop is () => {} in multi-element blocks, called by block instead
};
function HiddenRadioInput({ number, id }: { number: number; id?: string }) {
@@ -112,7 +103,6 @@ export function RatingQuestion({
e.preventDefault();
const updatedTtcObj = getUpdatedTtc(ttc, question.id, performance.now() - startTime);
setTtc(updatedTtcObj);
onSubmit({ [question.id]: value ?? "" }, updatedTtcObj);
}}
className="fb-w-full">
{isMediaAvailable ? <QuestionMedia imgUrl={question.imageUrl} videoUrl={question.videoUrl} /> : null}