mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 18:00:26 -06:00
fix: github annotations (#6240)
This commit is contained in:
committed by
GitHub
parent
eee9ee8995
commit
e83cfa85a4
@@ -30,16 +30,16 @@ interface ManageIntegrationProps {
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
const tableHeaders = [
|
||||
"common.survey",
|
||||
"environments.integrations.airtable.table_name",
|
||||
"common.questions",
|
||||
"common.updated_at",
|
||||
];
|
||||
|
||||
export const ManageIntegration = (props: ManageIntegrationProps) => {
|
||||
const { airtableIntegration, environment, environmentId, setIsConnected, surveys, airtableArray } = props;
|
||||
const { t } = useTranslate();
|
||||
|
||||
const tableHeaders = [
|
||||
t("common.survey"),
|
||||
t("environments.integrations.airtable.table_name"),
|
||||
t("common.questions"),
|
||||
t("common.updated_at"),
|
||||
];
|
||||
const [isDeleting, setisDeleting] = useState(false);
|
||||
const [isDeleteIntegrationModalOpen, setIsDeleteIntegrationModalOpen] = useState(false);
|
||||
const [defaultValues, setDefaultValues] = useState<(IntegrationModalInputs & { index: number }) | null>(
|
||||
@@ -100,7 +100,7 @@ export const ManageIntegration = (props: ManageIntegrationProps) => {
|
||||
<div className="grid h-12 grid-cols-8 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
|
||||
{tableHeaders.map((header) => (
|
||||
<div key={header} className={`col-span-2 hidden text-center sm:block`}>
|
||||
{t(header)}
|
||||
{header}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -281,7 +281,7 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
? `${dateRange?.from ? format(dateRange?.from, "dd LLL") : "Select first date"} - ${
|
||||
dateRange?.to ? format(dateRange.to, "dd LLL") : "Select last date"
|
||||
}`
|
||||
: t(filterRange)}
|
||||
: filterRange}
|
||||
</span>
|
||||
{isFilterDropDownOpen ? (
|
||||
<ChevronUp className="ml-2 h-4 w-4 opacity-50" />
|
||||
@@ -296,28 +296,28 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
setFilterRange(getFilterDropDownLabels(t).ALL_TIME);
|
||||
setDateRange({ from: undefined, to: getTodayDate() });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).ALL_TIME)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).ALL_TIME}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setFilterRange(getFilterDropDownLabels(t).LAST_7_DAYS);
|
||||
setDateRange({ from: startOfDay(subDays(new Date(), 7)), to: getTodayDate() });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_7_DAYS)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_7_DAYS}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setFilterRange(getFilterDropDownLabels(t).LAST_30_DAYS);
|
||||
setDateRange({ from: startOfDay(subDays(new Date(), 30)), to: getTodayDate() });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_30_DAYS)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_30_DAYS}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setFilterRange(getFilterDropDownLabels(t).THIS_MONTH);
|
||||
setDateRange({ from: startOfMonth(new Date()), to: getTodayDate() });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).THIS_MONTH)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).THIS_MONTH}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -327,14 +327,14 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
to: endOfMonth(subMonths(getTodayDate(), 1)),
|
||||
});
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_MONTH)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_MONTH}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setFilterRange(getFilterDropDownLabels(t).THIS_QUARTER);
|
||||
setDateRange({ from: startOfQuarter(new Date()), to: endOfQuarter(getTodayDate()) });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).THIS_QUARTER)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).THIS_QUARTER}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -344,7 +344,7 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
to: endOfQuarter(subQuarters(getTodayDate(), 1)),
|
||||
});
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_QUARTER)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_QUARTER}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -354,14 +354,14 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
to: endOfMonth(getTodayDate()),
|
||||
});
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_6_MONTHS)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_6_MONTHS}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setFilterRange(getFilterDropDownLabels(t).THIS_YEAR);
|
||||
setDateRange({ from: startOfYear(new Date()), to: endOfYear(getTodayDate()) });
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).THIS_YEAR)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).THIS_YEAR}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -371,7 +371,7 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
to: endOfYear(subYears(getTodayDate(), 1)),
|
||||
});
|
||||
}}>
|
||||
<p className="text-slate-700">{t(getFilterDropDownLabels(t).LAST_YEAR)}</p>
|
||||
<p className="text-slate-700">{getFilterDropDownLabels(t).LAST_YEAR}</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -380,7 +380,7 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => {
|
||||
setSelectingDate(DateSelected.FROM);
|
||||
}}>
|
||||
<p className="text-sm text-slate-700 hover:ring-0">
|
||||
{t(getFilterDropDownLabels(t).CUSTOM_RANGE)}
|
||||
{getFilterDropDownLabels(t).CUSTOM_RANGE}
|
||||
</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -21,8 +21,11 @@ import {
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { TTemplate, TTemplateRole } from "@formbricks/types/templates";
|
||||
|
||||
const defaultButtonLabel = "common.next";
|
||||
const defaultBackButtonLabel = "common.back";
|
||||
const getDefaultButtonLabel = (label: string | undefined, t: TFnType) =>
|
||||
createI18nString(label || t("common.next"), []);
|
||||
|
||||
const getDefaultBackButtonLabel = (label: string | undefined, t: TFnType) =>
|
||||
createI18nString(label || t("common.back"), []);
|
||||
|
||||
export const buildMultipleChoiceQuestion = ({
|
||||
id,
|
||||
@@ -63,8 +66,8 @@ export const buildMultipleChoiceQuestion = ({
|
||||
const id = containsOther && isLastIndex ? "other" : choiceIds ? choiceIds[index] : createId();
|
||||
return { id, label: createI18nString(choice, []) };
|
||||
}),
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
shuffleOption: shuffleOption || "none",
|
||||
required: required ?? false,
|
||||
logic,
|
||||
@@ -103,8 +106,8 @@ export const buildOpenTextQuestion = ({
|
||||
subheader: subheader ? createI18nString(subheader, []) : undefined,
|
||||
placeholder: placeholder ? createI18nString(placeholder, []) : undefined,
|
||||
headline: createI18nString(headline, []),
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
required: required ?? false,
|
||||
longAnswer,
|
||||
logic,
|
||||
@@ -151,8 +154,8 @@ export const buildRatingQuestion = ({
|
||||
headline: createI18nString(headline, []),
|
||||
scale,
|
||||
range,
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
required: required ?? false,
|
||||
isColorCodingEnabled,
|
||||
lowerLabel: lowerLabel ? createI18nString(lowerLabel, []) : undefined,
|
||||
@@ -192,8 +195,8 @@ export const buildNPSQuestion = ({
|
||||
type: TSurveyQuestionTypeEnum.NPS,
|
||||
subheader: subheader ? createI18nString(subheader, []) : undefined,
|
||||
headline: createI18nString(headline, []),
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
required: required ?? false,
|
||||
isColorCodingEnabled,
|
||||
lowerLabel: lowerLabel ? createI18nString(lowerLabel, []) : undefined,
|
||||
@@ -228,8 +231,8 @@ export const buildConsentQuestion = ({
|
||||
type: TSurveyQuestionTypeEnum.Consent,
|
||||
subheader: subheader ? createI18nString(subheader, []) : undefined,
|
||||
headline: createI18nString(headline, []),
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
required: required ?? false,
|
||||
label: createI18nString(label, []),
|
||||
logic,
|
||||
@@ -266,8 +269,8 @@ export const buildCTAQuestion = ({
|
||||
type: TSurveyQuestionTypeEnum.CTA,
|
||||
html: html ? createI18nString(html, []) : undefined,
|
||||
headline: createI18nString(headline, []),
|
||||
buttonLabel: createI18nString(buttonLabel || t(defaultButtonLabel), []),
|
||||
backButtonLabel: createI18nString(backButtonLabel || t(defaultBackButtonLabel), []),
|
||||
buttonLabel: getDefaultButtonLabel(buttonLabel, t),
|
||||
backButtonLabel: getDefaultBackButtonLabel(backButtonLabel, t),
|
||||
dismissButtonLabel: dismissButtonLabel ? createI18nString(dismissButtonLabel, []) : undefined,
|
||||
required: required ?? false,
|
||||
buttonExternal,
|
||||
|
||||
@@ -71,7 +71,7 @@ export const PricingCard = ({
|
||||
window.open(plan.href, "_blank", "noopener,noreferrer");
|
||||
}}
|
||||
className="flex justify-center bg-white">
|
||||
{t(plan.CTA ?? "common.request_pricing")}
|
||||
{plan.CTA ?? t("common.request_pricing")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export const PricingCard = ({
|
||||
setLoading(false);
|
||||
}}
|
||||
className="flex justify-center">
|
||||
{t(plan.CTA ?? "common.start_free_trial")}
|
||||
{plan.CTA ?? t("common.start_free_trial")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export const PricingCard = ({
|
||||
plan.featured ? "text-slate-900" : "text-slate-800",
|
||||
"text-sm font-semibold leading-6"
|
||||
)}>
|
||||
{t(plan.name)}
|
||||
{plan.name}
|
||||
</h2>
|
||||
{isCurrentPlan && (
|
||||
<Badge type="success" size="normal" text={t("environments.settings.billing.current_plan")} />
|
||||
@@ -155,7 +155,7 @@ export const PricingCard = ({
|
||||
? planPeriod === "monthly"
|
||||
? plan.price.monthly
|
||||
: plan.price.yearly
|
||||
: t(plan.price.monthly)}
|
||||
: plan.price.monthly}
|
||||
</p>
|
||||
{plan.id !== projectFeatureKeys.ENTERPRISE && (
|
||||
<div className="text-sm leading-5">
|
||||
@@ -196,7 +196,7 @@ export const PricingCard = ({
|
||||
className={cn(plan.featured ? "text-brand-dark" : "text-slate-500", "h-6 w-5 flex-none")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{t(mainFeature)}
|
||||
{mainFeature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -215,7 +215,7 @@ export const PricingCard = ({
|
||||
open={upgradeModalOpen}
|
||||
setOpen={setUpgradeModalOpen}
|
||||
text={t("environments.settings.billing.switch_plan_confirmation_text", {
|
||||
plan: t(plan.name),
|
||||
plan: plan.name,
|
||||
price: planPeriod === "monthly" ? plan.price.monthly : plan.price.yearly,
|
||||
period:
|
||||
planPeriod === "monthly"
|
||||
|
||||
@@ -11,30 +11,30 @@ interface TriggerCheckboxGroupProps {
|
||||
allowChanges: boolean;
|
||||
}
|
||||
|
||||
const triggers: {
|
||||
title: string;
|
||||
value: PipelineTriggers;
|
||||
}[] = [
|
||||
{
|
||||
title: "environments.integrations.webhooks.response_created",
|
||||
value: "responseCreated",
|
||||
},
|
||||
{
|
||||
title: "environments.integrations.webhooks.response_updated",
|
||||
value: "responseUpdated",
|
||||
},
|
||||
{
|
||||
title: "environments.integrations.webhooks.response_finished",
|
||||
value: "responseFinished",
|
||||
},
|
||||
];
|
||||
|
||||
export const TriggerCheckboxGroup: React.FC<TriggerCheckboxGroupProps> = ({
|
||||
selectedTriggers,
|
||||
onCheckboxChange,
|
||||
allowChanges,
|
||||
}) => {
|
||||
const { t } = useTranslate();
|
||||
|
||||
const triggers: {
|
||||
title: string;
|
||||
value: PipelineTriggers;
|
||||
}[] = [
|
||||
{
|
||||
title: t("environments.integrations.webhooks.response_created"),
|
||||
value: "responseCreated",
|
||||
},
|
||||
{
|
||||
title: t("environments.integrations.webhooks.response_updated"),
|
||||
value: "responseUpdated",
|
||||
},
|
||||
{
|
||||
title: t("environments.integrations.webhooks.response_finished"),
|
||||
value: "responseFinished",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="mt-1 rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
@@ -58,7 +58,7 @@ export const TriggerCheckboxGroup: React.FC<TriggerCheckboxGroupProps> = ({
|
||||
}}
|
||||
disabled={!allowChanges}
|
||||
/>
|
||||
<span className="ml-2">{t(trigger.title)}</span>
|
||||
<span className="ml-2">{trigger.title}</span>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -95,6 +95,5 @@ describe("getOrganizationAccessKeyDisplayName", () => {
|
||||
test("returns tolgee string for other keys", () => {
|
||||
const t = vi.fn((k) => k);
|
||||
expect(getOrganizationAccessKeyDisplayName("otherKey", t)).toBe("otherKey");
|
||||
expect(t).toHaveBeenCalledWith("otherKey");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,6 @@ export const getOrganizationAccessKeyDisplayName = (key: string, t: TFnType) =>
|
||||
case "accessControl":
|
||||
return t("environments.project.api_keys.access_control");
|
||||
default:
|
||||
return t(key);
|
||||
return key;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,14 +16,6 @@ import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { z } from "zod";
|
||||
|
||||
const placements = [
|
||||
{ name: "common.bottom_right", value: "bottomRight", disabled: false },
|
||||
{ name: "common.top_right", value: "topRight", disabled: false },
|
||||
{ name: "common.top_left", value: "topLeft", disabled: false },
|
||||
{ name: "common.bottom_left", value: "bottomLeft", disabled: false },
|
||||
{ name: "common.centered_modal", value: "center", disabled: false },
|
||||
];
|
||||
|
||||
interface EditPlacementProps {
|
||||
project: Project;
|
||||
environmentId: string;
|
||||
@@ -40,6 +32,14 @@ type EditPlacementFormValues = z.infer<typeof ZProjectPlacementInput>;
|
||||
|
||||
export const EditPlacementForm = ({ project, isReadOnly }: EditPlacementProps) => {
|
||||
const { t } = useTranslate();
|
||||
|
||||
const placements = [
|
||||
{ name: t("common.bottom_right"), value: "bottomRight", disabled: false },
|
||||
{ name: t("common.top_right"), value: "topRight", disabled: false },
|
||||
{ name: t("common.top_left"), value: "topLeft", disabled: false },
|
||||
{ name: t("common.bottom_left"), value: "bottomLeft", disabled: false },
|
||||
{ name: t("common.centered_modal"), value: "center", disabled: false },
|
||||
];
|
||||
const form = useForm<EditPlacementFormValues>({
|
||||
defaultValues: {
|
||||
placement: project.placement,
|
||||
@@ -102,7 +102,7 @@ export const EditPlacementForm = ({ project, isReadOnly }: EditPlacementProps) =
|
||||
<Label
|
||||
htmlFor={placement.value}
|
||||
className={`text-slate-900 ${isReadOnly ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`}>
|
||||
{t(placement.name)}
|
||||
{placement.name}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -12,16 +12,16 @@ import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group"
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
|
||||
const placements = [
|
||||
{ name: "common.bottom_right", value: "bottomRight", disabled: false },
|
||||
{ name: "common.top_right", value: "topRight", disabled: false },
|
||||
{ name: "common.top_left", value: "topLeft", disabled: false },
|
||||
{ name: "common.bottom_left", value: "bottomLeft", disabled: false },
|
||||
{ name: "common.centered_modal", value: "center", disabled: false },
|
||||
];
|
||||
|
||||
export const ProjectLookSettingsLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
|
||||
const placements = [
|
||||
{ name: t("common.bottom_right"), value: "bottomRight", disabled: false },
|
||||
{ name: t("common.top_right"), value: "topRight", disabled: false },
|
||||
{ name: t("common.top_left"), value: "topLeft", disabled: false },
|
||||
{ name: t("common.bottom_left"), value: "bottomLeft", disabled: false },
|
||||
{ name: t("common.centered_modal"), value: "center", disabled: false },
|
||||
];
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
@@ -140,7 +140,7 @@ export const ProjectLookSettingsLoading = () => {
|
||||
className={cn(
|
||||
placement.disabled ? "cursor-not-allowed text-slate-500" : "text-slate-900"
|
||||
)}>
|
||||
{t(placement.name)}
|
||||
{placement.name}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const TemplateFilters = ({
|
||||
: "bg-white text-slate-700 hover:bg-slate-100 focus:scale-105 focus:bg-slate-100 focus:outline-none focus:ring-0",
|
||||
"rounded border border-slate-800 px-2 py-1 text-xs transition-all duration-150"
|
||||
)}>
|
||||
{t(filter.label)}
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ const getChannelTag = (channels: NonNullabeChannel[] | undefined, t: TFnType): s
|
||||
const labels = channels
|
||||
.map((channel) => {
|
||||
const label = getLabel(channel);
|
||||
if (label) return t(label);
|
||||
if (label) return label;
|
||||
return undefined;
|
||||
})
|
||||
.filter((label): label is string => !!label)
|
||||
@@ -78,12 +78,12 @@ export const TemplateTags = ({ template, selectedFilter }: TemplateTagsProps) =>
|
||||
// if user selects an industry e.g. eCommerce than the tag should not say "Multiple industries" anymore but "E-Commerce".
|
||||
if (selectedFilter[1] !== null) {
|
||||
const industry = getIndustryMapping(t).find((industry) => industry.value === selectedFilter[1]);
|
||||
if (industry) return t(industry.label);
|
||||
if (industry) return industry.label;
|
||||
}
|
||||
if (!industries || industries.length === 0) return undefined;
|
||||
return industries.length > 1
|
||||
? t("environments.surveys.templates.multiple_industries")
|
||||
: t(getIndustryMapping(t).find((industry) => industry.value === industries[0])?.label ?? "");
|
||||
: getIndustryMapping(t).find((industry) => industry.value === industries[0])?.label;
|
||||
};
|
||||
|
||||
const industryTag = useMemo(
|
||||
@@ -93,7 +93,7 @@ export const TemplateTags = ({ template, selectedFilter }: TemplateTagsProps) =>
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<div className={cn("rounded border px-1.5 py-0.5 text-xs", roleBasedStyling)}>{t(roleTag ?? "")}</div>
|
||||
<div className={cn("rounded border px-1.5 py-0.5 text-xs", roleBasedStyling)}>{roleTag}</div>
|
||||
{industryTag && (
|
||||
<div
|
||||
className={cn("rounded border border-slate-300 bg-slate-50 px-1.5 py-0.5 text-xs text-slate-500")}>
|
||||
|
||||
@@ -42,7 +42,7 @@ export const Placement = ({
|
||||
<div key={placement.value} className="flex items-center space-x-2 whitespace-nowrap">
|
||||
<RadioGroupItem id={placement.value} value={placement.value} disabled={placement.disabled} />
|
||||
<Label htmlFor={placement.value} className="text-slate-900">
|
||||
{t(placement.name)}
|
||||
{placement.name}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -166,9 +166,9 @@ export const RecontactOptionsCard = ({
|
||||
className="aria-checked:border-brand-dark mx-5 disabled:border-slate-400 aria-checked:border-2"
|
||||
/>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{t(option.name)}</p>
|
||||
<p className="font-semibold text-slate-700">{option.name}</p>
|
||||
|
||||
<p className="mt-2 text-xs font-normal text-slate-600">{t(option.description)}</p>
|
||||
<p className="mt-2 text-xs font-normal text-slate-600">{option.description}</p>
|
||||
</div>
|
||||
</Label>
|
||||
{option.id === "displaySome" && localSurvey.displayOption === "displaySome" && (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DropdownMenuItem } from "@/modules/ui/components/dropdown-menu";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { TSortOption } from "@formbricks/types/surveys/types";
|
||||
import { SortOption } from "./sort-option";
|
||||
|
||||
// Mock dependencies
|
||||
@@ -21,7 +20,7 @@ vi.mock("@tolgee/react", () => ({
|
||||
describe("SortOption", () => {
|
||||
const mockOption: TSortOption = {
|
||||
label: "test.sort.option",
|
||||
value: "testValue",
|
||||
value: "createdAt",
|
||||
};
|
||||
|
||||
const mockHandleSortChange = vi.fn();
|
||||
@@ -32,14 +31,14 @@ describe("SortOption", () => {
|
||||
});
|
||||
|
||||
test("renders correctly with the option label", () => {
|
||||
render(<SortOption option={mockOption} sortBy="otherValue" handleSortChange={mockHandleSortChange} />);
|
||||
render(<SortOption option={mockOption} sortBy="createdAt" handleSortChange={mockHandleSortChange} />);
|
||||
|
||||
expect(screen.getByText("test.sort.option")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("dropdown-menu-item")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("applies correct styling when option is selected", () => {
|
||||
render(<SortOption option={mockOption} sortBy="testValue" handleSortChange={mockHandleSortChange} />);
|
||||
render(<SortOption option={mockOption} sortBy="createdAt" handleSortChange={mockHandleSortChange} />);
|
||||
|
||||
const circleIndicator = screen.getByTestId("dropdown-menu-item").querySelector("span");
|
||||
expect(circleIndicator).toHaveClass("bg-brand-dark");
|
||||
@@ -47,11 +46,10 @@ describe("SortOption", () => {
|
||||
});
|
||||
|
||||
test("applies correct styling when option is not selected", () => {
|
||||
render(
|
||||
<SortOption option={mockOption} sortBy="differentValue" handleSortChange={mockHandleSortChange} />
|
||||
);
|
||||
render(<SortOption option={mockOption} sortBy="updatedAt" handleSortChange={mockHandleSortChange} />);
|
||||
|
||||
const circleIndicator = screen.getByTestId("dropdown-menu-item").querySelector("span");
|
||||
expect(circleIndicator).toHaveClass("border-white");
|
||||
expect(circleIndicator).not.toHaveClass("bg-brand-dark");
|
||||
expect(circleIndicator).not.toHaveClass("outline-brand-dark");
|
||||
});
|
||||
@@ -59,7 +57,7 @@ describe("SortOption", () => {
|
||||
test("calls handleSortChange when clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<SortOption option={mockOption} sortBy="otherValue" handleSortChange={mockHandleSortChange} />);
|
||||
render(<SortOption option={mockOption} sortBy="createdAt" handleSortChange={mockHandleSortChange} />);
|
||||
|
||||
await user.click(screen.getByTestId("dropdown-menu-item"));
|
||||
expect(mockHandleSortChange).toHaveBeenCalledTimes(1);
|
||||
@@ -67,7 +65,7 @@ describe("SortOption", () => {
|
||||
});
|
||||
|
||||
test("translates the option label", () => {
|
||||
render(<SortOption option={mockOption} sortBy="otherValue" handleSortChange={mockHandleSortChange} />);
|
||||
render(<SortOption option={mockOption} sortBy="createdAt" handleSortChange={mockHandleSortChange} />);
|
||||
|
||||
// The mock for useTranslate returns the key itself, so we're checking if translation was attempted
|
||||
expect(screen.getByText(mockOption.label)).toBeInTheDocument();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { DropdownMenuItem } from "@/modules/ui/components/dropdown-menu";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
|
||||
interface SortOptionProps {
|
||||
@@ -11,7 +10,6 @@ interface SortOptionProps {
|
||||
}
|
||||
|
||||
export const SortOption = ({ option, sortBy, handleSortChange }: SortOptionProps) => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={option.label}
|
||||
@@ -22,7 +20,7 @@ export const SortOption = ({ option, sortBy, handleSortChange }: SortOptionProps
|
||||
<div className="flex h-full w-full items-center space-x-2 px-2 py-1 hover:bg-slate-700">
|
||||
<span
|
||||
className={`h-4 w-4 rounded-full border ${sortBy === option.value ? "bg-brand-dark outline-brand-dark border-slate-900 outline" : "border-white"}`}></span>
|
||||
<p className="font-normal text-white">{t(option.label)}</p>
|
||||
<p className="font-normal text-white">{option.label}</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
|
||||
@@ -4,11 +4,6 @@ import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TFilterOption } from "@formbricks/types/surveys/types";
|
||||
import { SurveyFilterDropdown } from "./survey-filter-dropdown";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@tolgee/react", () => ({
|
||||
useTranslate: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
|
||||
// Mock UI components
|
||||
vi.mock("@/modules/ui/components/checkbox", () => ({
|
||||
Checkbox: ({ checked, className }) => (
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { TFilterOption } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -30,7 +29,6 @@ export const SurveyFilterDropdown = ({
|
||||
isOpen,
|
||||
toggleDropdown,
|
||||
}: SurveyFilterDropdownProps) => {
|
||||
const { t } = useTranslate();
|
||||
const triggerClasses = `surveyFilterDropdown min-w-auto h-8 rounded-md border border-slate-700 sm:px-2 cursor-pointer outline-none
|
||||
${selectedOptions.length > 0 ? "bg-slate-900 text-white" : "hover:bg-slate-900"}`;
|
||||
|
||||
@@ -56,7 +54,7 @@ export const SurveyFilterDropdown = ({
|
||||
checked={selectedOptions.includes(option.value)}
|
||||
className={`bg-white ${selectedOptions.includes(option.value) ? "bg-brand-dark border-none" : ""}`}
|
||||
/>
|
||||
<p className="font-normal text-white">{t(option.label)}</p>
|
||||
<p className="font-normal text-white">{option.label}</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
@@ -183,7 +183,7 @@ export const SurveyFilters = ({
|
||||
<span className="text-sm">
|
||||
{t("common.sort_by")}:{" "}
|
||||
{getSortOptions(t).find((option) => option.value === sortBy)
|
||||
? t(getSortOptions(t).find((option) => option.value === sortBy)?.label ?? "")
|
||||
? getSortOptions(t).find((option) => option.value === sortBy)?.label
|
||||
: ""}
|
||||
</span>
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4" />
|
||||
|
||||
@@ -91,7 +91,7 @@ export const SelectedRowSettings = <T,>({
|
||||
<>
|
||||
<div className="bg-primary flex items-center gap-x-2 rounded-md p-1 px-2 text-xs text-white">
|
||||
<div className="lowercase">
|
||||
{selectedRowCount} {t(`common.${type}s`)} {t("common.selected")}
|
||||
{`${selectedRowCount} ${type === "response" ? t("common.responses") : t("common.contacts")} ${t("common.selected")}`}
|
||||
</div>
|
||||
<Separator />
|
||||
<Button
|
||||
@@ -146,7 +146,7 @@ export const SelectedRowSettings = <T,>({
|
||||
<DeleteDialog
|
||||
open={isDeleteDialogOpen}
|
||||
setOpen={setIsDeleteDialogOpen}
|
||||
deleteWhat={t(`common.${type}`)}
|
||||
deleteWhat={type === "response" ? t("common.responses") : t("common.contacts")}
|
||||
onDelete={handleDelete}
|
||||
isDeleting={isDeleting}
|
||||
text={deleteDialogText}
|
||||
|
||||
@@ -274,7 +274,7 @@ export const InputCombobox = ({
|
||||
)}
|
||||
<CommandList className="m-1">
|
||||
<CommandEmpty className="mx-2 my-0 text-xs font-semibold text-slate-500">
|
||||
{emptyDropdownText ? t(emptyDropdownText) : t("environments.surveys.edit.no_option_found")}
|
||||
{emptyDropdownText ?? t("environments.surveys.edit.no_option_found")}
|
||||
</CommandEmpty>
|
||||
{options && options.length > 0 && (
|
||||
<CommandGroup>
|
||||
|
||||
18
packages/js-core/.prettierrc.cjs
Normal file
18
packages/js-core/.prettierrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
// packages/js-core/.prettierrc.cjs
|
||||
const base = require("../config-prettier/prettier-preset");
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
plugins: [
|
||||
"@trivago/prettier-plugin-sort-imports",
|
||||
],
|
||||
|
||||
importOrder: [
|
||||
"^vitest$", // 1️⃣ vitest first
|
||||
"<THIRD_PARTY_MODULES>", // 2️⃣ then other externals
|
||||
"^@/.*$", // 3️⃣ then anything under @/
|
||||
"^\\.\\/__mocks__\\/.*$", // 4️⃣ then anything under ./__mocks__/
|
||||
"^[./]", // 5️⃣ finally all other relative imports
|
||||
],
|
||||
importOrderSortSpecifiers: true,
|
||||
};
|
||||
@@ -44,6 +44,7 @@
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@vitest/coverage-v8": "3.1.3",
|
||||
"terser": "5.39.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { wrapThrowsAsync } from "@/lib/common/utils";
|
||||
import { ApiResponse, ApiSuccessResponse, CreateOrUpdateUserResponse } from "@/types/api";
|
||||
import { TEnvironmentState } from "@/types/config";
|
||||
import { ApiErrorResponse, Result, err, ok } from "@/types/error";
|
||||
import { type ApiResponse, type ApiSuccessResponse, type CreateOrUpdateUserResponse } from "@/types/api";
|
||||
import { type TEnvironmentState } from "@/types/config";
|
||||
import { type ApiErrorResponse, type Result, err, ok } from "@/types/error";
|
||||
|
||||
export const makeRequest = async <T>(
|
||||
appUrl: string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// api.test.ts
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ApiClient, makeRequest } from "@/lib/common/api";
|
||||
import type { TEnvironmentState } from "@/types/config";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// Mock fetch
|
||||
const mockFetch = vi.fn();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { CommandQueue, CommandType } from "@/lib/common/command-queue";
|
||||
import { checkSetup } from "@/lib/common/status";
|
||||
import { UpdateQueue } from "@/lib/user/update-queue";
|
||||
import { type Result } from "@/types/error";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// Mock the setup module so we can control checkSetup()
|
||||
vi.mock("@/lib/common/status", () => ({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method -- required for mocking */
|
||||
// config.test.ts
|
||||
import { mockConfig } from "./__mocks__/config.mock";
|
||||
import { type Mock, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { JS_LOCAL_STORAGE_KEY } from "@/lib/common/constants";
|
||||
import type { TConfig, TConfigUpdateInput } from "@/types/config";
|
||||
import { type Mock, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { mockConfig } from "./__mocks__/config.mock";
|
||||
|
||||
// Define mocks outside of any describe block
|
||||
const getItemMock = localStorage.getItem as unknown as Mock;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// event-listeners.test.ts
|
||||
import { type Mock, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import {
|
||||
addCleanupEventListeners,
|
||||
addEventListeners,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
import * as environmentState from "@/lib/environment/state";
|
||||
import * as pageUrlEventListeners from "@/lib/survey/no-code-action";
|
||||
import * as userState from "@/lib/user/state";
|
||||
import { type Mock, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// 1) Mock all the imported dependencies
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file-upload.test.ts
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { StorageAPI } from "@/lib/common/file-upload";
|
||||
import type { TUploadFileConfig } from "@/types/storage";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// A global fetch mock so we can capture fetch calls.
|
||||
// Alternatively, use `vi.stubGlobal("fetch", ...)`.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// logger.test.ts
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// adjust import path as needed
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
|
||||
describe("Logger", () => {
|
||||
let logger: Logger;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method -- required for testing */
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { JS_LOCAL_STORAGE_KEY } from "@/lib/common/constants";
|
||||
import { addCleanupEventListeners, addEventListeners } from "@/lib/common/event-listeners";
|
||||
@@ -6,10 +7,10 @@ import { Logger } from "@/lib/common/logger";
|
||||
import { handleErrorOnFirstSetup, setup, tearDown } from "@/lib/common/setup";
|
||||
import { setIsSetup } from "@/lib/common/status";
|
||||
import { filterSurveys, isNowExpired } from "@/lib/common/utils";
|
||||
import type * as Utils from "@/lib/common/utils";
|
||||
import { fetchEnvironmentState } from "@/lib/environment/state";
|
||||
import { DEFAULT_USER_STATE_NO_USER_ID } from "@/lib/user/state";
|
||||
import { sendUpdatesToBackend } from "@/lib/user/update";
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const setItemMock = localStorage.setItem as unknown as Mock;
|
||||
|
||||
@@ -50,8 +51,9 @@ vi.mock("@/lib/environment/state", () => ({
|
||||
|
||||
// 6) Mock filterSurveys
|
||||
vi.mock("@/lib/common/utils", async (importOriginal) => {
|
||||
const originalModule = await importOriginal<typeof Utils>();
|
||||
return {
|
||||
...(await importOriginal<typeof import("@/lib/common/utils")>()),
|
||||
...originalModule,
|
||||
filterSurveys: vi.fn(),
|
||||
isNowExpired: vi.fn(),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { checkSetup, getIsSetup, setIsSetup } from "@/lib/common/status";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { checkSetup, getIsSetup, setIsSetup } from "@/lib/common/status";
|
||||
|
||||
describe("checkSetup()", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TimeoutStack } from "@/lib/common/timeout-stack";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TimeoutStack } from "@/lib/common/timeout-stack";
|
||||
|
||||
// Using vitest, we don't need to manually declare globals
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// utils.test.ts
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { mockProjectId, mockSurveyId } from "@/lib/common/tests/__mocks__/config.mock";
|
||||
import {
|
||||
checkUrlMatch,
|
||||
@@ -26,7 +27,6 @@ import type {
|
||||
TUserState,
|
||||
} from "@/types/config";
|
||||
import { type TActionClassNoCodeConfig, type TActionClassPageUrlRule } from "@/types/survey";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const mockSurveyId1 = "e3kxlpnzmdp84op9qzxl9olj";
|
||||
const mockSurveyId2 = "qo9rwjmms42hoy3k85fp8vgu";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// state.test.ts
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ApiClient } from "@/lib/common/api";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
fetchEnvironmentState,
|
||||
} from "@/lib/environment/state";
|
||||
import type { TEnvironmentState } from "@/types/config";
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// Mock the ApiClient so we can control environment.getEnvironmentState
|
||||
vi.mock("@/lib/common/api", () => ({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { trackAction, trackCodeAction, trackNoCodeAction } from "@/lib/survey/action";
|
||||
import { SurveyStore } from "@/lib/survey/store";
|
||||
import { triggerSurvey } from "@/lib/survey/widget";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
Config: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method -- mock functions are unbound */
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { checkSetup } from "@/lib/common/status";
|
||||
import { TimeoutStack } from "@/lib/common/timeout-stack";
|
||||
@@ -17,8 +18,7 @@ import {
|
||||
removeScrollDepthListener,
|
||||
} from "@/lib/survey/no-code-action";
|
||||
import { setIsSurveyRunning } from "@/lib/survey/widget";
|
||||
import { TActionClassNoCodeConfig } from "@/types/survey";
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { type TActionClassNoCodeConfig } from "@/types/survey";
|
||||
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
Config: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mockSurveyId, mockSurveyName } from "@/lib/survey/tests/__mocks__/store.mock";
|
||||
import { SurveyStore } from "@/lib/survey/store";
|
||||
import type { TEnvironmentStateSurvey } from "@/types/config";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { SurveyStore } from "@/lib/survey/store";
|
||||
import { mockSurveyId, mockSurveyName } from "@/lib/survey/tests/__mocks__/store.mock";
|
||||
import type { TEnvironmentStateSurvey } from "@/types/config";
|
||||
|
||||
describe("SurveyStore", () => {
|
||||
let store: SurveyStore;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { mockSurvey } from "@/lib/survey/tests/__mocks__/widget.mock";
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { filterSurveys, getLanguageCode, shouldDisplayBasedOnPercentage } from "@/lib/common/utils";
|
||||
import { mockSurvey } from "@/lib/survey/tests/__mocks__/widget.mock";
|
||||
import * as widget from "@/lib/survey/widget";
|
||||
import { type TEnvironmentStateSurvey } from "@/types/config";
|
||||
import { type Mock, type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
Config: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { setAttributes } from "@/lib/user/attribute";
|
||||
import { UpdateQueue } from "@/lib/user/update-queue";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
export const mockAttributes = {
|
||||
name: "John Doe",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { addUserStateExpiryCheckListener, clearUserStateExpiryCheckListener } from "@/lib/user/state";
|
||||
import { type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const mockUserId = "user_123";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { mockAttributes, mockUserId1, mockUserId2 } from "@/lib/user/tests/__mocks__/update-queue.mock";
|
||||
import { type Mock, type MockInstance, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { mockAttributes, mockUserId1, mockUserId2 } from "@/lib/user/tests/__mocks__/update-queue.mock";
|
||||
import { sendUpdates } from "@/lib/user/update";
|
||||
import { UpdateQueue } from "@/lib/user/update-queue";
|
||||
import { type Mock, type MockInstance, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { type Mock, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ApiClient } from "@/lib/common/api";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import {
|
||||
mockAppUrl,
|
||||
mockAttributes,
|
||||
mockEnvironmentId,
|
||||
mockUserId,
|
||||
} from "@/lib/user/tests/__mocks__/update.mock";
|
||||
import { ApiClient } from "@/lib/common/api";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { sendUpdates, sendUpdatesToBackend } from "@/lib/user/update";
|
||||
import { type TUpdates } from "@/types/config";
|
||||
import { type Mock, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
Config: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type Mock, type MockInstance, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { Config } from "@/lib/common/config";
|
||||
import { Logger } from "@/lib/common/logger";
|
||||
import { setup, tearDown } from "@/lib/common/setup";
|
||||
import { UpdateQueue } from "@/lib/user/update-queue";
|
||||
import { logout, setUserId } from "@/lib/user/user";
|
||||
import { type Mock, type MockInstance, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@/lib/common/config", () => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TUserState } from "@/types/config";
|
||||
import { ApiErrorResponse } from "@/types/error";
|
||||
import { type TUserState } from "@/types/config";
|
||||
import { type ApiErrorResponse } from "@/types/error";
|
||||
|
||||
export type ApiResponse = ApiSuccessResponse | ApiErrorResponse;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FILE_PICK_EVENT } from "@/lib/constants";
|
||||
import { getOriginalFileNameFromUrl } from "@/lib/storage";
|
||||
import { getMimeType, isFulfilled, isRejected } from "@/lib/utils";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||
import { type JSXInternal } from "preact/src/jsx";
|
||||
import { type TAllowedFileExtension } from "@formbricks/types/common";
|
||||
import { type TJsFileUploadParams } from "@formbricks/types/js";
|
||||
@@ -36,34 +36,39 @@ export function FileInput({
|
||||
const [parent] = useAutoAnimate();
|
||||
|
||||
// Helper function to filter duplicate files
|
||||
const filterDuplicateFiles = <T extends { name: string }>(
|
||||
files: T[],
|
||||
checkAgainstSelected: boolean = true
|
||||
): {
|
||||
filteredFiles: T[];
|
||||
duplicateFiles: T[];
|
||||
} => {
|
||||
const existingFileNames = fileUrls ? fileUrls.map(getOriginalFileNameFromUrl) : [];
|
||||
const filterDuplicateFiles = useCallback(
|
||||
<T extends { name: string }>(
|
||||
files: T[],
|
||||
checkAgainstSelected: boolean = true
|
||||
): {
|
||||
filteredFiles: T[];
|
||||
duplicateFiles: T[];
|
||||
} => {
|
||||
const existingFileNames = fileUrls ? fileUrls.map(getOriginalFileNameFromUrl) : [];
|
||||
|
||||
const duplicateFiles = files.filter(
|
||||
(file) =>
|
||||
existingFileNames.includes(file.name) ||
|
||||
(checkAgainstSelected && selectedFiles.some((selectedFile) => selectedFile.name === file.name))
|
||||
);
|
||||
const duplicateFiles = files.filter(
|
||||
(file) =>
|
||||
existingFileNames.includes(file.name) ||
|
||||
(checkAgainstSelected && selectedFiles.some((selectedFile) => selectedFile.name === file.name))
|
||||
);
|
||||
|
||||
const filteredFiles = files.filter(
|
||||
(file) =>
|
||||
!existingFileNames.includes(file.name) &&
|
||||
(!checkAgainstSelected || !selectedFiles.some((selectedFile) => selectedFile.name === file.name))
|
||||
);
|
||||
const filteredFiles = files.filter(
|
||||
(file) =>
|
||||
!existingFileNames.includes(file.name) &&
|
||||
(!checkAgainstSelected || !selectedFiles.some((selectedFile) => selectedFile.name === file.name))
|
||||
);
|
||||
|
||||
if (duplicateFiles.length > 0) {
|
||||
const duplicateNames = duplicateFiles.map((file) => file.name).join(", ");
|
||||
alert(`The following files are already uploaded: ${duplicateNames}. Duplicate files are not allowed.`);
|
||||
}
|
||||
if (duplicateFiles.length > 0) {
|
||||
const duplicateNames = duplicateFiles.map((file) => file.name).join(", ");
|
||||
alert(
|
||||
`The following files are already uploaded: ${duplicateNames}. Duplicate files are not allowed.`
|
||||
);
|
||||
}
|
||||
|
||||
return { filteredFiles, duplicateFiles };
|
||||
};
|
||||
return { filteredFiles, duplicateFiles };
|
||||
},
|
||||
[fileUrls, selectedFiles]
|
||||
);
|
||||
|
||||
// Listen for the native file-upload event dispatched via window.formbricksSurveys.onFilePick
|
||||
useEffect(() => {
|
||||
@@ -131,7 +136,15 @@ export function FileInput({
|
||||
return () => {
|
||||
window.removeEventListener(FILE_PICK_EVENT, handleNativeFileUpload as unknown as EventListener);
|
||||
};
|
||||
}, [allowedFileExtensions, fileUrls, maxSizeInMB, onFileUpload, onUploadCallback, surveyId]);
|
||||
}, [
|
||||
allowedFileExtensions,
|
||||
fileUrls,
|
||||
maxSizeInMB,
|
||||
onFileUpload,
|
||||
onUploadCallback,
|
||||
surveyId,
|
||||
filterDuplicateFiles,
|
||||
]);
|
||||
|
||||
const validateFileSize = async (file: File): Promise<boolean> => {
|
||||
if (maxSizeInMB) {
|
||||
|
||||
@@ -27,7 +27,7 @@ export function ProgressBar({ survey, questionId }: ProgressBarProps) {
|
||||
const elementIdx = calculateElementIdx(survey, idx, totalCards);
|
||||
return elementIdx / totalCards;
|
||||
},
|
||||
[survey]
|
||||
[survey, endingCardIds.length]
|
||||
);
|
||||
|
||||
const progressArray = useMemo(() => {
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -675,6 +675,9 @@ importers:
|
||||
'@formbricks/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../config-eslint
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2(prettier@3.5.3)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 3.1.3
|
||||
version: 3.1.3(vitest@3.1.3(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.0))
|
||||
|
||||
Reference in New Issue
Block a user