mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 18:00:26 -06:00
Compare commits
3 Commits
v3.17.0-rc
...
fix/github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe15f6b7bd | ||
|
|
1091b40bd1 | ||
|
|
87a2d727ed |
@@ -29,7 +29,7 @@ import {
|
|||||||
SquareStack,
|
SquareStack,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { TSegment } from "@formbricks/types/segment";
|
import { TSegment } from "@formbricks/types/segment";
|
||||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||||
import { TUser } from "@formbricks/types/user";
|
import { TUser } from "@formbricks/types/user";
|
||||||
@@ -77,6 +77,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: string;
|
description: string;
|
||||||
componentType: React.ComponentType<unknown>;
|
componentType: React.ComponentType<unknown>;
|
||||||
componentProps: unknown;
|
componentProps: unknown;
|
||||||
|
disabled?: boolean;
|
||||||
}[] = useMemo(
|
}[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -111,6 +112,7 @@ export const ShareSurveyModal = ({
|
|||||||
isContactsEnabled,
|
isContactsEnabled,
|
||||||
isFormbricksCloud,
|
isFormbricksCloud,
|
||||||
},
|
},
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.WEBSITE_EMBED,
|
id: ShareViaType.WEBSITE_EMBED,
|
||||||
@@ -121,6 +123,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.embed_on_website.description"),
|
description: t("environments.surveys.share.embed_on_website.description"),
|
||||||
componentType: WebsiteEmbedTab,
|
componentType: WebsiteEmbedTab,
|
||||||
componentProps: { surveyUrl },
|
componentProps: { surveyUrl },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.EMAIL,
|
id: ShareViaType.EMAIL,
|
||||||
@@ -131,6 +134,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.send_email.description"),
|
description: t("environments.surveys.share.send_email.description"),
|
||||||
componentType: EmailTab,
|
componentType: EmailTab,
|
||||||
componentProps: { surveyId: survey.id, email },
|
componentProps: { surveyId: survey.id, email },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.SOCIAL_MEDIA,
|
id: ShareViaType.SOCIAL_MEDIA,
|
||||||
@@ -141,6 +145,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.social_media.description"),
|
description: t("environments.surveys.share.social_media.description"),
|
||||||
componentType: SocialMediaTab,
|
componentType: SocialMediaTab,
|
||||||
componentProps: { surveyUrl, surveyTitle: survey.name },
|
componentProps: { surveyUrl, surveyTitle: survey.name },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.QR_CODE,
|
id: ShareViaType.QR_CODE,
|
||||||
@@ -151,6 +156,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.summary.qr_code_description"),
|
description: t("environments.surveys.summary.qr_code_description"),
|
||||||
componentType: QRCodeTab,
|
componentType: QRCodeTab,
|
||||||
componentProps: { surveyUrl },
|
componentProps: { surveyUrl },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.DYNAMIC_POPUP,
|
id: ShareViaType.DYNAMIC_POPUP,
|
||||||
@@ -177,9 +183,9 @@ export const ShareSurveyModal = ({
|
|||||||
t,
|
t,
|
||||||
survey,
|
survey,
|
||||||
publicDomain,
|
publicDomain,
|
||||||
setSurveyUrl,
|
|
||||||
user.locale,
|
user.locale,
|
||||||
surveyUrl,
|
surveyUrl,
|
||||||
|
isReadOnly,
|
||||||
environmentId,
|
environmentId,
|
||||||
segments,
|
segments,
|
||||||
isContactsEnabled,
|
isContactsEnabled,
|
||||||
@@ -188,9 +194,14 @@ export const ShareSurveyModal = ({
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [activeId, setActiveId] = useState<ShareViaType | ShareSettingsType>(
|
const getDefaultActiveId = useCallback(() => {
|
||||||
survey.type === "link" ? ShareViaType.ANON_LINKS : ShareViaType.APP
|
if (survey.type !== "link") {
|
||||||
);
|
return ShareViaType.APP;
|
||||||
|
}
|
||||||
|
return ShareViaType.ANON_LINKS;
|
||||||
|
}, [survey.type]);
|
||||||
|
|
||||||
|
const [activeId, setActiveId] = useState<ShareViaType | ShareSettingsType>(getDefaultActiveId());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -198,11 +209,19 @@ export const ShareSurveyModal = ({
|
|||||||
}
|
}
|
||||||
}, [open, modalView]);
|
}, [open, modalView]);
|
||||||
|
|
||||||
|
// Ensure active tab is not disabled - if it is, switch to default
|
||||||
|
useEffect(() => {
|
||||||
|
const activeTab = linkTabs.find((tab) => tab.id === activeId);
|
||||||
|
if (activeTab?.disabled) {
|
||||||
|
setActiveId(getDefaultActiveId());
|
||||||
|
}
|
||||||
|
}, [activeId, linkTabs, getDefaultActiveId]);
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setShowView("start");
|
setShowView("start");
|
||||||
setActiveId(ShareViaType.ANON_LINKS);
|
setActiveId(getDefaultActiveId());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface ShareViewProps {
|
|||||||
componentProps: any;
|
componentProps: any;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
activeId: ShareViaType | ShareSettingsType;
|
activeId: ShareViaType | ShareSettingsType;
|
||||||
setActiveId: React.Dispatch<React.SetStateAction<ShareViaType | ShareSettingsType>>;
|
setActiveId: React.Dispatch<React.SetStateAction<ShareViaType | ShareSettingsType>>;
|
||||||
@@ -109,12 +110,13 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
|
|||||||
onClick={() => setActiveId(tab.id)}
|
onClick={() => setActiveId(tab.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
"flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||||
tab.id === activeId
|
tab.id === activeId && !tab.disabled
|
||||||
? "bg-slate-100 font-medium text-slate-900"
|
? "bg-slate-100 font-medium text-slate-900"
|
||||||
: "text-slate-700"
|
: "text-slate-700"
|
||||||
)}
|
)}
|
||||||
tooltip={tab.label}
|
tooltip={tab.label}
|
||||||
isActive={tab.id === activeId}>
|
isActive={tab.id === activeId}
|
||||||
|
disabled={tab.disabled}>
|
||||||
<tab.icon className="h-4 w-4 text-slate-700" />
|
<tab.icon className="h-4 w-4 text-slate-700" />
|
||||||
<span>{tab.label}</span>
|
<span>{tab.label}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -136,9 +138,10 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setActiveId(tab.id)}
|
onClick={() => setActiveId(tab.id)}
|
||||||
|
disabled={tab.disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-md px-4 py-2",
|
"rounded-md px-4 py-2",
|
||||||
tab.id === activeId
|
tab.id === activeId && !tab.disabled
|
||||||
? "bg-white text-slate-900 shadow-sm hover:bg-white"
|
? "bg-white text-slate-900 shadow-sm hover:bg-white"
|
||||||
: "border-transparent text-slate-700 hover:text-slate-900"
|
: "border-transparent text-slate-700 hover:text-slate-900"
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@@ -89,94 +89,4 @@ describe("QuestionFilterComboBox", () => {
|
|||||||
await userEvent.click(comboBoxOpenerButton);
|
await userEvent.click(comboBoxOpenerButton);
|
||||||
expect(screen.queryByText("X")).not.toBeInTheDocument();
|
expect(screen.queryByText("X")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows text input for URL meta field", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: "Contains",
|
|
||||||
filterComboBoxValue: "example.com",
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
const textInput = screen.getByDisplayValue("example.com");
|
|
||||||
expect(textInput).toBeInTheDocument();
|
|
||||||
expect(textInput).toHaveAttribute("type", "text");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("text input is disabled when no filter value is selected for URL field", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: undefined,
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
const textInput = screen.getByRole("textbox");
|
|
||||||
expect(textInput).toBeDisabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("text input calls onChangeFilterComboBoxValue when typing for URL field", async () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: "Contains",
|
|
||||||
filterComboBoxValue: "",
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
const textInput = screen.getByRole("textbox");
|
|
||||||
await userEvent.type(textInput, "t");
|
|
||||||
expect(props.onChangeFilterComboBoxValue).toHaveBeenCalledWith("t");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("shows regular combobox for non-URL meta fields", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "source",
|
|
||||||
filterValue: "Equals",
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
|
|
||||||
expect(screen.getAllByRole("button").length).toBeGreaterThanOrEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("shows regular combobox for URL field with non-text operations", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Other",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: "Equals",
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
|
|
||||||
expect(screen.getAllByRole("button").length).toBeGreaterThanOrEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("text input handles string filter combo box values correctly", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: "Contains",
|
|
||||||
filterComboBoxValue: "test-url",
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
const textInput = screen.getByDisplayValue("test-url");
|
|
||||||
expect(textInput).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("text input handles non-string filter combo box values gracefully", () => {
|
|
||||||
const props = {
|
|
||||||
...defaultProps,
|
|
||||||
type: "Meta",
|
|
||||||
fieldId: "url",
|
|
||||||
filterValue: "Contains",
|
|
||||||
filterComboBoxValue: ["array-value"],
|
|
||||||
} as any;
|
|
||||||
render(<QuestionFilterComboBox {...props} />);
|
|
||||||
const textInput = screen.getByRole("textbox");
|
|
||||||
expect(textInput).toHaveValue("");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ type QuestionFilterComboBoxProps = {
|
|||||||
type?: TSurveyQuestionTypeEnum | Omit<OptionsType, OptionsType.QUESTIONS>;
|
type?: TSurveyQuestionTypeEnum | Omit<OptionsType, OptionsType.QUESTIONS>;
|
||||||
handleRemoveMultiSelect: (value: string[]) => void;
|
handleRemoveMultiSelect: (value: string[]) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
fieldId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuestionFilterComboBox = ({
|
export const QuestionFilterComboBox = ({
|
||||||
@@ -46,7 +45,6 @@ export const QuestionFilterComboBox = ({
|
|||||||
type,
|
type,
|
||||||
handleRemoveMultiSelect,
|
handleRemoveMultiSelect,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
fieldId,
|
|
||||||
}: QuestionFilterComboBoxProps) => {
|
}: QuestionFilterComboBoxProps) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [openFilterValue, setOpenFilterValue] = React.useState<boolean>(false);
|
const [openFilterValue, setOpenFilterValue] = React.useState<boolean>(false);
|
||||||
@@ -77,9 +75,6 @@ export const QuestionFilterComboBox = ({
|
|||||||
(type === TSurveyQuestionTypeEnum.NPS || type === TSurveyQuestionTypeEnum.Rating) &&
|
(type === TSurveyQuestionTypeEnum.NPS || type === TSurveyQuestionTypeEnum.Rating) &&
|
||||||
(filterValue === "Submitted" || filterValue === "Skipped");
|
(filterValue === "Submitted" || filterValue === "Skipped");
|
||||||
|
|
||||||
// Check if this is a URL field with string comparison operations that require text input
|
|
||||||
const isTextInputField = type === OptionsType.META && fieldId === "url";
|
|
||||||
|
|
||||||
const filteredOptions = options?.filter((o) =>
|
const filteredOptions = options?.filter((o) =>
|
||||||
(typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o)
|
(typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o)
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -166,80 +161,70 @@ export const QuestionFilterComboBox = ({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
{isTextInputField ? (
|
<Command ref={commandRef} className="h-10 overflow-visible bg-transparent">
|
||||||
<Input
|
<div
|
||||||
type="text"
|
className={clsx(
|
||||||
value={typeof filterComboBoxValue === "string" ? filterComboBoxValue : ""}
|
"group flex items-center justify-between rounded-md rounded-l-none bg-white px-3 py-2 text-sm"
|
||||||
onChange={(e) => onChangeFilterComboBoxValue(e.target.value)}
|
)}>
|
||||||
disabled={disabled || !filterValue}
|
{filterComboBoxValue && filterComboBoxValue.length > 0 ? (
|
||||||
className="h-9 rounded-l-none border-none bg-white text-sm focus:ring-offset-0"
|
filterComboBoxItem
|
||||||
/>
|
) : (
|
||||||
) : (
|
|
||||||
<Command ref={commandRef} className="h-10 overflow-visible bg-transparent">
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"group flex items-center justify-between rounded-md rounded-l-none bg-white px-3 py-2 text-sm"
|
|
||||||
)}>
|
|
||||||
{filterComboBoxValue && filterComboBoxValue.length > 0 ? (
|
|
||||||
filterComboBoxItem
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => !disabled && !isDisabledComboBox && filterValue && setOpen(true)}
|
|
||||||
disabled={disabled || isDisabledComboBox || !filterValue}
|
|
||||||
className={clsx(
|
|
||||||
"flex-1 text-left text-slate-400",
|
|
||||||
disabled || isDisabledComboBox || !filterValue ? "opacity-50" : "cursor-pointer"
|
|
||||||
)}>
|
|
||||||
{t("common.select")}...
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => !disabled && !isDisabledComboBox && filterValue && setOpen(true)}
|
onClick={() => !disabled && !isDisabledComboBox && filterValue && setOpen(true)}
|
||||||
disabled={disabled || isDisabledComboBox || !filterValue}
|
disabled={disabled || isDisabledComboBox || !filterValue}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"ml-2 flex items-center justify-center",
|
"flex-1 text-left text-slate-400",
|
||||||
disabled || isDisabledComboBox || !filterValue ? "opacity-50" : "cursor-pointer"
|
disabled || isDisabledComboBox || !filterValue ? "opacity-50" : "cursor-pointer"
|
||||||
)}>
|
)}>
|
||||||
{open ? (
|
{t("common.select")}...
|
||||||
<ChevronUp className="h-4 w-4 opacity-50" />
|
|
||||||
) : (
|
|
||||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
<div className="relative mt-2 h-full">
|
<button
|
||||||
{open && (
|
type="button"
|
||||||
<div className="animate-in absolute top-0 z-10 max-h-52 w-full overflow-auto rounded-md bg-white outline-none">
|
onClick={() => !disabled && !isDisabledComboBox && filterValue && setOpen(true)}
|
||||||
<CommandList>
|
disabled={disabled || isDisabledComboBox || !filterValue}
|
||||||
<div className="p-2">
|
className={clsx(
|
||||||
<Input
|
"ml-2 flex items-center justify-center",
|
||||||
type="text"
|
disabled || isDisabledComboBox || !filterValue ? "opacity-50" : "cursor-pointer"
|
||||||
autoFocus
|
)}>
|
||||||
placeholder={t("common.search") + "..."}
|
{open ? (
|
||||||
value={searchQuery}
|
<ChevronUp className="h-4 w-4 opacity-50" />
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
) : (
|
||||||
className="w-full rounded-md border border-slate-300 p-2 text-sm focus:border-slate-300"
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CommandEmpty>{t("common.no_result_found")}</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{filteredOptions?.map((o, index) => (
|
|
||||||
<CommandItem
|
|
||||||
key={`option-${typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o}-${index}`}
|
|
||||||
onSelect={() => commandItemOnSelect(o)}
|
|
||||||
className="cursor-pointer">
|
|
||||||
{typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</button>
|
||||||
</Command>
|
</div>
|
||||||
)}
|
<div className="relative mt-2 h-full">
|
||||||
|
{open && (
|
||||||
|
<div className="animate-in absolute top-0 z-10 max-h-52 w-full overflow-auto rounded-md bg-white outline-none">
|
||||||
|
<CommandList>
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
autoFocus
|
||||||
|
placeholder={t("common.search") + "..."}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-slate-300 p-2 text-sm focus:border-slate-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CommandEmpty>{t("common.no_result_found")}</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{filteredOptions?.map((o, index) => (
|
||||||
|
<CommandItem
|
||||||
|
key={`option-${typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o}-${index}`}
|
||||||
|
onSelect={() => commandItemOnSelect(o)}
|
||||||
|
className="cursor-pointer">
|
||||||
|
{typeof o === "object" ? getLocalizedValue(o, defaultLanguageCode) : o}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Command>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
HomeIcon,
|
HomeIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
LanguagesIcon,
|
LanguagesIcon,
|
||||||
LinkIcon,
|
|
||||||
ListIcon,
|
ListIcon,
|
||||||
ListOrderedIcon,
|
ListOrderedIcon,
|
||||||
MessageSquareTextIcon,
|
MessageSquareTextIcon,
|
||||||
@@ -95,7 +94,6 @@ const questionIcons = {
|
|||||||
source: ArrowUpFromDotIcon,
|
source: ArrowUpFromDotIcon,
|
||||||
action: MousePointerClickIcon,
|
action: MousePointerClickIcon,
|
||||||
country: FlagIcon,
|
country: FlagIcon,
|
||||||
url: LinkIcon,
|
|
||||||
|
|
||||||
// others
|
// others
|
||||||
Language: LanguagesIcon,
|
Language: LanguagesIcon,
|
||||||
@@ -140,7 +138,7 @@ export const SelectedCommandItem = ({ label, questionType, type }: Partial<Quest
|
|||||||
|
|
||||||
const getLabelStyle = (): string | undefined => {
|
const getLabelStyle = (): string | undefined => {
|
||||||
if (type !== OptionsType.META) return undefined;
|
if (type !== OptionsType.META) return undefined;
|
||||||
return label === "os" || label === "url" ? "uppercase" : "capitalize";
|
return label === "os" ? "uppercase" : "capitalize";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -246,9 +246,9 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
|
|||||||
<div className="flex w-full flex-wrap gap-3 md:flex-nowrap">
|
<div className="flex w-full flex-wrap gap-3 md:flex-nowrap">
|
||||||
<div
|
<div
|
||||||
className="grid w-full grid-cols-1 items-center gap-3 md:grid-cols-2"
|
className="grid w-full grid-cols-1 items-center gap-3 md:grid-cols-2"
|
||||||
key={`${s.questionType.id}-${i}-${s.questionType.label}`}>
|
key={`${s.questionType.id}-${i}`}>
|
||||||
<QuestionsComboBox
|
<QuestionsComboBox
|
||||||
key={`${s.questionType.label}-${i}-${s.questionType.id}`}
|
key={`${s.questionType.label}-${i}`}
|
||||||
options={questionComboBoxOptions}
|
options={questionComboBoxOptions}
|
||||||
selected={s.questionType}
|
selected={s.questionType}
|
||||||
onChangeValue={(value) => handleOnChangeQuestionComboBoxValue(value, i)}
|
onChangeValue={(value) => handleOnChangeQuestionComboBoxValue(value, i)}
|
||||||
@@ -276,7 +276,6 @@ export const ResponseFilter = ({ survey }: ResponseFilterProps) => {
|
|||||||
? s?.questionType?.questionType
|
? s?.questionType?.questionType
|
||||||
: s?.questionType?.type
|
: s?.questionType?.type
|
||||||
}
|
}
|
||||||
fieldId={s?.questionType?.id}
|
|
||||||
handleRemoveMultiSelect={(value) => handleRemoveMultiSelect(value, i)}
|
handleRemoveMultiSelect={(value) => handleRemoveMultiSelect(value, i)}
|
||||||
onChangeFilterComboBoxValue={(value) => handleOnChangeFilterComboBoxValue(value, i)}
|
onChangeFilterComboBoxValue={(value) => handleOnChangeFilterComboBoxValue(value, i)}
|
||||||
onChangeFilterValue={(value) => handleOnChangeFilterValue(value, i)}
|
onChangeFilterValue={(value) => handleOnChangeFilterValue(value, i)}
|
||||||
|
|||||||
@@ -231,43 +231,6 @@ describe("surveys", () => {
|
|||||||
expect(result.questionFilterOptions.some((o) => o.id === "q7")).toBeTruthy();
|
expect(result.questionFilterOptions.some((o) => o.id === "q7")).toBeTruthy();
|
||||||
expect(result.questionFilterOptions.some((o) => o.id === "q8")).toBeTruthy();
|
expect(result.questionFilterOptions.some((o) => o.id === "q8")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should provide extended filter options for URL meta field", () => {
|
|
||||||
const survey = {
|
|
||||||
id: "survey1",
|
|
||||||
name: "Test Survey",
|
|
||||||
questions: [],
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
environmentId: "env1",
|
|
||||||
status: "draft",
|
|
||||||
} as unknown as TSurvey;
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
url: ["https://example.com", "https://test.com"],
|
|
||||||
source: ["web", "mobile"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = generateQuestionAndFilterOptions(survey, undefined, {}, meta, {});
|
|
||||||
|
|
||||||
const urlFilterOption = result.questionFilterOptions.find((o) => o.id === "url");
|
|
||||||
const sourceFilterOption = result.questionFilterOptions.find((o) => o.id === "source");
|
|
||||||
|
|
||||||
expect(urlFilterOption).toBeDefined();
|
|
||||||
expect(urlFilterOption?.filterOptions).toEqual([
|
|
||||||
"Equals",
|
|
||||||
"Not equals",
|
|
||||||
"Contains",
|
|
||||||
"Does not contain",
|
|
||||||
"Starts with",
|
|
||||||
"Does not start with",
|
|
||||||
"Ends with",
|
|
||||||
"Does not end with",
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(sourceFilterOption).toBeDefined();
|
|
||||||
expect(sourceFilterOption?.filterOptions).toEqual(["Equals", "Not equals"]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFormattedFilters", () => {
|
describe("getFormattedFilters", () => {
|
||||||
@@ -754,119 +717,6 @@ describe("surveys", () => {
|
|||||||
expect(result.data?.npsQ).toEqual({ op: "greaterThan", value: 7 });
|
expect(result.data?.npsQ).toEqual({ op: "greaterThan", value: 7 });
|
||||||
expect(result.tags?.applied).toContain("Tag 1");
|
expect(result.tags?.applied).toContain("Tag 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should format URL meta filters with string operations", () => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "url", id: "url" },
|
|
||||||
filterType: { filterValue: "Contains", filterComboBoxValue: "example.com" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
|
|
||||||
expect(result.meta?.url).toEqual({ op: "contains", value: "example.com" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should format URL meta filters with all supported string operations", () => {
|
|
||||||
const testCases = [
|
|
||||||
{ filterValue: "Equals", expected: { op: "equals", value: "https://example.com" } },
|
|
||||||
{ filterValue: "Not equals", expected: { op: "notEquals", value: "https://example.com" } },
|
|
||||||
{ filterValue: "Contains", expected: { op: "contains", value: "example.com" } },
|
|
||||||
{ filterValue: "Does not contain", expected: { op: "doesNotContain", value: "test.com" } },
|
|
||||||
{ filterValue: "Starts with", expected: { op: "startsWith", value: "https://" } },
|
|
||||||
{ filterValue: "Does not start with", expected: { op: "doesNotStartWith", value: "http://" } },
|
|
||||||
{ filterValue: "Ends with", expected: { op: "endsWith", value: ".com" } },
|
|
||||||
{ filterValue: "Does not end with", expected: { op: "doesNotEndWith", value: ".org" } },
|
|
||||||
];
|
|
||||||
|
|
||||||
testCases.forEach(({ filterValue, expected }) => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "url", id: "url" },
|
|
||||||
filterType: { filterValue, filterComboBoxValue: expected.value },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
expect(result.meta?.url).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle URL meta filters with empty string values", () => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "url", id: "url" },
|
|
||||||
filterType: { filterValue: "Contains", filterComboBoxValue: "" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
|
|
||||||
expect(result.meta?.url).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle URL meta filters with whitespace-only values", () => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "url", id: "url" },
|
|
||||||
filterType: { filterValue: "Contains", filterComboBoxValue: " " },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
|
|
||||||
expect(result.meta?.url).toEqual({ op: "contains", value: "" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should still handle existing meta filters with array values", () => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "source", id: "source" },
|
|
||||||
filterType: { filterValue: "Equals", filterComboBoxValue: ["google"] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
|
|
||||||
expect(result.meta?.source).toEqual({ op: "equals", value: "google" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle mixed URL and traditional meta filters", () => {
|
|
||||||
const selectedFilter = {
|
|
||||||
responseStatus: "all",
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "url", id: "url" },
|
|
||||||
filterType: { filterValue: "Contains", filterComboBoxValue: "formbricks.com" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
questionType: { type: "Meta", label: "source", id: "source" },
|
|
||||||
filterType: { filterValue: "Equals", filterComboBoxValue: ["newsletter"] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const result = getFormattedFilters(survey, selectedFilter, dateRange);
|
|
||||||
|
|
||||||
expect(result.meta?.url).toEqual({ op: "contains", value: "formbricks.com" });
|
|
||||||
expect(result.meta?.source).toEqual({ op: "equals", value: "newsletter" });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getTodayDate", () => {
|
describe("getTodayDate", () => {
|
||||||
|
|||||||
@@ -47,18 +47,6 @@ const filterOptions = {
|
|||||||
ranking: ["Filled out", "Skipped"],
|
ranking: ["Filled out", "Skipped"],
|
||||||
};
|
};
|
||||||
|
|
||||||
// URL/meta text operators mapping
|
|
||||||
const META_OP_MAP = {
|
|
||||||
Equals: "equals",
|
|
||||||
"Not equals": "notEquals",
|
|
||||||
Contains: "contains",
|
|
||||||
"Does not contain": "doesNotContain",
|
|
||||||
"Starts with": "startsWith",
|
|
||||||
"Does not start with": "doesNotStartWith",
|
|
||||||
"Ends with": "endsWith",
|
|
||||||
"Does not end with": "doesNotEndWith",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// creating the options for the filtering to be selected there are 4 types questions, attributes, tags and metadata
|
// creating the options for the filtering to be selected there are 4 types questions, attributes, tags and metadata
|
||||||
export const generateQuestionAndFilterOptions = (
|
export const generateQuestionAndFilterOptions = (
|
||||||
survey: TSurvey,
|
survey: TSurvey,
|
||||||
@@ -177,7 +165,7 @@ export const generateQuestionAndFilterOptions = (
|
|||||||
Object.keys(meta).forEach((m) => {
|
Object.keys(meta).forEach((m) => {
|
||||||
questionFilterOptions.push({
|
questionFilterOptions.push({
|
||||||
type: "Meta",
|
type: "Meta",
|
||||||
filterOptions: m === "url" ? Object.keys(META_OP_MAP) : ["Equals", "Not equals"],
|
filterOptions: ["Equals", "Not equals"],
|
||||||
filterComboBoxOptions: meta[m],
|
filterComboBoxOptions: meta[m],
|
||||||
id: m,
|
id: m,
|
||||||
});
|
});
|
||||||
@@ -493,23 +481,17 @@ export const getFormattedFilters = (
|
|||||||
if (meta.length) {
|
if (meta.length) {
|
||||||
meta.forEach(({ filterType, questionType }) => {
|
meta.forEach(({ filterType, questionType }) => {
|
||||||
if (!filters.meta) filters.meta = {};
|
if (!filters.meta) filters.meta = {};
|
||||||
|
if (!filterType.filterComboBoxValue) return;
|
||||||
// For text input cases (URL filtering)
|
if (filterType.filterValue === "Equals") {
|
||||||
if (typeof filterType.filterComboBoxValue === "string" && filterType.filterComboBoxValue.length > 0) {
|
filters.meta[questionType.label ?? ""] = {
|
||||||
const value = filterType.filterComboBoxValue.trim();
|
op: "equals",
|
||||||
const op = META_OP_MAP[filterType.filterValue as keyof typeof META_OP_MAP];
|
value: filterType.filterComboBoxValue as string,
|
||||||
if (op) {
|
};
|
||||||
filters.meta[questionType.label ?? ""] = { op, value };
|
} else if (filterType.filterValue === "Not equals") {
|
||||||
}
|
filters.meta[questionType.label ?? ""] = {
|
||||||
}
|
op: "notEquals",
|
||||||
// For dropdown/select cases (existing metadata fields)
|
value: filterType.filterComboBoxValue as string,
|
||||||
else if (Array.isArray(filterType.filterComboBoxValue) && filterType.filterComboBoxValue.length > 0) {
|
};
|
||||||
const value = filterType.filterComboBoxValue[0]; // Take first selected value
|
|
||||||
if (filterType.filterValue === "Equals") {
|
|
||||||
filters.meta[questionType.label ?? ""] = { op: "equals", value };
|
|
||||||
} else if (filterType.filterValue === "Not equals") {
|
|
||||||
filters.meta[questionType.label ?? ""] = { op: "notEquals", value };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,46 +157,6 @@ describe("Response Utils", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("meta: URL string comparison operations", () => {
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
name: "contains",
|
|
||||||
criteria: { meta: { url: { op: "contains" as const, value: "example.com" } } },
|
|
||||||
expected: { meta: { path: ["url"], string_contains: "example.com" } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "doesNotContain",
|
|
||||||
criteria: { meta: { url: { op: "doesNotContain" as const, value: "test.com" } } },
|
|
||||||
expected: { NOT: { meta: { path: ["url"], string_contains: "test.com" } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "startsWith",
|
|
||||||
criteria: { meta: { url: { op: "startsWith" as const, value: "https://" } } },
|
|
||||||
expected: { meta: { path: ["url"], string_starts_with: "https://" } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "doesNotStartWith",
|
|
||||||
criteria: { meta: { url: { op: "doesNotStartWith" as const, value: "http://" } } },
|
|
||||||
expected: { NOT: { meta: { path: ["url"], string_starts_with: "http://" } } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "endsWith",
|
|
||||||
criteria: { meta: { url: { op: "endsWith" as const, value: ".com" } } },
|
|
||||||
expected: { meta: { path: ["url"], string_ends_with: ".com" } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "doesNotEndWith",
|
|
||||||
criteria: { meta: { url: { op: "doesNotEndWith" as const, value: ".org" } } },
|
|
||||||
expected: { NOT: { meta: { path: ["url"], string_ends_with: ".org" } } },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
testCases.forEach(({ criteria, expected }) => {
|
|
||||||
const result = buildWhereClause(baseSurvey as TSurvey, criteria);
|
|
||||||
expect(result.AND).toEqual([{ AND: [expected] }]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildWhereClause – data‐field filter operations", () => {
|
describe("buildWhereClause – data‐field filter operations", () => {
|
||||||
@@ -535,98 +495,10 @@ describe("Response Utils", () => {
|
|||||||
expect(result.os).toContain("MacOS");
|
expect(result.os).toContain("MacOS");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should extract URL data correctly", () => {
|
|
||||||
const responses = [
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
url: "https://example.com/page1",
|
|
||||||
source: "direct",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
url: "https://test.com/page2?param=value",
|
|
||||||
source: "google",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const result = getResponseMeta(responses as Pick<TResponse, "contactAttributes" | "data" | "meta">[]);
|
|
||||||
expect(result.url).toEqual([]);
|
|
||||||
expect(result.source).toContain("direct");
|
|
||||||
expect(result.source).toContain("google");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle mixed meta data with URLs", () => {
|
|
||||||
const responses = [
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
userAgent: { browser: "Chrome", device: "desktop" },
|
|
||||||
url: "https://formbricks.com/dashboard",
|
|
||||||
country: "US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
userAgent: { browser: "Safari", device: "mobile" },
|
|
||||||
url: "https://formbricks.com/surveys/123",
|
|
||||||
country: "UK",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const result = getResponseMeta(responses as Pick<TResponse, "contactAttributes" | "data" | "meta">[]);
|
|
||||||
expect(result.browser).toContain("Chrome");
|
|
||||||
expect(result.browser).toContain("Safari");
|
|
||||||
expect(result.device).toContain("desktop");
|
|
||||||
expect(result.device).toContain("mobile");
|
|
||||||
expect(result.url).toEqual([]);
|
|
||||||
expect(result.country).toContain("US");
|
|
||||||
expect(result.country).toContain("UK");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should handle empty responses", () => {
|
test("should handle empty responses", () => {
|
||||||
const result = getResponseMeta([]);
|
const result = getResponseMeta([]);
|
||||||
expect(result).toEqual({});
|
expect(result).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should ignore empty or null URL values", () => {
|
|
||||||
const responses = [
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
url: "",
|
|
||||||
source: "direct",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
url: null as any,
|
|
||||||
source: "newsletter",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contactAttributes: {},
|
|
||||||
data: {},
|
|
||||||
meta: {
|
|
||||||
url: "https://valid.com",
|
|
||||||
source: "google",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const result = getResponseMeta(responses as Pick<TResponse, "contactAttributes" | "data" | "meta">[]);
|
|
||||||
expect(result.url).toEqual([]);
|
|
||||||
expect(result.source).toEqual(expect.arrayContaining(["direct", "newsletter", "google"]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getResponseHiddenFields", () => {
|
describe("getResponseHiddenFields", () => {
|
||||||
|
|||||||
@@ -234,60 +234,6 @@ export const buildWhereClause = (survey: TSurvey, filterCriteria?: TResponseFilt
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "contains":
|
|
||||||
meta.push({
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_contains: val.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "doesNotContain":
|
|
||||||
meta.push({
|
|
||||||
NOT: {
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_contains: val.value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "startsWith":
|
|
||||||
meta.push({
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_starts_with: val.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "doesNotStartWith":
|
|
||||||
meta.push({
|
|
||||||
NOT: {
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_starts_with: val.value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "endsWith":
|
|
||||||
meta.push({
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_ends_with: val.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "doesNotEndWith":
|
|
||||||
meta.push({
|
|
||||||
NOT: {
|
|
||||||
meta: {
|
|
||||||
path: updatedKey,
|
|
||||||
string_ends_with: val.value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -780,13 +726,10 @@ export const getResponseMeta = (
|
|||||||
|
|
||||||
responses.forEach((response) => {
|
responses.forEach((response) => {
|
||||||
Object.entries(response.meta).forEach(([key, value]) => {
|
Object.entries(response.meta).forEach(([key, value]) => {
|
||||||
|
// skip url
|
||||||
|
if (key === "url") return;
|
||||||
|
|
||||||
// Handling nested objects (like userAgent)
|
// Handling nested objects (like userAgent)
|
||||||
if (key === "url") {
|
|
||||||
if (!meta[key]) {
|
|
||||||
meta[key] = new Set();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null) {
|
||||||
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
||||||
if (typeof nestedValue === "string" && nestedValue) {
|
if (typeof nestedValue === "string" && nestedValue) {
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Filter anwenden",
|
"apply_filters": "Filter anwenden",
|
||||||
"are_you_sure": "Bist Du sicher?",
|
"are_you_sure": "Bist Du sicher?",
|
||||||
"attributes": "Attribute",
|
"attributes": "Attribute",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"billing": "Abrechnung",
|
"billing": "Abrechnung",
|
||||||
"booked": "Gebucht",
|
"booked": "Gebucht",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Was passiert, wenn Du das Konto löschst",
|
"account_deletion_consequences_warning": "Was passiert, wenn Du das Konto löschst",
|
||||||
"avatar_update_failed": "Aktualisierung des Avatars fehlgeschlagen. Bitte versuche es erneut.",
|
|
||||||
"backup_code": "Backup-Code",
|
"backup_code": "Backup-Code",
|
||||||
"change_image": "Bild ändern",
|
|
||||||
"confirm_delete_account": "Lösche dein Konto mit all deinen persönlichen Informationen und Daten",
|
"confirm_delete_account": "Lösche dein Konto mit all deinen persönlichen Informationen und Daten",
|
||||||
"confirm_delete_my_account": "Konto löschen",
|
"confirm_delete_my_account": "Konto löschen",
|
||||||
"confirm_your_current_password_to_get_started": "Bestätige dein aktuelles Passwort, um loszulegen.",
|
"confirm_your_current_password_to_get_started": "Bestätige dein aktuelles Passwort, um loszulegen.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "Deine Anfrage zur Änderung der E-Mail wurde eingeleitet.",
|
"email_change_initiated": "Deine Anfrage zur Änderung der E-Mail wurde eingeleitet.",
|
||||||
"enable_two_factor_authentication": "Zwei-Faktor-Authentifizierung aktivieren",
|
"enable_two_factor_authentication": "Zwei-Faktor-Authentifizierung aktivieren",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Gib den Code aus deiner Authentifizierungs-App unten ein.",
|
"enter_the_code_from_your_authenticator_app_below": "Gib den Code aus deiner Authentifizierungs-App unten ein.",
|
||||||
"file_size_must_be_less_than_10mb": "Dateigröße muss weniger als 10MB sein.",
|
|
||||||
"invalid_file_type": "Ungültiger Dateityp. Nur JPEG-, PNG- und WEBP-Dateien sind erlaubt.",
|
|
||||||
"lost_access": "Zugriff verloren",
|
"lost_access": "Zugriff verloren",
|
||||||
"or_enter_the_following_code_manually": "Oder gib den folgenden Code manuell ein:",
|
"or_enter_the_following_code_manually": "Oder gib den folgenden Code manuell ein:",
|
||||||
"organization_identification": "Hilf deiner Organisation, Dich auf Formbricks zu identifizieren",
|
|
||||||
"organizations_delete_message": "Du bist der einzige Besitzer dieser Organisationen, also werden sie <b>auch gelöscht.</b>",
|
"organizations_delete_message": "Du bist der einzige Besitzer dieser Organisationen, also werden sie <b>auch gelöscht.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Dauerhafte Entfernung all deiner persönlichen Informationen und Daten",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Dauerhafte Entfernung all deiner persönlichen Informationen und Daten",
|
||||||
"personal_information": "Persönliche Informationen",
|
"personal_information": "Persönliche Informationen",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Bitte gib {email} in das folgende Feld ein, um die endgültige Löschung deines Kontos zu bestätigen:",
|
"please_enter_email_to_confirm_account_deletion": "Bitte gib {email} in das folgende Feld ein, um die endgültige Löschung deines Kontos zu bestätigen:",
|
||||||
"profile_updated_successfully": "Dein Profil wurde erfolgreich aktualisiert",
|
"profile_updated_successfully": "Dein Profil wurde erfolgreich aktualisiert",
|
||||||
"remove_image": "Bild entfernen",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Speichere die folgenden Backup-Codes an einem sicheren Ort.",
|
"save_the_following_backup_codes_in_a_safe_place": "Speichere die folgenden Backup-Codes an einem sicheren Ort.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Scanne den QR-Code unten mit deiner Authentifizierungs-App.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Scanne den QR-Code unten mit deiner Authentifizierungs-App.",
|
||||||
"security_description": "Verwalte dein Passwort und andere Sicherheitseinstellungen wie Zwei-Faktor-Authentifizierung (2FA).",
|
"security_description": "Verwalte dein Passwort und andere Sicherheitseinstellungen wie Zwei-Faktor-Authentifizierung (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Zwei-Faktor-Code",
|
"two_factor_code": "Zwei-Faktor-Code",
|
||||||
"unlock_two_factor_authentication": "Zwei-Faktor-Authentifizierung mit einem höheren Plan freischalten",
|
"unlock_two_factor_authentication": "Zwei-Faktor-Authentifizierung mit einem höheren Plan freischalten",
|
||||||
"update_personal_info": "Persönliche Daten aktualisieren",
|
"update_personal_info": "Persönliche Daten aktualisieren",
|
||||||
"upload_image": "Bild hochladen",
|
|
||||||
"warning_cannot_delete_account": "Du bist der einzige Besitzer dieser Organisation. Bitte übertrage das Eigentum zuerst an ein anderes Mitglied.",
|
"warning_cannot_delete_account": "Du bist der einzige Besitzer dieser Organisation. Bitte übertrage das Eigentum zuerst an ein anderes Mitglied.",
|
||||||
"warning_cannot_undo": "Das kann nicht rückgängig gemacht werden",
|
"warning_cannot_undo": "Das kann nicht rückgängig gemacht werden"
|
||||||
"you_must_select_a_file": "Du musst eine Datei auswählen."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Füge Mitglieder zum Team hinzu und bestimme ihre Rolle.",
|
"add_members_description": "Füge Mitglieder zum Team hinzu und bestimme ihre Rolle.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "Die Meta-Daten werden basierend auf dem `lang` Wert in der URL geladen.",
|
"language_help_text": "Die Meta-Daten werden basierend auf dem `lang` Wert in der URL geladen.",
|
||||||
"link_description": "Linkbeschreibung",
|
"link_description": "Linkbeschreibung",
|
||||||
"link_description_description": "Beschreibung mit 55-200 Zeichen funktionieren am besten.",
|
"link_description_description": "Beschreibung mit 55-200 Zeichen funktionieren am besten.",
|
||||||
"link_description_placeholder": "Hilf uns, indem du deine Gedanken teilst.",
|
|
||||||
"link_title": "Linktitel",
|
"link_title": "Linktitel",
|
||||||
"link_title_description": "Kurze Titel funktionieren am besten als Meta-Titel.",
|
"link_title_description": "Kurze Titel funktionieren am besten als Meta-Titel.",
|
||||||
"link_title_placeholder": "Kundenfeedback-Umfrage",
|
|
||||||
"preview_image": "Vorschaubild",
|
"preview_image": "Vorschaubild",
|
||||||
"preview_image_description": "Querformatige Bilder mit kleiner Dateigröße (<4MB) funktionieren am besten.",
|
"preview_image_description": "Querformatige Bilder mit kleiner Dateigröße (<4MB) funktionieren am besten.",
|
||||||
"title": "Link-Einstellungen"
|
"title": "Link-Einstellungen"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Apply filters",
|
"apply_filters": "Apply filters",
|
||||||
"are_you_sure": "Are you sure?",
|
"are_you_sure": "Are you sure?",
|
||||||
"attributes": "Attributes",
|
"attributes": "Attributes",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"booked": "Booked",
|
"booked": "Booked",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Account deletion consequences",
|
"account_deletion_consequences_warning": "Account deletion consequences",
|
||||||
"avatar_update_failed": "Avatar update failed. Please try again.",
|
|
||||||
"backup_code": "Backup Code",
|
"backup_code": "Backup Code",
|
||||||
"change_image": "Change image",
|
|
||||||
"confirm_delete_account": "Delete your account with all of your personal information and data",
|
"confirm_delete_account": "Delete your account with all of your personal information and data",
|
||||||
"confirm_delete_my_account": "Delete My Account",
|
"confirm_delete_my_account": "Delete My Account",
|
||||||
"confirm_your_current_password_to_get_started": "Confirm your current password to get started.",
|
"confirm_your_current_password_to_get_started": "Confirm your current password to get started.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "Your email change request has been initiated.",
|
"email_change_initiated": "Your email change request has been initiated.",
|
||||||
"enable_two_factor_authentication": "Enable two factor authentication",
|
"enable_two_factor_authentication": "Enable two factor authentication",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Enter the code from your authenticator app below.",
|
"enter_the_code_from_your_authenticator_app_below": "Enter the code from your authenticator app below.",
|
||||||
"file_size_must_be_less_than_10mb": "File size must be less than 10MB.",
|
|
||||||
"invalid_file_type": "Invalid file type. Only JPEG, PNG, and WEBP files are allowed.",
|
|
||||||
"lost_access": "Lost access",
|
"lost_access": "Lost access",
|
||||||
"or_enter_the_following_code_manually": "Or enter the following code manually:",
|
"or_enter_the_following_code_manually": "Or enter the following code manually:",
|
||||||
"organization_identification": "Assist your organization in identifying you on Formbricks",
|
|
||||||
"organizations_delete_message": "You are the only owner of these organizations, so they <b>will be deleted as well.</b>",
|
"organizations_delete_message": "You are the only owner of these organizations, so they <b>will be deleted as well.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Permanent removal of all of your personal information and data",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Permanent removal of all of your personal information and data",
|
||||||
"personal_information": "Personal information",
|
"personal_information": "Personal information",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Please enter {email} in the following field to confirm the definitive deletion of your account:",
|
"please_enter_email_to_confirm_account_deletion": "Please enter {email} in the following field to confirm the definitive deletion of your account:",
|
||||||
"profile_updated_successfully": "Your profile was updated successfully",
|
"profile_updated_successfully": "Your profile was updated successfully",
|
||||||
"remove_image": "Remove image",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Save the following backup codes in a safe place.",
|
"save_the_following_backup_codes_in_a_safe_place": "Save the following backup codes in a safe place.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Scan the QR code below with your authenticator app.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Scan the QR code below with your authenticator app.",
|
||||||
"security_description": "Manage your password and other security settings like two-factor authentication (2FA).",
|
"security_description": "Manage your password and other security settings like two-factor authentication (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Two-Factor Code",
|
"two_factor_code": "Two-Factor Code",
|
||||||
"unlock_two_factor_authentication": "Unlock two-factor authentication with a higher plan",
|
"unlock_two_factor_authentication": "Unlock two-factor authentication with a higher plan",
|
||||||
"update_personal_info": "Update your personal information",
|
"update_personal_info": "Update your personal information",
|
||||||
"upload_image": "Upload image",
|
|
||||||
"warning_cannot_delete_account": "You are the only owner of this organization. Please transfer ownership to another member first.",
|
"warning_cannot_delete_account": "You are the only owner of this organization. Please transfer ownership to another member first.",
|
||||||
"warning_cannot_undo": "This cannot be undone",
|
"warning_cannot_undo": "This cannot be undone"
|
||||||
"you_must_select_a_file": "You must select a file."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Add members to the team and determine their role.",
|
"add_members_description": "Add members to the team and determine their role.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "The meta data is loaded based on the `lang` value in the URL.",
|
"language_help_text": "The meta data is loaded based on the `lang` value in the URL.",
|
||||||
"link_description": "Link description",
|
"link_description": "Link description",
|
||||||
"link_description_description": "Descriptions between 55-200 characters perform best.",
|
"link_description_description": "Descriptions between 55-200 characters perform best.",
|
||||||
"link_description_placeholder": "Help us improve by sharing your thoughts.",
|
|
||||||
"link_title": "Link title",
|
"link_title": "Link title",
|
||||||
"link_title_description": "Short titles perform best as Meta Titles.",
|
"link_title_description": "Short titles perform best as Meta Titles.",
|
||||||
"link_title_placeholder": "Customer Feedback Survey",
|
|
||||||
"preview_image": "Preview image",
|
"preview_image": "Preview image",
|
||||||
"preview_image_description": "Landscape images with small file sizes (<4MB) perform best.",
|
"preview_image_description": "Landscape images with small file sizes (<4MB) perform best.",
|
||||||
"title": "Link settings"
|
"title": "Link settings"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Appliquer des filtres",
|
"apply_filters": "Appliquer des filtres",
|
||||||
"are_you_sure": "Es-tu sûr ?",
|
"are_you_sure": "Es-tu sûr ?",
|
||||||
"attributes": "Attributs",
|
"attributes": "Attributs",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Retour",
|
"back": "Retour",
|
||||||
"billing": "Facturation",
|
"billing": "Facturation",
|
||||||
"booked": "Réservé",
|
"booked": "Réservé",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Conséquences de la suppression de compte",
|
"account_deletion_consequences_warning": "Conséquences de la suppression de compte",
|
||||||
"avatar_update_failed": "La mise à jour de l'avatar a échoué. Veuillez réessayer.",
|
|
||||||
"backup_code": "Code de sauvegarde",
|
"backup_code": "Code de sauvegarde",
|
||||||
"change_image": "Changer l'image",
|
|
||||||
"confirm_delete_account": "Supprimez votre compte avec toutes vos informations personnelles et données.",
|
"confirm_delete_account": "Supprimez votre compte avec toutes vos informations personnelles et données.",
|
||||||
"confirm_delete_my_account": "Supprimer mon compte",
|
"confirm_delete_my_account": "Supprimer mon compte",
|
||||||
"confirm_your_current_password_to_get_started": "Confirmez votre mot de passe actuel pour commencer.",
|
"confirm_your_current_password_to_get_started": "Confirmez votre mot de passe actuel pour commencer.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "Votre demande de changement d'email a été initiée.",
|
"email_change_initiated": "Votre demande de changement d'email a été initiée.",
|
||||||
"enable_two_factor_authentication": "Activer l'authentification à deux facteurs",
|
"enable_two_factor_authentication": "Activer l'authentification à deux facteurs",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Entrez le code de votre application d'authentification ci-dessous.",
|
"enter_the_code_from_your_authenticator_app_below": "Entrez le code de votre application d'authentification ci-dessous.",
|
||||||
"file_size_must_be_less_than_10mb": "La taille du fichier doit être inférieure à 10 Mo.",
|
|
||||||
"invalid_file_type": "Type de fichier invalide. Seuls les fichiers JPEG, PNG et WEBP sont autorisés.",
|
|
||||||
"lost_access": "Accès perdu",
|
"lost_access": "Accès perdu",
|
||||||
"or_enter_the_following_code_manually": "Ou entrez le code suivant manuellement :",
|
"or_enter_the_following_code_manually": "Ou entrez le code suivant manuellement :",
|
||||||
"organization_identification": "Aidez votre organisation à vous identifier sur Formbricks",
|
|
||||||
"organizations_delete_message": "Tu es le seul propriétaire de ces organisations, elles <b>seront aussi supprimées.</b>",
|
"organizations_delete_message": "Tu es le seul propriétaire de ces organisations, elles <b>seront aussi supprimées.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Suppression permanente de toutes vos informations et données personnelles.",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Suppression permanente de toutes vos informations et données personnelles.",
|
||||||
"personal_information": "Informations personnelles",
|
"personal_information": "Informations personnelles",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Veuillez entrer {email} dans le champ suivant pour confirmer la suppression définitive de votre compte :",
|
"please_enter_email_to_confirm_account_deletion": "Veuillez entrer {email} dans le champ suivant pour confirmer la suppression définitive de votre compte :",
|
||||||
"profile_updated_successfully": "Votre profil a été mis à jour avec succès.",
|
"profile_updated_successfully": "Votre profil a été mis à jour avec succès.",
|
||||||
"remove_image": "Supprimer l'image",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Enregistrez les codes de sauvegarde suivants dans un endroit sûr.",
|
"save_the_following_backup_codes_in_a_safe_place": "Enregistrez les codes de sauvegarde suivants dans un endroit sûr.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Scannez le code QR ci-dessous avec votre application d'authentification.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Scannez le code QR ci-dessous avec votre application d'authentification.",
|
||||||
"security_description": "Gérez votre mot de passe et d'autres paramètres de sécurité comme l'authentification à deux facteurs (2FA).",
|
"security_description": "Gérez votre mot de passe et d'autres paramètres de sécurité comme l'authentification à deux facteurs (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Code à deux facteurs",
|
"two_factor_code": "Code à deux facteurs",
|
||||||
"unlock_two_factor_authentication": "Débloquez l'authentification à deux facteurs avec une offre supérieure",
|
"unlock_two_factor_authentication": "Débloquez l'authentification à deux facteurs avec une offre supérieure",
|
||||||
"update_personal_info": "Mettez à jour vos informations personnelles",
|
"update_personal_info": "Mettez à jour vos informations personnelles",
|
||||||
"upload_image": "Télécharger l'image",
|
|
||||||
"warning_cannot_delete_account": "Tu es le seul propriétaire de cette organisation. Transfère la propriété à un autre membre d'abord.",
|
"warning_cannot_delete_account": "Tu es le seul propriétaire de cette organisation. Transfère la propriété à un autre membre d'abord.",
|
||||||
"warning_cannot_undo": "Ceci ne peut pas être annulé",
|
"warning_cannot_undo": "Ceci ne peut pas être annulé"
|
||||||
"you_must_select_a_file": "Vous devez sélectionner un fichier."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Ajoutez des membres à l'équipe et déterminez leur rôle.",
|
"add_members_description": "Ajoutez des membres à l'équipe et déterminez leur rôle.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "Les métadonnées sont chargées en fonction de la valeur « lang » dans l'URL.",
|
"language_help_text": "Les métadonnées sont chargées en fonction de la valeur « lang » dans l'URL.",
|
||||||
"link_description": "Description du lien",
|
"link_description": "Description du lien",
|
||||||
"link_description_description": "« Les descriptions entre 55 et 200 caractères donnent les meilleurs résultats. »",
|
"link_description_description": "« Les descriptions entre 55 et 200 caractères donnent les meilleurs résultats. »",
|
||||||
"link_description_placeholder": "Aidez-nous à nous améliorer en partageant vos pensées.",
|
|
||||||
"link_title": "Titre du lien",
|
"link_title": "Titre du lien",
|
||||||
"link_title_description": "Les titres courts fonctionnent mieux comme titres méta.",
|
"link_title_description": "Les titres courts fonctionnent mieux comme titres méta.",
|
||||||
"link_title_placeholder": "Sondage de Retour Clients",
|
|
||||||
"preview_image": "Aperçu de l'image",
|
"preview_image": "Aperçu de l'image",
|
||||||
"preview_image_description": "Les images en paysage avec de petites tailles de fichier (<4MB) fonctionnent le mieux.",
|
"preview_image_description": "Les images en paysage avec de petites tailles de fichier (<4MB) fonctionnent le mieux.",
|
||||||
"title": "Paramètres de lien"
|
"title": "Paramètres de lien"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Aplicar filtros",
|
"apply_filters": "Aplicar filtros",
|
||||||
"are_you_sure": "Certeza?",
|
"are_you_sure": "Certeza?",
|
||||||
"attributes": "atributos",
|
"attributes": "atributos",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Voltar",
|
"back": "Voltar",
|
||||||
"billing": "Faturamento",
|
"billing": "Faturamento",
|
||||||
"booked": "Reservado",
|
"booked": "Reservado",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Consequências da exclusão da conta",
|
"account_deletion_consequences_warning": "Consequências da exclusão da conta",
|
||||||
"avatar_update_failed": "Falha ao atualizar o avatar. Por favor, tente novamente.",
|
|
||||||
"backup_code": "Código de Backup",
|
"backup_code": "Código de Backup",
|
||||||
"change_image": "Mudar imagem",
|
|
||||||
"confirm_delete_account": "Apague sua conta com todas as suas informações pessoais e dados",
|
"confirm_delete_account": "Apague sua conta com todas as suas informações pessoais e dados",
|
||||||
"confirm_delete_my_account": "Excluir Minha Conta",
|
"confirm_delete_my_account": "Excluir Minha Conta",
|
||||||
"confirm_your_current_password_to_get_started": "Confirme sua senha atual para começar.",
|
"confirm_your_current_password_to_get_started": "Confirme sua senha atual para começar.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "Sua solicitação de alteração de e-mail foi iniciada.",
|
"email_change_initiated": "Sua solicitação de alteração de e-mail foi iniciada.",
|
||||||
"enable_two_factor_authentication": "Ativar autenticação de dois fatores",
|
"enable_two_factor_authentication": "Ativar autenticação de dois fatores",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Digite o código do seu app autenticador abaixo.",
|
"enter_the_code_from_your_authenticator_app_below": "Digite o código do seu app autenticador abaixo.",
|
||||||
"file_size_must_be_less_than_10mb": "O tamanho do arquivo deve ser menor que 10MB.",
|
|
||||||
"invalid_file_type": "Tipo de arquivo inválido. Só são permitidos arquivos JPEG, PNG e WEBP.",
|
|
||||||
"lost_access": "Perdi o acesso",
|
"lost_access": "Perdi o acesso",
|
||||||
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
||||||
"organization_identification": "Ajude sua organização a te identificar no Formbricks",
|
|
||||||
"organizations_delete_message": "Você é o único dono dessas organizações, então elas <b>também serão apagadas.</b>",
|
"organizations_delete_message": "Você é o único dono dessas organizações, então elas <b>também serão apagadas.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Remoção permanente de todas as suas informações e dados pessoais",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Remoção permanente de todas as suas informações e dados pessoais",
|
||||||
"personal_information": "Informações pessoais",
|
"personal_information": "Informações pessoais",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Por favor, insira {email} no campo abaixo para confirmar a exclusão definitiva da sua conta:",
|
"please_enter_email_to_confirm_account_deletion": "Por favor, insira {email} no campo abaixo para confirmar a exclusão definitiva da sua conta:",
|
||||||
"profile_updated_successfully": "Seu perfil foi atualizado com sucesso",
|
"profile_updated_successfully": "Seu perfil foi atualizado com sucesso",
|
||||||
"remove_image": "Remover imagem",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup em um lugar seguro.",
|
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup em um lugar seguro.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Escaneie o código QR abaixo com seu app autenticador.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Escaneie o código QR abaixo com seu app autenticador.",
|
||||||
"security_description": "Gerencie sua senha e outras configurações de segurança como a autenticação de dois fatores (2FA).",
|
"security_description": "Gerencie sua senha e outras configurações de segurança como a autenticação de dois fatores (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Código de Dois Fatores",
|
"two_factor_code": "Código de Dois Fatores",
|
||||||
"unlock_two_factor_authentication": "Desbloqueia a autenticação de dois fatores com um plano melhor",
|
"unlock_two_factor_authentication": "Desbloqueia a autenticação de dois fatores com um plano melhor",
|
||||||
"update_personal_info": "Atualize suas informações pessoais",
|
"update_personal_info": "Atualize suas informações pessoais",
|
||||||
"upload_image": "Enviar imagem",
|
|
||||||
"warning_cannot_delete_account": "Você é o único dono desta organização. Transfere a propriedade para outra pessoa primeiro.",
|
"warning_cannot_delete_account": "Você é o único dono desta organização. Transfere a propriedade para outra pessoa primeiro.",
|
||||||
"warning_cannot_undo": "Isso não pode ser desfeito",
|
"warning_cannot_undo": "Isso não pode ser desfeito"
|
||||||
"you_must_select_a_file": "Você tem que selecionar um arquivo."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Adicione membros à equipe e determine sua função.",
|
"add_members_description": "Adicione membros à equipe e determine sua função.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "Os metadados são carregados com base no valor `lang` na URL.",
|
"language_help_text": "Os metadados são carregados com base no valor `lang` na URL.",
|
||||||
"link_description": "Descrição do link",
|
"link_description": "Descrição do link",
|
||||||
"link_description_description": "\"Descrições entre 55-200 caracteres têm um melhor desempenho.\"",
|
"link_description_description": "\"Descrições entre 55-200 caracteres têm um melhor desempenho.\"",
|
||||||
"link_description_placeholder": "Ajude-nos a melhorar compartilhando suas opiniões.",
|
|
||||||
"link_title": "Título do link",
|
"link_title": "Título do link",
|
||||||
"link_title_description": "Títulos curtos têm melhor desempenho como Meta Títulos.",
|
"link_title_description": "Títulos curtos têm melhor desempenho como Meta Títulos.",
|
||||||
"link_title_placeholder": "Pesquisa de Feedback do Cliente",
|
|
||||||
"preview_image": "Imagem de prévia",
|
"preview_image": "Imagem de prévia",
|
||||||
"preview_image_description": "Imagens em paisagem com tamanhos de arquivo pequenos (<4MB) têm o melhor desempenho.",
|
"preview_image_description": "Imagens em paisagem com tamanhos de arquivo pequenos (<4MB) têm o melhor desempenho.",
|
||||||
"title": "Configurações de link"
|
"title": "Configurações de link"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Aplicar filtros",
|
"apply_filters": "Aplicar filtros",
|
||||||
"are_you_sure": "Tem a certeza?",
|
"are_you_sure": "Tem a certeza?",
|
||||||
"attributes": "Atributos",
|
"attributes": "Atributos",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Voltar",
|
"back": "Voltar",
|
||||||
"billing": "Faturação",
|
"billing": "Faturação",
|
||||||
"booked": "Reservado",
|
"booked": "Reservado",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Consequências da eliminação da conta",
|
"account_deletion_consequences_warning": "Consequências da eliminação da conta",
|
||||||
"avatar_update_failed": "Falha na atualização do avatar. Por favor, tente novamente.",
|
|
||||||
"backup_code": "Código de Backup",
|
"backup_code": "Código de Backup",
|
||||||
"change_image": "Alterar imagem",
|
|
||||||
"confirm_delete_account": "Eliminar a sua conta com todas as suas informações e dados pessoais",
|
"confirm_delete_account": "Eliminar a sua conta com todas as suas informações e dados pessoais",
|
||||||
"confirm_delete_my_account": "Eliminar a Minha Conta",
|
"confirm_delete_my_account": "Eliminar a Minha Conta",
|
||||||
"confirm_your_current_password_to_get_started": "Confirme a sua palavra-passe atual para começar.",
|
"confirm_your_current_password_to_get_started": "Confirme a sua palavra-passe atual para começar.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "O seu pedido de alteração de email foi iniciado.",
|
"email_change_initiated": "O seu pedido de alteração de email foi iniciado.",
|
||||||
"enable_two_factor_authentication": "Ativar autenticação de dois fatores",
|
"enable_two_factor_authentication": "Ativar autenticação de dois fatores",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Introduza o código da sua aplicação de autenticação abaixo.",
|
"enter_the_code_from_your_authenticator_app_below": "Introduza o código da sua aplicação de autenticação abaixo.",
|
||||||
"file_size_must_be_less_than_10mb": "O tamanho do ficheiro deve ser inferior a 10MB.",
|
|
||||||
"invalid_file_type": "Tipo de ficheiro inválido. Apenas são permitidos ficheiros JPEG, PNG e WEBP.",
|
|
||||||
"lost_access": "Perdeu o acesso",
|
"lost_access": "Perdeu o acesso",
|
||||||
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
||||||
"organization_identification": "Ajude a sua organização a identificá-lo no Formbricks",
|
|
||||||
"organizations_delete_message": "É o único proprietário destas organizações, por isso <b>também serão eliminadas.</b>",
|
"organizations_delete_message": "É o único proprietário destas organizações, por isso <b>também serão eliminadas.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Remoção permanente de todas as suas informações e dados pessoais",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Remoção permanente de todas as suas informações e dados pessoais",
|
||||||
"personal_information": "Informações pessoais",
|
"personal_information": "Informações pessoais",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Por favor, insira {email} no campo seguinte para confirmar a eliminação definitiva da sua conta:",
|
"please_enter_email_to_confirm_account_deletion": "Por favor, insira {email} no campo seguinte para confirmar a eliminação definitiva da sua conta:",
|
||||||
"profile_updated_successfully": "O seu perfil foi atualizado com sucesso",
|
"profile_updated_successfully": "O seu perfil foi atualizado com sucesso",
|
||||||
"remove_image": "Remover imagem",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup num local seguro.",
|
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup num local seguro.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Digitalize o código QR abaixo com a sua aplicação de autenticação.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Digitalize o código QR abaixo com a sua aplicação de autenticação.",
|
||||||
"security_description": "Gerir a sua palavra-passe e outras definições de segurança, como a autenticação de dois fatores (2FA).",
|
"security_description": "Gerir a sua palavra-passe e outras definições de segurança, como a autenticação de dois fatores (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Código de Dois Fatores",
|
"two_factor_code": "Código de Dois Fatores",
|
||||||
"unlock_two_factor_authentication": "Desbloqueie a autenticação de dois fatores com um plano superior",
|
"unlock_two_factor_authentication": "Desbloqueie a autenticação de dois fatores com um plano superior",
|
||||||
"update_personal_info": "Atualize as suas informações pessoais",
|
"update_personal_info": "Atualize as suas informações pessoais",
|
||||||
"upload_image": "Carregar imagem",
|
|
||||||
"warning_cannot_delete_account": "É o único proprietário desta organização. Transfira a propriedade para outro membro primeiro.",
|
"warning_cannot_delete_account": "É o único proprietário desta organização. Transfira a propriedade para outro membro primeiro.",
|
||||||
"warning_cannot_undo": "Isto não pode ser desfeito",
|
"warning_cannot_undo": "Isto não pode ser desfeito"
|
||||||
"you_must_select_a_file": "Deve selecionar um ficheiro."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Adicionar membros à equipa e determinar o seu papel.",
|
"add_members_description": "Adicionar membros à equipa e determinar o seu papel.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "Os metadados são carregados com base no valor `lang` no URL.",
|
"language_help_text": "Os metadados são carregados com base no valor `lang` no URL.",
|
||||||
"link_description": "Descrição do link",
|
"link_description": "Descrição do link",
|
||||||
"link_description_description": "Descrições entre 55 a 200 caracteres têm melhor desempenho.",
|
"link_description_description": "Descrições entre 55 a 200 caracteres têm melhor desempenho.",
|
||||||
"link_description_placeholder": "Ajude-nos a melhorar compartilhando suas opiniões.",
|
|
||||||
"link_title": "Título do Link",
|
"link_title": "Título do Link",
|
||||||
"link_title_description": "Títulos curtos têm melhor desempenho como Meta Titles.",
|
"link_title_description": "Títulos curtos têm melhor desempenho como Meta Titles.",
|
||||||
"link_title_placeholder": "Inquérito de Feedback do Cliente",
|
|
||||||
"preview_image": "Pré-visualização da imagem",
|
"preview_image": "Pré-visualização da imagem",
|
||||||
"preview_image_description": "Imagens de paisagem com tamanhos pequenos (<4MB) apresentam melhor desempenho.",
|
"preview_image_description": "Imagens de paisagem com tamanhos pequenos (<4MB) apresentam melhor desempenho.",
|
||||||
"title": "Definições de ligação"
|
"title": "Definições de ligação"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "Aplică filtre",
|
"apply_filters": "Aplică filtre",
|
||||||
"are_you_sure": "Ești sigur?",
|
"are_you_sure": "Ești sigur?",
|
||||||
"attributes": "Atribute",
|
"attributes": "Atribute",
|
||||||
"avatar": "Avatar",
|
|
||||||
"back": "Înapoi",
|
"back": "Înapoi",
|
||||||
"billing": "Facturare",
|
"billing": "Facturare",
|
||||||
"booked": "Rezervat",
|
"booked": "Rezervat",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "Consecințele ștergerii contului",
|
"account_deletion_consequences_warning": "Consecințele ștergerii contului",
|
||||||
"avatar_update_failed": "Actualizarea avatarului a eșuat. Vă rugăm să încercați din nou.",
|
|
||||||
"backup_code": "Cod de rezervă",
|
"backup_code": "Cod de rezervă",
|
||||||
"change_image": "Schimbă imaginea",
|
|
||||||
"confirm_delete_account": "Șterge contul tău cu toate informațiile personale și datele tale",
|
"confirm_delete_account": "Șterge contul tău cu toate informațiile personale și datele tale",
|
||||||
"confirm_delete_my_account": "Șterge Contul Meu",
|
"confirm_delete_my_account": "Șterge Contul Meu",
|
||||||
"confirm_your_current_password_to_get_started": "Confirmaţi parola curentă pentru a începe.",
|
"confirm_your_current_password_to_get_started": "Confirmaţi parola curentă pentru a începe.",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "Cererea dvs. de schimbare a e-mailului a fost inițiată.",
|
"email_change_initiated": "Cererea dvs. de schimbare a e-mailului a fost inițiată.",
|
||||||
"enable_two_factor_authentication": "Activează autentificarea în doi pași",
|
"enable_two_factor_authentication": "Activează autentificarea în doi pași",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "Introduceți codul din aplicația dvs. de autentificare mai jos.",
|
"enter_the_code_from_your_authenticator_app_below": "Introduceți codul din aplicația dvs. de autentificare mai jos.",
|
||||||
"file_size_must_be_less_than_10mb": "Dimensiunea fișierului trebuie să fie mai mică de 10MB.",
|
|
||||||
"invalid_file_type": "Tip de fișier invalid. Sunt permise numai fișiere JPEG, PNG și WEBP.",
|
|
||||||
"lost_access": "Acces pierdut",
|
"lost_access": "Acces pierdut",
|
||||||
"or_enter_the_following_code_manually": "Sau introduceți manual următorul cod:",
|
"or_enter_the_following_code_manually": "Sau introduceți manual următorul cod:",
|
||||||
"organization_identification": "Ajutați organizația să vă identifice pe Formbricks",
|
|
||||||
"organizations_delete_message": "Ești singurul proprietar al acestor organizații, deci ele <b>vor fi șterse și ele.</b>",
|
"organizations_delete_message": "Ești singurul proprietar al acestor organizații, deci ele <b>vor fi șterse și ele.</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "Ștergerea permanentă a tuturor informațiilor și datelor tale personale",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "Ștergerea permanentă a tuturor informațiilor și datelor tale personale",
|
||||||
"personal_information": "Informații personale",
|
"personal_information": "Informații personale",
|
||||||
"please_enter_email_to_confirm_account_deletion": "Vă rugăm să introduceți {email} în câmpul următor pentru a confirma ștergerea definitivă a contului dumneavoastră:",
|
"please_enter_email_to_confirm_account_deletion": "Vă rugăm să introduceți {email} în câmpul următor pentru a confirma ștergerea definitivă a contului dumneavoastră:",
|
||||||
"profile_updated_successfully": "Profilul dvs. a fost actualizat cu succes",
|
"profile_updated_successfully": "Profilul dvs. a fost actualizat cu succes",
|
||||||
"remove_image": "Șterge imaginea",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "Salvează următoarele coduri de rezervă într-un loc sigur.",
|
"save_the_following_backup_codes_in_a_safe_place": "Salvează următoarele coduri de rezervă într-un loc sigur.",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "Scanați codul QR de mai jos cu aplicația dvs. de autentificare.",
|
"scan_the_qr_code_below_with_your_authenticator_app": "Scanați codul QR de mai jos cu aplicația dvs. de autentificare.",
|
||||||
"security_description": "Gestionează parola și alte setări de securitate, precum autentificarea în doi pași (2FA).",
|
"security_description": "Gestionează parola și alte setări de securitate, precum autentificarea în doi pași (2FA).",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "Codul cu doi factori",
|
"two_factor_code": "Codul cu doi factori",
|
||||||
"unlock_two_factor_authentication": "Deblocați autentificarea în doi pași cu un plan superior",
|
"unlock_two_factor_authentication": "Deblocați autentificarea în doi pași cu un plan superior",
|
||||||
"update_personal_info": "Actualizează informațiile tale personale",
|
"update_personal_info": "Actualizează informațiile tale personale",
|
||||||
"upload_image": "Încărcați imagine",
|
|
||||||
"warning_cannot_delete_account": "Ești singurul proprietar al acestei organizații. Te rugăm să transferi proprietatea către un alt membru mai întâi.",
|
"warning_cannot_delete_account": "Ești singurul proprietar al acestei organizații. Te rugăm să transferi proprietatea către un alt membru mai întâi.",
|
||||||
"warning_cannot_undo": "Aceasta nu poate fi anulată",
|
"warning_cannot_undo": "Aceasta nu poate fi anulată"
|
||||||
"you_must_select_a_file": "Trebuie să selectați un fișier."
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "Adaugă membri în echipă și stabilește rolul lor.",
|
"add_members_description": "Adaugă membri în echipă și stabilește rolul lor.",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "Meta datele sunt încărcate pe baza valorii `lang` din URL.",
|
"language_help_text": "Meta datele sunt încărcate pe baza valorii `lang` din URL.",
|
||||||
"link_description": "Descriere legătură",
|
"link_description": "Descriere legătură",
|
||||||
"link_description_description": "Descrierile între 55-200 de caractere au cele mai bune performanțe.",
|
"link_description_description": "Descrierile între 55-200 de caractere au cele mai bune performanțe.",
|
||||||
"link_description_placeholder": "Ajutați-ne să ne îmbunătățim împărtășindu-vă gândurile.",
|
|
||||||
"link_title": "Titlu link",
|
"link_title": "Titlu link",
|
||||||
"link_title_description": "Titlurile scurte funcționează cel mai bine ca Meta Title-uri.",
|
"link_title_description": "Titlurile scurte funcționează cel mai bine ca Meta Title-uri.",
|
||||||
"link_title_placeholder": "Chestionar de feedback al clienților",
|
|
||||||
"preview_image": "Previzualizare imagine",
|
"preview_image": "Previzualizare imagine",
|
||||||
"preview_image_description": "Imaginile panoramice cu dimensiuni de fișier mici (<4MB) au cel mai bun randament.",
|
"preview_image_description": "Imaginile panoramice cu dimensiuni de fișier mici (<4MB) au cel mai bun randament.",
|
||||||
"title": "Setări link"
|
"title": "Setări link"
|
||||||
|
|||||||
@@ -141,7 +141,6 @@
|
|||||||
"apply_filters": "套用篩選器",
|
"apply_filters": "套用篩選器",
|
||||||
"are_you_sure": "您確定嗎?",
|
"are_you_sure": "您確定嗎?",
|
||||||
"attributes": "屬性",
|
"attributes": "屬性",
|
||||||
"avatar": "頭像",
|
|
||||||
"back": "返回",
|
"back": "返回",
|
||||||
"billing": "帳單",
|
"billing": "帳單",
|
||||||
"booked": "已預訂",
|
"booked": "已預訂",
|
||||||
@@ -1111,9 +1110,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"account_deletion_consequences_warning": "帳戶刪除後果",
|
"account_deletion_consequences_warning": "帳戶刪除後果",
|
||||||
"avatar_update_failed": "頭像更新失敗。請再試一次。",
|
|
||||||
"backup_code": "備份碼",
|
"backup_code": "備份碼",
|
||||||
"change_image": "變更圖片",
|
|
||||||
"confirm_delete_account": "刪除您的帳戶以及您的所有個人資訊和資料",
|
"confirm_delete_account": "刪除您的帳戶以及您的所有個人資訊和資料",
|
||||||
"confirm_delete_my_account": "刪除我的帳戶",
|
"confirm_delete_my_account": "刪除我的帳戶",
|
||||||
"confirm_your_current_password_to_get_started": "確認您目前的密碼以開始使用。",
|
"confirm_your_current_password_to_get_started": "確認您目前的密碼以開始使用。",
|
||||||
@@ -1124,17 +1121,13 @@
|
|||||||
"email_change_initiated": "您的 email 更改請求已啟動。",
|
"email_change_initiated": "您的 email 更改請求已啟動。",
|
||||||
"enable_two_factor_authentication": "啟用雙重驗證",
|
"enable_two_factor_authentication": "啟用雙重驗證",
|
||||||
"enter_the_code_from_your_authenticator_app_below": "在下方輸入您驗證器應用程式中的程式碼。",
|
"enter_the_code_from_your_authenticator_app_below": "在下方輸入您驗證器應用程式中的程式碼。",
|
||||||
"file_size_must_be_less_than_10mb": "檔案大小必須小於 10MB。",
|
|
||||||
"invalid_file_type": "無效的檔案類型。僅允許 JPEG、PNG 和 WEBP 檔案。",
|
|
||||||
"lost_access": "無法存取",
|
"lost_access": "無法存取",
|
||||||
"or_enter_the_following_code_manually": "或手動輸入下列程式碼:",
|
"or_enter_the_following_code_manually": "或手動輸入下列程式碼:",
|
||||||
"organization_identification": "協助您的組織在 Formbricks 上識別您",
|
|
||||||
"organizations_delete_message": "您是這些組織的唯一擁有者,因此它們也 <b>將被刪除。</b>",
|
"organizations_delete_message": "您是這些組織的唯一擁有者,因此它們也 <b>將被刪除。</b>",
|
||||||
"permanent_removal_of_all_of_your_personal_information_and_data": "永久移除您的所有個人資訊和資料",
|
"permanent_removal_of_all_of_your_personal_information_and_data": "永久移除您的所有個人資訊和資料",
|
||||||
"personal_information": "個人資訊",
|
"personal_information": "個人資訊",
|
||||||
"please_enter_email_to_confirm_account_deletion": "請在以下欄位中輸入 '{'email'}' 以確認永久刪除您的帳戶:",
|
"please_enter_email_to_confirm_account_deletion": "請在以下欄位中輸入 '{'email'}' 以確認永久刪除您的帳戶:",
|
||||||
"profile_updated_successfully": "您的個人資料已成功更新",
|
"profile_updated_successfully": "您的個人資料已成功更新",
|
||||||
"remove_image": "移除圖片",
|
|
||||||
"save_the_following_backup_codes_in_a_safe_place": "將下列備份碼儲存在安全的地方。",
|
"save_the_following_backup_codes_in_a_safe_place": "將下列備份碼儲存在安全的地方。",
|
||||||
"scan_the_qr_code_below_with_your_authenticator_app": "使用您的驗證器應用程式掃描下方的 QR 碼。",
|
"scan_the_qr_code_below_with_your_authenticator_app": "使用您的驗證器應用程式掃描下方的 QR 碼。",
|
||||||
"security_description": "管理您的密碼和其他安全性設定,例如雙重驗證 (2FA)。",
|
"security_description": "管理您的密碼和其他安全性設定,例如雙重驗證 (2FA)。",
|
||||||
@@ -1144,10 +1137,8 @@
|
|||||||
"two_factor_code": "雙重驗證碼",
|
"two_factor_code": "雙重驗證碼",
|
||||||
"unlock_two_factor_authentication": "使用更高等級的方案解鎖雙重驗證",
|
"unlock_two_factor_authentication": "使用更高等級的方案解鎖雙重驗證",
|
||||||
"update_personal_info": "更新您的個人資訊",
|
"update_personal_info": "更新您的個人資訊",
|
||||||
"upload_image": "上傳圖片",
|
|
||||||
"warning_cannot_delete_account": "您是此組織的唯一擁有者。請先將所有權轉讓給其他成員。",
|
"warning_cannot_delete_account": "您是此組織的唯一擁有者。請先將所有權轉讓給其他成員。",
|
||||||
"warning_cannot_undo": "此操作無法復原",
|
"warning_cannot_undo": "此操作無法復原"
|
||||||
"you_must_select_a_file": "您必須選取檔案。"
|
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"add_members_description": "將成員新增至團隊並確定其角色。",
|
"add_members_description": "將成員新增至團隊並確定其角色。",
|
||||||
@@ -1715,10 +1706,8 @@
|
|||||||
"language_help_text": "中 繼資料 會 根據 URL 中 的 `lang` 值 載入。",
|
"language_help_text": "中 繼資料 會 根據 URL 中 的 `lang` 值 載入。",
|
||||||
"link_description": "連結描述",
|
"link_description": "連結描述",
|
||||||
"link_description_description": "描述在 55 - 200 個字符之間的表現最好。",
|
"link_description_description": "描述在 55 - 200 個字符之間的表現最好。",
|
||||||
"link_description_placeholder": "幫助 我們 改善 , 分享 您 的 想法 。",
|
|
||||||
"link_title": "連結標題",
|
"link_title": "連結標題",
|
||||||
"link_title_description": "短 標題 在 Meta Titles 中表現最佳。",
|
"link_title_description": "短 標題 在 Meta Titles 中表現最佳。",
|
||||||
"link_title_placeholder": "顧客 回饋 調查",
|
|
||||||
"preview_image": "預覽 圖片",
|
"preview_image": "預覽 圖片",
|
||||||
"preview_image_description": "景觀 圖片 檔案 大小 小於 4MB 效果 最佳。",
|
"preview_image_description": "景觀 圖片 檔案 大小 小於 4MB 效果 最佳。",
|
||||||
"title": "連結 設定"
|
"title": "連結 設定"
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ This guide explains the settings you need to use to configure SAML with your Ide
|
|||||||
|
|
||||||
**Entity ID / Identifier / Audience URI / Audience Restriction:** [https://saml.formbricks.com](https://saml.formbricks.com)
|
**Entity ID / Identifier / Audience URI / Audience Restriction:** [https://saml.formbricks.com](https://saml.formbricks.com)
|
||||||
|
|
||||||
> **Note:** [https://saml.formbricks.com](https://saml.formbricks.com) is hardcoded in Formbricks — do not replace it with your instance URL. It is the fixed SP Entity ID and must match exactly as shown in SAML assertions.
|
|
||||||
|
|
||||||
**Response:** Signed
|
**Response:** Signed
|
||||||
|
|
||||||
**Assertion Signature:** Signed
|
**Assertion Signature:** Signed
|
||||||
@@ -79,7 +77,7 @@ This guide explains the settings you need to use to configure SAML with your Ide
|
|||||||
</Step>
|
</Step>
|
||||||
<Step title="Enter the SAML Integration Settings as shown and click Next">
|
<Step title="Enter the SAML Integration Settings as shown and click Next">
|
||||||
- **Single Sign-On URL**: `https://<your-formbricks-instance>/api/auth/saml/callback` or `http://localhost:3000/api/auth/saml/callback` (if you are running Formbricks locally)
|
- **Single Sign-On URL**: `https://<your-formbricks-instance>/api/auth/saml/callback` or `http://localhost:3000/api/auth/saml/callback` (if you are running Formbricks locally)
|
||||||
- **Audience URI (SP Entity ID)**: `https://saml.formbricks.com` (hardcoded; do not replace with your instance URL)
|
- **Audience URI (SP Entity ID)**: `https://saml.formbricks.com`
|
||||||
<img src="/images/development/guides/auth-and-provision/okta/saml-integration-settings.webp" />
|
<img src="/images/development/guides/auth-and-provision/okta/saml-integration-settings.webp" />
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Fill the fields mapping as shown and click Next">
|
<Step title="Fill the fields mapping as shown and click Next">
|
||||||
|
|||||||
@@ -765,7 +765,7 @@ export function Survey({
|
|||||||
<LanguageSwitch
|
<LanguageSwitch
|
||||||
surveyLanguages={localSurvey.languages}
|
surveyLanguages={localSurvey.languages}
|
||||||
setSelectedLanguageCode={setselectedLanguage}
|
setSelectedLanguageCode={setselectedLanguage}
|
||||||
hoverColor={styling.inputColor?.light ?? "#000000"}
|
hoverColor={styling.inputColor?.light ?? "#f8fafc"}
|
||||||
borderRadius={styling.roundness ?? 8}
|
borderRadius={styling.roundness ?? 8}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -776,7 +776,7 @@ export function Survey({
|
|||||||
{isCloseButtonVisible && (
|
{isCloseButtonVisible && (
|
||||||
<SurveyCloseButton
|
<SurveyCloseButton
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
hoverColor={styling.inputColor?.light ?? "#000000"}
|
hoverColor={styling.inputColor?.light ?? "#f8fafc"}
|
||||||
borderRadius={styling.roundness ?? 8}
|
borderRadius={styling.roundness ?? 8}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,12 +31,6 @@ export const ZResponseFilterCondition = z.enum([
|
|||||||
"isEmpty",
|
"isEmpty",
|
||||||
"isNotEmpty",
|
"isNotEmpty",
|
||||||
"isAnyOf",
|
"isAnyOf",
|
||||||
"contains",
|
|
||||||
"doesNotContain",
|
|
||||||
"startsWith",
|
|
||||||
"doesNotStartWith",
|
|
||||||
"endsWith",
|
|
||||||
"doesNotEndWith",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TResponseDataValue = z.infer<typeof ZResponseDataValue>;
|
export type TResponseDataValue = z.infer<typeof ZResponseDataValue>;
|
||||||
@@ -155,36 +149,6 @@ const ZResponseFilterCriteriaIsAnyOf = z.object({
|
|||||||
value: z.record(z.string(), z.array(z.string())),
|
value: z.record(z.string(), z.array(z.string())),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ZResponseFilterCriteriaContains = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.contains),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaDoesNotContain = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.doesNotContain),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaStartsWith = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.startsWith),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaDoesNotStartWith = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.doesNotStartWith),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaEndsWith = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.endsWith),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaDoesNotEndWith = z.object({
|
|
||||||
op: z.literal(ZResponseFilterCondition.Values.doesNotEndWith),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZResponseFilterCriteriaFilledOut = z.object({
|
const ZResponseFilterCriteriaFilledOut = z.object({
|
||||||
op: z.literal("filledOut"),
|
op: z.literal("filledOut"),
|
||||||
});
|
});
|
||||||
@@ -253,16 +217,10 @@ export const ZResponseFilterCriteria = z.object({
|
|||||||
|
|
||||||
meta: z
|
meta: z
|
||||||
.record(
|
.record(
|
||||||
z.union([
|
z.object({
|
||||||
ZResponseFilterCriteriaDataEquals,
|
op: z.enum(["equals", "notEquals"]),
|
||||||
ZResponseFilterCriteriaDataNotEquals,
|
value: z.union([z.string(), z.number()]),
|
||||||
ZResponseFilterCriteriaContains,
|
})
|
||||||
ZResponseFilterCriteriaDoesNotContain,
|
|
||||||
ZResponseFilterCriteriaStartsWith,
|
|
||||||
ZResponseFilterCriteriaDoesNotStartWith,
|
|
||||||
ZResponseFilterCriteriaEndsWith,
|
|
||||||
ZResponseFilterCriteriaDoesNotEndWith,
|
|
||||||
])
|
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user