mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-24 03:21:20 -05:00
feat: Placeholder input for contact and address question (#4549)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6e1ee6df12
commit
d197c91995
+8
-22
@@ -83,9 +83,7 @@ export const AddressQuestionForm = ({
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
updateQuestion(questionIdx, { required: false });
|
||||
}
|
||||
updateQuestion(questionIdx, { required: !allFieldsAreOptional });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
question.addressLine1,
|
||||
@@ -152,25 +150,13 @@ export const AddressQuestionForm = ({
|
||||
<QuestionToggleTable
|
||||
type="address"
|
||||
fields={fields}
|
||||
onShowToggle={(field, show) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show,
|
||||
required: field.required,
|
||||
},
|
||||
// when show changes, and the field is required, the question should be required
|
||||
...(show && field.required && { required: true }),
|
||||
});
|
||||
}}
|
||||
onRequiredToggle={(field, required) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show: field.show,
|
||||
required,
|
||||
},
|
||||
required: true,
|
||||
});
|
||||
}}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
isInvalid={isInvalid}
|
||||
updateQuestion={updateQuestion}
|
||||
selectedLanguageCode={selectedLanguageCode}
|
||||
setSelectedLanguageCode={setSelectedLanguageCode}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+9
-22
@@ -78,9 +78,8 @@ export const ContactInfoQuestionForm = ({
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
updateQuestion(questionIdx, { required: false });
|
||||
}
|
||||
updateQuestion(questionIdx, { required: !allFieldsAreOptional });
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [question.firstName, question.lastName, question.email, question.phone, question.company]);
|
||||
|
||||
@@ -142,25 +141,13 @@ export const ContactInfoQuestionForm = ({
|
||||
<QuestionToggleTable
|
||||
type="contact"
|
||||
fields={fields}
|
||||
onShowToggle={(field, show) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show,
|
||||
required: field.required,
|
||||
},
|
||||
// when show changes, and the field is required, the question should be required
|
||||
...(show && field.required && { required: true }),
|
||||
});
|
||||
}}
|
||||
onRequiredToggle={(field, required) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show: field.show,
|
||||
required,
|
||||
},
|
||||
required: true,
|
||||
});
|
||||
}}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
isInvalid={isInvalid}
|
||||
updateQuestion={updateQuestion}
|
||||
selectedLanguageCode={selectedLanguageCode}
|
||||
setSelectedLanguageCode={setSelectedLanguageCode}
|
||||
locale={locale}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+29
@@ -108,6 +108,21 @@ export const QuestionCard = ({
|
||||
|
||||
const getIsRequiredToggleDisabled = (): boolean => {
|
||||
if (question.type === TSurveyQuestionTypeEnum.Address) {
|
||||
const allFieldsAreOptional = [
|
||||
question.addressLine1,
|
||||
question.addressLine2,
|
||||
question.city,
|
||||
question.state,
|
||||
question.zip,
|
||||
question.country,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [
|
||||
question.addressLine1,
|
||||
question.addressLine2,
|
||||
@@ -121,6 +136,20 @@ export const QuestionCard = ({
|
||||
}
|
||||
|
||||
if (question.type === TSurveyQuestionTypeEnum.ContactInfo) {
|
||||
const allFieldsAreOptional = [
|
||||
question.firstName,
|
||||
question.lastName,
|
||||
question.email,
|
||||
question.phone,
|
||||
question.company,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [question.firstName, question.lastName, question.email, question.phone, question.company]
|
||||
.filter((field) => field.show)
|
||||
.some((condition) => condition.required === true);
|
||||
|
||||
+29
@@ -6,9 +6,12 @@ import { checkForEmptyFallBackValue } from "@formbricks/lib/utils/recall";
|
||||
import { ZSegmentFilters } from "@formbricks/types/segment";
|
||||
import {
|
||||
TI18nString,
|
||||
TInputFieldConfig,
|
||||
TSurvey,
|
||||
TSurveyAddressQuestion,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyContactInfoQuestion,
|
||||
TSurveyEndScreenCard,
|
||||
TSurveyLanguage,
|
||||
TSurveyMatrixQuestion,
|
||||
@@ -67,6 +70,26 @@ const handleI18nCheckForMatrixLabels = (
|
||||
return rowsAndColumns.every((label) => isLabelValidForAllLanguages(label, languages));
|
||||
};
|
||||
|
||||
const handleI18nCheckForContactAndAddressFields = (
|
||||
question: TSurveyContactInfoQuestion | TSurveyAddressQuestion,
|
||||
languages: TSurveyLanguage[]
|
||||
): boolean => {
|
||||
let fields: TInputFieldConfig[] = [];
|
||||
if (question.type === "contactInfo") {
|
||||
const { firstName, lastName, phone, email, company } = question;
|
||||
fields = [firstName, lastName, phone, email, company];
|
||||
} else if (question.type === "address") {
|
||||
const { addressLine1, addressLine2, city, state, zip, country } = question;
|
||||
fields = [addressLine1, addressLine2, city, state, zip, country];
|
||||
}
|
||||
return fields.every((field) => {
|
||||
if (field.show) {
|
||||
return isLabelValidForAllLanguages(field.placeholder, languages);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// Validation rules
|
||||
export const validationRules = {
|
||||
openText: (question: TSurveyOpenTextQuestion, languages: TSurveyLanguage[]) => {
|
||||
@@ -96,6 +119,12 @@ export const validationRules = {
|
||||
matrix: (question: TSurveyMatrixQuestion, languages: TSurveyLanguage[]) => {
|
||||
return handleI18nCheckForMatrixLabels(question, languages);
|
||||
},
|
||||
contactInfo: (question: TSurveyContactInfoQuestion, languages: TSurveyLanguage[]) => {
|
||||
return handleI18nCheckForContactAndAddressFields(question, languages);
|
||||
},
|
||||
address: (question: TSurveyAddressQuestion, languages: TSurveyLanguage[]) => {
|
||||
return handleI18nCheckForContactAndAddressFields(question, languages);
|
||||
},
|
||||
// Assuming headline is of type TI18nString
|
||||
defaultValidation: (question: TSurveyQuestion, languages: TSurveyLanguage[], isFirstQuestion: boolean) => {
|
||||
// headline and subheader are default for every question
|
||||
|
||||
@@ -25,7 +25,7 @@ export function LanguageIndicator({
|
||||
const languageDropdownRef = useRef(null);
|
||||
|
||||
const changeLanguage = (language: TSurveyLanguage) => {
|
||||
setSelectedLanguageCode(language.language.code);
|
||||
setSelectedLanguageCode(language.default ? "default" : language.language.code);
|
||||
if (setFirstRender) {
|
||||
//for lexical editor
|
||||
setFirstRender(true);
|
||||
|
||||
@@ -148,7 +148,12 @@ export const QuestionFormInput = ({
|
||||
}
|
||||
|
||||
return (
|
||||
(question && (question[id as keyof TSurveyQuestion] as TI18nString)) ||
|
||||
(question &&
|
||||
(id.includes(".")
|
||||
? // Handle nested properties
|
||||
(question[id.split(".")[0] as keyof TSurveyQuestion] as any)?.[id.split(".")[1]]
|
||||
: // Original behavior
|
||||
(question[id as keyof TSurveyQuestion] as TI18nString))) ||
|
||||
createI18nString("", surveyLanguageCodes)
|
||||
);
|
||||
}, [
|
||||
@@ -340,10 +345,22 @@ export const QuestionFormInput = ({
|
||||
const updateQuestionDetails = useCallback(
|
||||
(translatedText: TI18nString) => {
|
||||
if (updateQuestion) {
|
||||
updateQuestion(questionIdx, { [id]: translatedText });
|
||||
// Handle nested properties if id contains a dot
|
||||
if (id.includes(".")) {
|
||||
const [parent, child] = id.split(".");
|
||||
updateQuestion(questionIdx, {
|
||||
[parent]: {
|
||||
...question[parent],
|
||||
[child]: translatedText,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Original behavior for non-nested properties
|
||||
updateQuestion(questionIdx, { [id]: translatedText });
|
||||
}
|
||||
}
|
||||
},
|
||||
[id, questionIdx, updateQuestion]
|
||||
[id, questionIdx, updateQuestion, question]
|
||||
);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurvey,
|
||||
TSurveyAddressQuestion,
|
||||
TSurveyContactInfoQuestion,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
|
||||
interface QuestionToggleTableProps {
|
||||
type: "address" | "contact";
|
||||
@@ -8,48 +16,75 @@ interface QuestionToggleTableProps {
|
||||
show: boolean;
|
||||
id: string;
|
||||
label: string;
|
||||
placeholder: TI18nString;
|
||||
}[];
|
||||
onShowToggle: (
|
||||
field: {
|
||||
id: string;
|
||||
required: boolean;
|
||||
show: boolean;
|
||||
},
|
||||
show: boolean
|
||||
) => void;
|
||||
onRequiredToggle: (
|
||||
field: {
|
||||
id: string;
|
||||
show: boolean;
|
||||
required: boolean;
|
||||
},
|
||||
required: boolean
|
||||
localSurvey: TSurvey;
|
||||
questionIdx: number;
|
||||
isInvalid: boolean;
|
||||
updateQuestion: (
|
||||
questionIdx: number,
|
||||
updatedAttributes: Partial<TSurveyContactInfoQuestion | TSurveyAddressQuestion>
|
||||
) => void;
|
||||
selectedLanguageCode: string;
|
||||
setSelectedLanguageCode: (languageCode: string) => void;
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
export const QuestionToggleTable = ({
|
||||
type,
|
||||
fields,
|
||||
onShowToggle,
|
||||
onRequiredToggle,
|
||||
localSurvey,
|
||||
questionIdx,
|
||||
isInvalid,
|
||||
updateQuestion,
|
||||
selectedLanguageCode,
|
||||
setSelectedLanguageCode,
|
||||
locale,
|
||||
}: QuestionToggleTableProps) => {
|
||||
const onShowToggle = (
|
||||
field: { id: string; show: boolean; required: boolean; placeholder: TI18nString },
|
||||
show: boolean
|
||||
) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onRequiredToggle = (
|
||||
field: { id: string; show: boolean; required: boolean; placeholder: TI18nString },
|
||||
required: boolean
|
||||
) => {
|
||||
updateQuestion(questionIdx, {
|
||||
[field.id]: {
|
||||
show: field.show,
|
||||
required,
|
||||
placeholder: field.placeholder,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<table className="mt-4 w-1/2 table-fixed">
|
||||
<table className="mt-4 w-full table-fixed">
|
||||
<thead>
|
||||
<tr className="text-left text-slate-800">
|
||||
<th className="w-1/2 text-sm font-semibold">
|
||||
<th className="w-1/4 text-sm font-semibold">
|
||||
{type === "address"
|
||||
? t("environments.surveys.edit.address_fields")
|
||||
: t("environments.surveys.edit.contact_fields")}
|
||||
</th>
|
||||
<th className="w-1/4 text-sm font-semibold">{t("common.show")}</th>
|
||||
<th className="w-1/4 text-sm font-semibold">{t("environments.surveys.edit.required")}</th>
|
||||
<th className="w-1/6 text-sm font-semibold">{t("common.show")}</th>
|
||||
<th className="w-1/6 text-sm font-semibold">{t("environments.surveys.edit.required")}</th>
|
||||
<th className="text-sm font-semibold">{t("common.placeholder")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fields.map((field) => (
|
||||
<tr className="text-slate-900">
|
||||
<tr className="text-slate-900" key={field.id}>
|
||||
<td className="py-2 text-sm">{field.label}</td>
|
||||
<td className="py-">
|
||||
<Switch
|
||||
@@ -72,6 +107,21 @@ export const QuestionToggleTable = ({
|
||||
disabled={!field.show}
|
||||
/>
|
||||
</td>
|
||||
<td className="py-2">
|
||||
<QuestionFormInput
|
||||
id={`${field.id}.placeholder`}
|
||||
label={""}
|
||||
value={field.placeholder}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
isInvalid={isInvalid}
|
||||
updateQuestion={updateQuestion}
|
||||
selectedLanguageCode={selectedLanguageCode}
|
||||
setSelectedLanguageCode={setSelectedLanguageCode}
|
||||
contactAttributeKeys={[]}
|
||||
locale={locale}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -184,8 +184,16 @@ test.describe("Survey Create & Submit Response without logic", async () => {
|
||||
|
||||
// Address Question
|
||||
await expect(page.getByText(surveys.createAndSubmit.address.question)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.address.placeholder)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.address.placeholder).fill("This is my Address");
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.addressLine1)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByPlaceholder(surveys.createAndSubmit.address.placeholder.addressLine1)
|
||||
.fill("This is my Address");
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.city)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.city).fill("This is my city");
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.zip)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await page.locator("#questionCard-10").getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// Contact Info Question
|
||||
@@ -523,6 +531,28 @@ test.describe("Multi Language Survey Create", async () => {
|
||||
await page
|
||||
.getByPlaceholder("Your question here. Recall")
|
||||
.fill(surveys.germanCreate.addressQuestion.question);
|
||||
await page.locator('[id="addressLine1\\.placeholder"]').click();
|
||||
await page
|
||||
.locator('[id="addressLine1\\.placeholder"]')
|
||||
.fill(surveys.germanCreate.addressQuestion.placeholder.addressLine1);
|
||||
await page.locator('[id="addressLine2\\.placeholder"]').click();
|
||||
await page
|
||||
.locator('[id="addressLine2\\.placeholder"]')
|
||||
.fill(surveys.germanCreate.addressQuestion.placeholder.addressLine2);
|
||||
await page.locator('[id="city\\.placeholder"]').click();
|
||||
await page
|
||||
.locator('[id="city\\.placeholder"]')
|
||||
.fill(surveys.germanCreate.addressQuestion.placeholder.city);
|
||||
await page.locator('[id="state\\.placeholder"]').click();
|
||||
await page
|
||||
.locator('[id="state\\.placeholder"]')
|
||||
.fill(surveys.germanCreate.addressQuestion.placeholder.state);
|
||||
await page.locator('[id="zip\\.placeholder"]').click();
|
||||
await page.locator('[id="zip\\.placeholder"]').fill(surveys.germanCreate.addressQuestion.placeholder.zip);
|
||||
await page.locator('[id="country\\.placeholder"]').click();
|
||||
await page
|
||||
.locator('[id="country\\.placeholder"]')
|
||||
.fill(surveys.germanCreate.addressQuestion.placeholder.country);
|
||||
await page.getByText("Show Advanced settings").first().click();
|
||||
await page.getByPlaceholder("Next").click();
|
||||
await page.getByPlaceholder("Next").fill(surveys.germanCreate.next);
|
||||
@@ -810,10 +840,22 @@ test.describe("Testing Survey with advanced logic", async () => {
|
||||
|
||||
// Address Question
|
||||
await expect(page.getByText(surveys.createWithLogicAndSubmit.address.question)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder)).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder)
|
||||
.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
.fill("This is my Address");
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.city)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.city)
|
||||
.fill("This is my city");
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.zip)
|
||||
).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await page.locator("#questionCard-13").getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// loading spinner -> wait for it to disappear
|
||||
|
||||
@@ -158,7 +158,11 @@ export const surveys = {
|
||||
},
|
||||
address: {
|
||||
question: "Where do you live?",
|
||||
placeholder: "Address Line 1",
|
||||
placeholder: {
|
||||
addressLine1: "Address Line 1",
|
||||
city: "City",
|
||||
zip: "Zip",
|
||||
},
|
||||
},
|
||||
contactInfo: {
|
||||
question: "Contact Info",
|
||||
@@ -233,7 +237,11 @@ export const surveys = {
|
||||
},
|
||||
address: {
|
||||
question: "Where do you live?",
|
||||
placeholder: "Address Line 1",
|
||||
placeholder: {
|
||||
addressLine1: "Address Line 1",
|
||||
city: "City",
|
||||
zip: "Zip",
|
||||
},
|
||||
},
|
||||
ranking: {
|
||||
question: "This is my Ranking Question",
|
||||
@@ -307,6 +315,14 @@ export const surveys = {
|
||||
},
|
||||
addressQuestion: {
|
||||
question: "Wo wohnst du ?",
|
||||
placeholder: {
|
||||
addressLine1: "Adresse",
|
||||
addressLine2: "Adresse",
|
||||
city: "Adresse",
|
||||
state: "Adresse",
|
||||
zip: "Adresse",
|
||||
country: "Adresse",
|
||||
},
|
||||
},
|
||||
ranking: {
|
||||
question: "Was ist für Sie im Leben am wichtigsten?",
|
||||
|
||||
Reference in New Issue
Block a user