Compare commits

..

6 Commits

Author SHA1 Message Date
Dhruwang
bf6acec10b fix: transaltions 2026-02-04 18:39:05 +05:30
Dhruwang
62e2540511 Merge branch 'main' of https://github.com/formbricks/formbricks into chore-update-hun-20260203 2026-02-04 18:36:52 +05:30
Dhruwang
12bf5b71cf fix: translation check 2026-02-04 18:35:15 +05:30
Dhruwang Jariwala
727e586b16 feat: responseID in response table (#7195) 2026-02-04 09:59:37 +00:00
Theodór Tómas
4a9b4d52ca fix: resolve infinite re-render loop in Survey Editor (#7142) 2026-02-04 05:03:09 +00:00
Balázs Úr
f9b84b718c fix: Hungarian translations 2026-02-03 13:17:34 +01:00
21 changed files with 177 additions and 165 deletions

View File

@@ -316,6 +316,14 @@ export const generateResponseTableColumns = (
},
};
const responseIdColumn: ColumnDef<TResponseTableData> = {
accessorKey: "responseId",
header: () => <div className="gap-x-1.5">{t("common.response_id")}</div>,
cell: ({ row }) => {
return <IdBadge id={row.original.responseId} />;
},
};
const quotasColumn: ColumnDef<TResponseTableData> = {
accessorKey: "quota",
header: t("common.quota"),
@@ -376,24 +384,24 @@ export const generateResponseTableColumns = (
const hiddenFieldColumns: ColumnDef<TResponseTableData>[] = survey.hiddenFields.fieldIds
? survey.hiddenFields.fieldIds.map((hiddenFieldId) => {
return {
accessorKey: "HIDDEN_FIELD_" + hiddenFieldId,
header: () => (
<div className="flex items-center space-x-2 overflow-hidden">
<span className="h-4 w-4">
<EyeOffIcon className="h-4 w-4" />
</span>
<span className="truncate">{hiddenFieldId}</span>
</div>
),
cell: ({ row }) => {
const hiddenFieldResponse = row.original.responseData[hiddenFieldId];
if (typeof hiddenFieldResponse === "string") {
return <div className="text-slate-900">{hiddenFieldResponse}</div>;
}
},
};
})
return {
accessorKey: "HIDDEN_FIELD_" + hiddenFieldId,
header: () => (
<div className="flex items-center space-x-2 overflow-hidden">
<span className="h-4 w-4">
<EyeOffIcon className="h-4 w-4" />
</span>
<span className="truncate">{hiddenFieldId}</span>
</div>
),
cell: ({ row }) => {
const hiddenFieldResponse = row.original.responseData[hiddenFieldId];
if (typeof hiddenFieldResponse === "string") {
return <div className="text-slate-900">{hiddenFieldResponse}</div>;
}
},
};
})
: [];
const metadataColumns = getMetadataColumnsData(t);
@@ -414,6 +422,7 @@ export const generateResponseTableColumns = (
const baseColumns = [
personColumn,
singleUseIdColumn,
responseIdColumn,
dateColumn,
...(showQuotasColumn ? [quotasColumn] : []),
statusColumn,

View File

@@ -323,6 +323,7 @@ checksums:
common/request_trial_license: 560df1240ef621f7c60d3f7d65422ccd
common/reset_to_default: 68ee98b46677392f44b505b268053b26
common/response: c7a9d88269d8ff117abcbc0d97f88b2c
common/response_id: 73375099cc976dc7203b8e27f5f709e0
common/responses: 14bb6c69f906d7bbd1359f7ef1bb3c28
common/restart: bab6232e89f24e3129f8e48268739d5b
common/role: 53743bbb6ca938f5b893552e839d067f

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Testlizenz anfordern",
"reset_to_default": "Auf Standard zurücksetzen",
"response": "Antwort",
"response_id": "Antwort-ID",
"responses": "Antworten",
"restart": "Neustart",
"role": "Rolle",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Request trial license",
"reset_to_default": "Reset to default",
"response": "Response",
"response_id": "Response ID",
"responses": "Responses",
"restart": "Restart",
"role": "Role",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Solicitar licencia de prueba",
"reset_to_default": "Restablecer a valores predeterminados",
"response": "Respuesta",
"response_id": "ID de respuesta",
"responses": "Respuestas",
"restart": "Reiniciar",
"role": "Rol",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Demander une licence d'essai",
"reset_to_default": "Réinitialiser par défaut",
"response": "Réponse",
"response_id": "ID de réponse",
"responses": "Réponses",
"restart": "Recommencer",
"role": "Rôle",

View File

@@ -254,7 +254,7 @@
"label": "Címke",
"language": "Nyelv",
"learn_more": "Tudjon meg többet",
"license_expired": "Licenc lejárt",
"license_expired": "A licenc lejárt",
"light_overlay": "Világos rávetítés",
"limits_reached": "Korlátok elérve",
"link": "Összekapcsolás",
@@ -350,6 +350,7 @@
"request_trial_license": "Próbalicenc kérése",
"reset_to_default": "Visszaállítás az alapértelmezettre",
"response": "Válasz",
"response_id": "Válasz azonosító",
"responses": "Válaszok",
"restart": "Újraindítás",
"role": "Szerep",
@@ -462,7 +463,7 @@
"you_have_reached_your_monthly_miu_limit_of": "Elérte a havi MIU-korlátját ennek:",
"you_have_reached_your_monthly_response_limit_of": "Elérte a havi válaszkorlátját ennek:",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vissza lesz állítva a közösségi kiadásra ekkor: {date}.",
"your_license_has_expired_please_renew": "A vállalati licenced lejárt. Kérjük, újítsd meg, hogy továbbra is használhasd a vállalati funkciókat."
"your_license_has_expired_please_renew": "A vállalati licence lejárt. Újítsa meg, hogy továbbra is használhassa a vállalati funkciókat."
},
"emails": {
"accept": "Elfogadás",
@@ -811,7 +812,7 @@
"webhook_deleted_successfully": "A webhorog sikeresen törölve",
"webhook_name_placeholder": "Választható: címkézze meg a webhorgot az egyszerű azonosításért",
"webhook_test_failed_due_to": "A webhorog tesztelése sikertelen a következő miatt:",
"webhook_updated_successfully": "A webhorog sikeresen frissítve.",
"webhook_updated_successfully": "A webhorog sikeresen frissítve",
"webhook_url_placeholder": "Illessze be azt az URL-t, amelyen az eseményt aktiválni szeretné"
},
"website_or_app_integration_description": "A Formbricks integrálása a webhelyébe vagy alkalmazásába",
@@ -820,7 +821,7 @@
"segments": {
"add_filter_below": "Szűrő hozzáadása lent",
"add_your_first_filter_to_get_started": "Adja hozzá az első szűrőt a kezdéshez",
"cannot_delete_segment_used_in_surveys": "Nem tudja eltávolítani ezt a szakaszt, mert még mindig használatban van ezekben a kérdőívekben:",
"cannot_delete_segment_used_in_surveys": "Nem tudja törölni ezt a szakaszt, mert még mindig használatban van ezekben a kérdőívekben:",
"clone_and_edit_segment": "Szakasz klónozása és szerkesztése",
"create_group": "Csoport létrehozása",
"create_your_first_segment": "Hozza létre az első szakaszt a kezdéshez",
@@ -851,11 +852,11 @@
"reset_all_filters": "Összes szűrő visszaállítása",
"save_as_new_segment": "Mentés új szakaszként",
"save_your_filters_as_a_segment_to_use_it_in_other_surveys": "A szűrők mentése szakaszként más kérdőívekben való használathoz",
"segment_created_successfully": "A szakasz sikeresen létrehozva!",
"segment_deleted_successfully": "A szakasz sikeresen törölve!",
"segment_created_successfully": "A szakasz sikeresen létrehozva",
"segment_deleted_successfully": "A szakasz sikeresen törölve",
"segment_id": "Szakaszazonosító",
"segment_saved_successfully": "A szakasz sikeresen elmentve",
"segment_updated_successfully": "A szakasz sikeresen frissítve!",
"segment_updated_successfully": "A szakasz sikeresen frissítve",
"segments_help_you_target_users_with_same_characteristics_easily": "A szakaszok segítik a hasonló jellemzőkkel rendelkező felhasználók könnyű megcélzását",
"target_audience": "Célközönség",
"this_action_resets_all_filters_in_this_survey": "Ez a művelet visszaállítja az összes szűrőt ebben a kérdőívben.",
@@ -1004,8 +1005,8 @@
"member_invited_successfully": "A tag sikeresen meghívva",
"once_its_gone_its_gone": "Ha egyszer eltűnt, akkor eltűnt.",
"only_org_owner_can_perform_action": "Csak a szervezet tulajdonosai férhetnek hozzá ehhez a beállításhoz.",
"organization_created_successfully": "A szervezet sikeresen létrehozva!",
"organization_deleted_successfully": "A szervezet sikeresen törölve.",
"organization_created_successfully": "A szervezet sikeresen létrehozva",
"organization_deleted_successfully": "A szervezet sikeresen törölve",
"organization_invite_link_ready": "A szervezete meghívási hivatkozása készen áll!",
"organization_name": "Szervezet neve",
"organization_name_description": "Adjon a szervezetének egy leíró nevet.",
@@ -1105,8 +1106,8 @@
"select_member": "Tag kiválasztása",
"select_workspace": "Munkaterület kiválasztása",
"team_admin": "Csapatadminisztrátor",
"team_created_successfully": "A csapat sikeresen létrehozva.",
"team_deleted_successfully": "A csapat sikeresen törölve.",
"team_created_successfully": "A csapat sikeresen létrehozva",
"team_deleted_successfully": "A csapat sikeresen törölve",
"team_deletion_not_allowed": "Önnek nem engedélyezett ennek a csapatnak a törlése.",
"team_name": "Csapat neve",
"team_name_settings_title": "{teamName} beállításai",
@@ -1129,7 +1130,7 @@
"copy_survey_error": "Nem sikerült másolni a kérdőívet",
"copy_survey_link_to_clipboard": "Kérdőív hivatkozásának másolása a vágólapra",
"copy_survey_partially_success": "{success} kérdőív sikeresen másolva, {error} sikertelen.",
"copy_survey_success": "A kérdőív sikeresen másolva!",
"copy_survey_success": "A kérdőív sikeresen másolva",
"delete_survey_and_responses_warning": "Biztosan törölni szeretné ezt a kérdőívet és az összes válaszát?",
"edit": {
"1_choose_the_default_language_for_this_survey": "1. Válassza ki a kérdőív alapértelmezett nyelvét:",
@@ -1273,7 +1274,7 @@
"disable_the_visibility_of_survey_progress": "A kérdőív előrehaladási folyamata láthatóságának letiltása.",
"display_an_estimate_of_completion_time_for_survey": "A kérdőív becsült kitöltési idejének megjelenítése",
"display_number_of_responses_for_survey": "A kérdőív válaszai számának megjelenítése",
"display_type": "Megjelenítési típus",
"display_type": "Megjelenített típus",
"divide": "Osztás /",
"does_not_contain": "Nem tartalmazza",
"does_not_end_with": "Nem ezzel végződik",
@@ -1281,7 +1282,7 @@
"does_not_include_all_of": "Nem tartalmazza ezekből az összeset",
"does_not_include_one_of": "Nem tartalmazza ezek egyikét",
"does_not_start_with": "Nem ezzel kezdődik",
"dropdown": "Legördülő menü",
"dropdown": "Legördülő",
"duplicate_block": "Blokk kettőzése",
"duplicate_question": "Kérdés kettőzése",
"edit_link": "Hivatkozás szerkesztése",
@@ -1545,7 +1546,7 @@
"send_survey_to_audience_who_match": "Kérdőív küldése az erre illeszkedő közönségnek…",
"send_your_respondents_to_a_page_of_your_choice": "A válaszadók küldése a választási lehetőség oldalára.",
"set_the_global_placement_in_the_look_feel_settings": "A globális elhelyezés beállítása a megjelenítési beállításokban.",
"settings_saved_successfully": "A beállítások sikeresen elmentve.",
"settings_saved_successfully": "A beállítások sikeresen elmentve",
"seven_points": "7 pont",
"show_block_settings": "Blokkbeállítások megjelenítése",
"show_button": "Gomb megjelenítése",
@@ -1712,7 +1713,7 @@
"person_attributes": "A személy jellemzői a beküldés időpontjában",
"phone": "Telefon",
"respondent_skipped_questions": "A válaszadó kihagyta ezeket a kérdéseket.",
"response_deleted_successfully": "A válasz sikeresen törölve.",
"response_deleted_successfully": "A válasz sikeresen törölve",
"single_use_id": "Egyszer használatos azonosító",
"source": "Forrás",
"state_region": "Állam vagy régió",
@@ -1724,7 +1725,7 @@
"search_by_survey_name": "Keresés kérőívnév alapján",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Ha nem titkosítja az egyszer használatos azonosítót, akkor a „suid=…” bármilyen értéke működik egy válasznál.",
"custom_single_use_id_description": "Ha nem titkosítja az egyszer használatos azonosítókat, akkor a „suid=…” bármilyen értéke működik egy válasznál.",
"custom_single_use_id_title": "Bármilyen értéket beállíthat egyszer használatos azonosítóként az URL-ben.",
"custom_start_point": "Egyéni kezdési pont",
"data_prefilling": "Adatok előre kitöltése",
@@ -1949,8 +1950,8 @@
"your_survey_is_public": "A kérdőíve nyilvános",
"youre_not_plugged_in_yet": "Még nincs csatlakoztatva!"
},
"survey_deleted_successfully": "A kérdőív sikeresen törölve!",
"survey_duplicated_successfully": "A kérdőív sikeresen megkettőzve.",
"survey_deleted_successfully": "A kérdőív sikeresen törölve",
"survey_duplicated_successfully": "A kérdőív sikeresen megkettőzve",
"survey_duplication_error": "Nem sikerült megkettőzni a kérdőívet.",
"templates": {
"all_channels": "Összes csatorna",
@@ -2013,8 +2014,8 @@
"custom_scripts_updated_successfully": "Az egyéni parancsfájlok sikeres frissítve",
"custom_scripts_warning": "A parancsfájlok teljes böngésző-hozzáféréssel kerülnek végrehajtásra. Csak megbízható forrásokból származó parancsfájlokat adjon hozzá.",
"delete_workspace": "Munkaterület törlése",
"delete_workspace_confirmation": "Biztosan törölni szeretné a(z) {projectName} projektet? Ezt a műveletet nem lehet visszavonni.",
"delete_workspace_name_includes_surveys_responses_people_and_more": "A(z) {projectName} projekt törlése, beleértve az összes kérdőívet, választ, személyt, műveletet és attribútumot is.",
"delete_workspace_confirmation": "Biztosan törölni szeretné a(z) {projectName} munkaterületet? Ezt a műveletet nem lehet visszavonni.",
"delete_workspace_name_includes_surveys_responses_people_and_more": "A(z) {projectName} munkaterület törlése, beleértve az összes kérdőívet, választ, személyt, műveletet és attribútumot is.",
"delete_workspace_settings_description": "A munkaterület törlése az összes kérdőívvel, válasszal, személlyel, művelettel és attribútummal együtt. Ezt nem lehet visszavonni.",
"error_saving_workspace_information": "Hiba a munkaterület-információk mentésekor",
"only_owners_or_managers_can_delete_workspaces": "Csak tulajdonosok vagy kezelők törölhetnek munkaterületeket",
@@ -2097,9 +2098,9 @@
"search_tags": "Címkék keresése…",
"tag": "Címke",
"tag_already_exists": "A címke már létezik",
"tag_deleted": "Címke törölve",
"tag_updated": "Címke frissítve",
"tags_merged": "Címkék egyesítve"
"tag_deleted": "A címke sikeresen törölve",
"tag_updated": "A címke sikeresen frissítve",
"tags_merged": "A címkék sikeresen egyesítve"
},
"teams": {
"manage_teams": "Csapatok kezelése",
@@ -2294,7 +2295,7 @@
"career_development_survey_question_5_choice_5": "Üzemeltetés",
"career_development_survey_question_5_choice_6": "Egyéb",
"career_development_survey_question_5_headline": "Milyen funkcióban dolgozik?",
"career_development_survey_question_5_subheader": "Válassza a következők egyikét",
"career_development_survey_question_5_subheader": "Válassza ki a következő lehetőségek egyikét:",
"career_development_survey_question_6_choice_1": "Egyéni közreműködő",
"career_development_survey_question_6_choice_2": "Igazgató",
"career_development_survey_question_6_choice_3": "Vezető igazgató",
@@ -2302,7 +2303,7 @@
"career_development_survey_question_6_choice_5": "Igazgató",
"career_development_survey_question_6_choice_6": "Egyéb",
"career_development_survey_question_6_headline": "Az alábbiak közül melyik írja le legjobban a jelenlegi munkája szintjét?",
"career_development_survey_question_6_subheader": "Válassza a következők egyikét",
"career_development_survey_question_6_subheader": "Válassza ki a következő lehetőségek egyikét:",
"cess_survey_name": "Ügyfél-erőfeszítési pontszám kérdőív",
"cess_survey_question_1_headline": "A(z) $[projectName] megkönnyíti számomra a [CÉL HOZZÁADÁSA] tevékenységet",
"cess_survey_question_1_lower_label": "Egyáltalán nem értek egyet",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "トライアルライセンスをリクエスト",
"reset_to_default": "デフォルトにリセット",
"response": "回答",
"response_id": "回答ID",
"responses": "回答",
"restart": "再開",
"role": "役割",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Proeflicentie aanvragen",
"reset_to_default": "Resetten naar standaard",
"response": "Antwoord",
"response_id": "Antwoord-ID",
"responses": "Reacties",
"restart": "Opnieuw opstarten",
"role": "Rol",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Pedir licença de teste",
"reset_to_default": "Restaurar para o padrão",
"response": "Resposta",
"response_id": "ID da resposta",
"responses": "Respostas",
"restart": "Reiniciar",
"role": "Rolê",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Solicitar licença de teste",
"reset_to_default": "Repor para o padrão",
"response": "Resposta",
"response_id": "ID de resposta",
"responses": "Respostas",
"restart": "Reiniciar",
"role": "Função",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Solicitați o licență de încercare",
"reset_to_default": "Revino la implicit",
"response": "Răspuns",
"response_id": "ID răspuns",
"responses": "Răspunsuri",
"restart": "Repornește",
"role": "Rolul",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Запросить пробную лицензию",
"reset_to_default": "Сбросить по умолчанию",
"response": "Ответ",
"response_id": "ID ответа",
"responses": "Ответы",
"restart": "Перезапустить",
"role": "Роль",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "Begär provlicens",
"reset_to_default": "Återställ till standard",
"response": "Svar",
"response_id": "Svar-ID",
"responses": "Svar",
"restart": "Starta om",
"role": "Roll",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "申请试用许可证",
"reset_to_default": "重置为 默认",
"response": "响应",
"response_id": "响应 ID",
"responses": "反馈",
"restart": "重新启动",
"role": "角色",

View File

@@ -350,6 +350,7 @@
"request_trial_license": "請求試用授權",
"reset_to_default": "重設為預設值",
"response": "回應",
"response_id": "回應 ID",
"responses": "回應",
"restart": "重新開始",
"role": "角色",

View File

@@ -1,7 +1,7 @@
"use client";
import { useMemo, useTransition } from "react";
import type { Dispatch, SetStateAction } from "react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import type { TI18nString } from "@formbricks/types/i18n";
import type { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
@@ -74,6 +74,8 @@ export function LocalizedEditor({
[id, isInvalid, localSurvey.languages, value]
);
const [, startTransition] = useTransition();
return (
<div className="relative w-full">
<Editor
@@ -109,44 +111,45 @@ export function LocalizedEditor({
sanitizedContent = v.replaceAll(/<a[^>]*>(.*?)<\/a>/gi, "$1");
}
// Check if the elements still exists before updating
const currentElement = elements[elementIdx];
// if this is a card, we wanna check if the card exists in the localSurvey
if (isCard) {
const isWelcomeCard = elementIdx === -1;
const isEndingCard = elementIdx >= elements.length;
startTransition(() => {
// if this is a card, we wanna check if the card exists in the localSurvey
if (isCard) {
const isWelcomeCard = elementIdx === -1;
const isEndingCard = elementIdx >= elements.length;
// For ending cards, check if the field exists before updating
if (isEndingCard) {
const ending = localSurvey.endings.find((ending) => ending.id === elementId);
// If the field doesn't exist on the ending card, don't create it
if (!ending || ending[id] === undefined) {
// For ending cards, check if the field exists before updating
if (isEndingCard) {
const ending = localSurvey.endings.find((ending) => ending.id === elementId);
// If the field doesn't exist on the ending card, don't create it
if (!ending || ending[id] === undefined) {
return;
}
}
// For welcome cards, check if it exists
if (isWelcomeCard && !localSurvey.welcomeCard) {
return;
}
}
// For welcome cards, check if it exists
if (isWelcomeCard && !localSurvey.welcomeCard) {
const translatedContent = {
...value,
[selectedLanguageCode]: sanitizedContent,
};
updateElement({ [id]: translatedContent });
return;
}
const translatedContent = {
...value,
[selectedLanguageCode]: sanitizedContent,
};
updateElement({ [id]: translatedContent });
return;
}
// Check if the field exists on the element (not just if it's not undefined)
if (currentElement && id in currentElement && currentElement[id] !== undefined) {
const translatedContent = {
...value,
[selectedLanguageCode]: sanitizedContent,
};
updateElement(elementIdx, { [id]: translatedContent });
}
// Check if the field exists on the element (not just if it's not undefined)
if (currentElement && id in currentElement && currentElement[id] !== undefined) {
const translatedContent = {
...value,
[selectedLanguageCode]: sanitizedContent,
};
updateElement(elementIdx, { [id]: translatedContent });
}
});
}}
localSurvey={localSurvey}
elementId={elementId}

View File

@@ -54,7 +54,7 @@ import {
} from "@/modules/survey/editor/lib/utils";
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
import { isEndingCardValid, isWelcomeCardValid, validateSurveyElementsInBatch } from "../lib/validation";
import { isEndingCardValid, isWelcomeCardValid, validateElement } from "../lib/validation";
interface ElementsViewProps {
localSurvey: TSurvey;
@@ -211,35 +211,6 @@ export const ElementsView = ({
};
};
useEffect(() => {
if (!invalidElements) return;
let updatedInvalidElements: string[] = [...invalidElements];
// Check welcome card
if (localSurvey.welcomeCard.enabled && !isWelcomeCardValid(localSurvey.welcomeCard, surveyLanguages)) {
if (!updatedInvalidElements.includes("start")) {
updatedInvalidElements = [...updatedInvalidElements, "start"];
}
} else {
updatedInvalidElements = updatedInvalidElements.filter((elementId) => elementId !== "start");
}
// Check thank you card
localSurvey.endings.forEach((ending) => {
if (!isEndingCardValid(ending, surveyLanguages)) {
if (!updatedInvalidElements.includes(ending.id)) {
updatedInvalidElements = [...updatedInvalidElements, ending.id];
}
} else {
updatedInvalidElements = updatedInvalidElements.filter((elementId) => elementId !== ending.id);
}
});
if (JSON.stringify(updatedInvalidElements) !== JSON.stringify(invalidElements)) {
setInvalidElements(updatedInvalidElements);
}
}, [localSurvey.welcomeCard, localSurvey.endings, surveyLanguages, invalidElements, setInvalidElements]);
const updateElement = (elementIdx: number, updatedAttributes: any) => {
// Get element ID from current elements array (for validation)
const element = elements[elementIdx];
@@ -250,7 +221,6 @@ export const ElementsView = ({
// Track side effects that need to happen after state update
let newActiveElementId: string | null = null;
let invalidElementsUpdate: string[] | null = null;
// Use functional update to ensure we work with the latest state
setLocalSurvey((prevSurvey) => {
@@ -296,13 +266,6 @@ export const ElementsView = ({
const initialElementId = elementId;
updatedSurvey = handleElementLogicChange(updatedSurvey, initialElementId, elementLevelAttributes.id);
// Track side effects to apply after state update
if (invalidElements?.includes(initialElementId)) {
invalidElementsUpdate = invalidElements.map((id) =>
id === initialElementId ? elementLevelAttributes.id : id
);
}
// Track new active element ID
newActiveElementId = elementLevelAttributes.id;
@@ -344,9 +307,6 @@ export const ElementsView = ({
});
// Apply side effects after state update is queued
if (invalidElementsUpdate) {
setInvalidElements(invalidElementsUpdate);
}
if (newActiveElementId) {
setActiveElementId(newActiveElementId);
}
@@ -764,23 +724,67 @@ export const ElementsView = ({
setLocalSurvey(result.data);
};
//useEffect to validate survey when changes are made to languages
useEffect(() => {
if (!invalidElements) return;
let updatedInvalidElements: string[] = invalidElements;
// Validate each element
elements.forEach((element) => {
updatedInvalidElements = validateSurveyElementsInBatch(
element,
updatedInvalidElements,
surveyLanguages
);
});
// Validate survey when changes are made to languages or elements
// using set for O(1) lookup
useEffect(
() => {
if (!invalidElements) return;
if (JSON.stringify(updatedInvalidElements) !== JSON.stringify(invalidElements)) {
setInvalidElements(updatedInvalidElements);
}
}, [elements, surveyLanguages, invalidElements, setInvalidElements]);
const currentInvalidSet = new Set(invalidElements);
let hasChanges = false;
// Validate each element
elements.forEach((element) => {
const isValid = validateElement(element, surveyLanguages);
if (isValid) {
if (currentInvalidSet.has(element.id)) {
currentInvalidSet.delete(element.id);
hasChanges = true;
}
} else if (!currentInvalidSet.has(element.id)) {
currentInvalidSet.add(element.id);
hasChanges = true;
}
});
// Check welcome card
if (localSurvey.welcomeCard.enabled && !isWelcomeCardValid(localSurvey.welcomeCard, surveyLanguages)) {
if (!currentInvalidSet.has("start")) {
currentInvalidSet.add("start");
hasChanges = true;
}
} else if (currentInvalidSet.has("start")) {
currentInvalidSet.delete("start");
hasChanges = true;
}
// Check thank you card
localSurvey.endings.forEach((ending) => {
if (!isEndingCardValid(ending, surveyLanguages)) {
if (!currentInvalidSet.has(ending.id)) {
currentInvalidSet.add(ending.id);
hasChanges = true;
}
} else if (currentInvalidSet.has(ending.id)) {
currentInvalidSet.delete(ending.id);
hasChanges = true;
}
});
if (hasChanges) {
setInvalidElements(Array.from(currentInvalidSet));
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
elements,
surveyLanguages,
invalidElements,
setInvalidElements,
localSurvey.welcomeCard,
localSurvey.endings,
]
);
useEffect(() => {
const elementWithEmptyFallback = checkForEmptyFallBackValue(localSurvey, selectedLanguageCode);
@@ -791,7 +795,7 @@ export const ElementsView = ({
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeElementId, setActiveElementId]);
}, [activeElementId, setActiveElementId, localSurvey, selectedLanguageCode]);
const sensors = useSensors(
useSensor(PointerSensor, {

View File

@@ -86,6 +86,7 @@ export const SurveyEditor = ({
const [activeElementId, setActiveElementId] = useState<string | null>(null);
const [localSurvey, setLocalSurvey] = useState<TSurvey | null>(() => structuredClone(survey));
const [invalidElements, setInvalidElements] = useState<string[] | null>(null);
const [selectedLanguageCode, setSelectedLanguageCode] = useState<string>("default");
const surveyEditorRef = useRef(null);
const [localProject, setLocalProject] = useState<Project>(project);

View File

@@ -83,37 +83,17 @@ export const addPageUrlEventListeners = (): void => {
// Monkey patch history methods if not already done
if (!isHistoryPatched) {
try {
// eslint-disable-next-line @typescript-eslint/unbound-method -- We need to access the original method
const originalPushState = history.pushState;
// eslint-disable-next-line @typescript-eslint/unbound-method -- We need to access the original method
const originalReplaceState = history.replaceState;
// eslint-disable-next-line @typescript-eslint/unbound-method -- We need to access the original method
const originalPushState = history.pushState;
// Use Object.defineProperty to override read-only properties
Object.defineProperty(history, "pushState", {
value: function (...args: Parameters<typeof originalPushState>) {
originalPushState.apply(this, args);
const event = new Event("pushstate");
window.dispatchEvent(event);
},
writable: true,
configurable: true,
});
// eslint-disable-next-line func-names -- We need an anonymous function here
history.pushState = function (...args) {
originalPushState.apply(this, args);
const event = new Event("pushstate");
window.dispatchEvent(event);
};
Object.defineProperty(history, "replaceState", {
value: function (...args: Parameters<typeof originalReplaceState>) {
originalReplaceState.apply(this, args);
const event = new Event("replacestate");
window.dispatchEvent(event);
},
writable: true,
configurable: true,
});
isHistoryPatched = true;
} catch (error) {
console.error("🧱 Formbricks - Failed to patch history methods:", error);
}
isHistoryPatched = true;
}
events.forEach((event) => {

View File

@@ -21,8 +21,8 @@
"respondents_will_not_see_this_card": "A válaszadók nem fogják látni ezt a kártyát",
"retry": "Újrapróbálkozás",
"retrying": "Újrapróbálkozás…",
"select_option": "Válassz egy lehetőséget",
"select_options": "Válassz lehetőségeket",
"select_option": "Lehetőség kiválasztása",
"select_options": "Lehetőségek kiválasztása",
"sending_responses": "Válaszok küldése…",
"takes_less_than_x_minutes": "{count, plural, one {Kevesebb mint 1 percet vesz igénybe} other {Kevesebb mint {count} percet vesz igénybe}}",
"takes_x_minutes": "{count, plural, one {1 percet vesz igénybe} other {{count} percet vesz igénybe}}",
@@ -48,7 +48,7 @@
},
"invalid_device_error": {
"message": "Tiltsa le a szemét elleni védekezést a kérdőív beállításaiban, hogy tovább használhassa ezt az eszközt.",
"title": "Ez az eszköz nem támogatja a spam elleni védelmet."
"title": "Ez az eszköz nem támogatja a szemét elleni védekezést."
},
"invalid_format": "Adjon meg egy érvényes formátumot",
"is_between": "Válasszon egy dátumot {startDate} és {endDate} között",
@@ -71,7 +71,7 @@
"please_fill_out_this_field": "Töltse ki ezt a mezőt",
"recaptcha_error": {
"message": "A válaszát nem sikerült elküldeni, mert automatizált tevékenységként lett megjelölve. Ha lélegzik, akkor próbálja meg újra.",
"title": "Nem tudtuk ellenőrizni, hogy ember vagy."
"title": "Nem tudtuk ellenőrizni, hogy Ön ember-e."
},
"value_must_contain": "Az értéknek tartalmaznia kell ezt: {value}",
"value_must_equal": "Az értéknek egyenlőnek kell lennie ezzel: {value}",