fix: empty container in template UI (#6556)

This commit is contained in:
Dhruwang Jariwala
2025-09-18 12:15:20 +05:30
committed by GitHub
parent bf4c6238d5
commit c3c06eb309
12 changed files with 88 additions and 198 deletions

View File

@@ -1,6 +1,5 @@
import { describe, expect, test } from "vitest";
import { TShuffleOption, TSurveyLogic, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TTemplateRole } from "@formbricks/types/templates";
import {
buildCTAQuestion,
buildConsentQuestion,

View File

@@ -19,7 +19,7 @@ import {
TSurveyRatingQuestion,
TSurveyWelcomeCard,
} from "@formbricks/types/surveys/types";
import { TTemplate } from "@formbricks/types/templates";
import { TTemplate, TTemplateRole } from "@formbricks/types/templates";
const getDefaultButtonLabel = (label: string | undefined, t: TFnType) =>
createI18nString(label || t("common.next"), []);
@@ -391,6 +391,7 @@ export const buildSurvey = (
name: string;
industries: ("eCommerce" | "saas" | "other")[];
channels: ("link" | "app" | "website")[];
role: TTemplateRole;
description: string;
questions: TSurveyQuestion[];
endings?: TSurveyEnding[];
@@ -403,6 +404,7 @@ export const buildSurvey = (
name: config.name,
industries: config.industries,
channels: config.channels,
role: config.role,
description: config.description,
preset: {
...localSurvey,

View File

@@ -24,6 +24,7 @@ const cartAbandonmentSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.card_abandonment_survey"),
role: "productManager",
industries: ["eCommerce"],
channels: ["app", "website", "link"],
description: t("templates.card_abandonment_survey_description"),
@@ -124,6 +125,7 @@ const siteAbandonmentSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.site_abandonment_survey"),
role: "productManager",
industries: ["eCommerce"],
channels: ["app", "website"],
description: t("templates.site_abandonment_survey_description"),
@@ -221,6 +223,7 @@ const productMarketFitSuperhuman = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.product_market_fit_superhuman"),
role: "productManager",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.product_market_fit_superhuman_description"),
@@ -295,6 +298,7 @@ const onboardingSegmentation = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.onboarding_segmentation"),
role: "productManager",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.onboarding_segmentation_description"),
@@ -358,6 +362,7 @@ const churnSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.churn_survey"),
role: "sales",
industries: ["saas", "eCommerce", "other"],
channels: ["app", "link"],
description: t("templates.churn_survey_description"),
@@ -447,6 +452,7 @@ const earnedAdvocacyScore = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.earned_advocacy_score_name"),
role: "customerSuccess",
industries: ["saas", "eCommerce", "other"],
channels: ["app", "link"],
description: t("templates.earned_advocacy_score_description"),
@@ -519,6 +525,7 @@ const usabilityScoreRatingSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.usability_score_name"),
role: "customerSuccess",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.usability_rating_description"),
@@ -644,6 +651,7 @@ const improveTrialConversion = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.improve_trial_conversion_name"),
role: "sales",
industries: ["saas"],
channels: ["link", "app"],
description: t("templates.improve_trial_conversion_description"),
@@ -745,6 +753,7 @@ const reviewPrompt = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.review_prompt_name"),
role: "marketing",
industries: ["saas", "eCommerce", "other"],
channels: ["link", "app"],
description: t("templates.review_prompt_description"),
@@ -823,6 +832,7 @@ const interviewPrompt = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.interview_prompt_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.interview_prompt_description"),
@@ -850,6 +860,7 @@ const improveActivationRate = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.improve_activation_rate_name"),
role: "productManager",
industries: ["saas"],
channels: ["link"],
description: t("templates.improve_activation_rate_description"),
@@ -940,6 +951,7 @@ const employeeSatisfaction = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.employee_satisfaction_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["app", "link"],
description: t("templates.employee_satisfaction_description"),
@@ -1017,6 +1029,7 @@ const uncoverStrengthsAndWeaknesses = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.uncover_strengths_and_weaknesses_name"),
role: "productManager",
industries: ["saas", "other"],
channels: ["app", "link"],
description: t("templates.uncover_strengths_and_weaknesses_description"),
@@ -1070,6 +1083,7 @@ const productMarketFitShort = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.product_market_fit_short_name"),
role: "productManager",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.product_market_fit_short_description"),
@@ -1106,6 +1120,7 @@ const marketAttribution = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.market_attribution_name"),
role: "marketing",
industries: ["saas", "eCommerce"],
channels: ["website", "app", "link"],
description: t("templates.market_attribution_description"),
@@ -1136,6 +1151,7 @@ const changingSubscriptionExperience = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.changing_subscription_experience_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.changing_subscription_experience_description"),
@@ -1178,6 +1194,7 @@ const identifyCustomerGoals = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.identify_customer_goals_name"),
role: "productManager",
industries: ["saas", "other"],
channels: ["app", "website"],
description: t("templates.identify_customer_goals_description"),
@@ -1207,6 +1224,7 @@ const featureChaser = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.feature_chaser_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.feature_chaser_description"),
@@ -1245,6 +1263,7 @@ const fakeDoorFollowUp = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.fake_door_follow_up_name"),
role: "productManager",
industries: ["saas", "eCommerce"],
channels: ["app", "website"],
description: t("templates.fake_door_follow_up_description"),
@@ -1288,6 +1307,7 @@ const feedbackBox = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.feedback_box_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.feedback_box_description"),
@@ -1357,6 +1377,7 @@ const integrationSetupSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.integration_setup_survey_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.integration_setup_survey_description"),
@@ -1429,6 +1450,7 @@ const newIntegrationSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.new_integration_survey_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.new_integration_survey_description"),
@@ -1460,6 +1482,7 @@ const docsFeedback = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.docs_feedback_name"),
role: "productManager",
industries: ["saas"],
channels: ["app", "website", "link"],
description: t("templates.docs_feedback_description"),
@@ -1499,6 +1522,7 @@ const nps = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.nps_name"),
role: "customerSuccess",
industries: ["saas", "eCommerce", "other"],
channels: ["app", "link", "website"],
description: t("templates.nps_description"),
@@ -1539,6 +1563,7 @@ const customerSatisfactionScore = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.csat_name"),
role: "customerSuccess",
industries: ["saas", "eCommerce", "other"],
channels: ["app", "link", "website"],
description: t("templates.csat_description"),
@@ -1707,6 +1732,7 @@ const collectFeedback = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.collect_feedback_name"),
role: "productManager",
industries: ["other", "eCommerce"],
channels: ["website", "link"],
description: t("templates.collect_feedback_description"),
@@ -1853,6 +1879,7 @@ const identifyUpsellOpportunities = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.identify_upsell_opportunities_name"),
role: "sales",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.identify_upsell_opportunities_description"),
@@ -1882,6 +1909,7 @@ const prioritizeFeatures = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.prioritize_features_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.prioritize_features_description"),
@@ -1934,6 +1962,7 @@ const gaugeFeatureSatisfaction = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.gauge_feature_satisfaction_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.gauge_feature_satisfaction_description"),
@@ -1967,6 +1996,7 @@ const marketSiteClarity = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.market_site_clarity_name"),
role: "marketing",
industries: ["saas", "eCommerce", "other"],
channels: ["website"],
description: t("templates.market_site_clarity_description"),
@@ -2008,6 +2038,7 @@ const customerEffortScore = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.customer_effort_score_name"),
role: "productManager",
industries: ["saas"],
channels: ["app"],
description: t("templates.customer_effort_score_description"),
@@ -2039,6 +2070,7 @@ const careerDevelopmentSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.career_development_survey_name"),
role: "productManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.career_development_survey_description"),
@@ -2125,6 +2157,7 @@ const professionalDevelopmentSurvey = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.professional_development_survey_name"),
role: "productManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.professional_development_survey_description"),
@@ -2212,6 +2245,7 @@ const rateCheckoutExperience = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.rate_checkout_experience_name"),
role: "productManager",
industries: ["eCommerce"],
channels: ["website", "app"],
description: t("templates.rate_checkout_experience_description"),
@@ -2288,6 +2322,7 @@ const measureSearchExperience = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.measure_search_experience_name"),
role: "productManager",
industries: ["saas", "eCommerce"],
channels: ["app", "website"],
description: t("templates.measure_search_experience_description"),
@@ -2364,6 +2399,7 @@ const evaluateContentQuality = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.evaluate_content_quality_name"),
role: "marketing",
industries: ["other"],
channels: ["website"],
description: t("templates.evaluate_content_quality_description"),
@@ -2441,6 +2477,7 @@ const measureTaskAccomplishment = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.measure_task_accomplishment_name"),
role: "productManager",
industries: ["saas"],
channels: ["app", "website"],
description: t("templates.measure_task_accomplishment_description"),
@@ -2623,6 +2660,7 @@ const identifySignUpBarriers = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.identify_sign_up_barriers_name"),
role: "marketing",
industries: ["saas", "eCommerce", "other"],
channels: ["website"],
description: t("templates.identify_sign_up_barriers_description"),
@@ -2774,6 +2812,7 @@ const buildProductRoadmap = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.build_product_roadmap_name"),
role: "productManager",
industries: ["saas"],
channels: ["app", "link"],
description: t("templates.build_product_roadmap_description"),
@@ -2808,6 +2847,7 @@ const understandPurchaseIntention = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.understand_purchase_intention_name"),
role: "sales",
industries: ["eCommerce"],
channels: ["website", "link", "app"],
description: t("templates.understand_purchase_intention_description"),
@@ -2863,6 +2903,7 @@ const improveNewsletterContent = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.improve_newsletter_content_name"),
role: "marketing",
industries: ["eCommerce", "saas", "other"],
channels: ["link"],
description: t("templates.improve_newsletter_content_description"),
@@ -2953,6 +2994,7 @@ const evaluateAProductIdea = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.evaluate_a_product_idea_name"),
role: "productManager",
industries: ["saas", "other"],
channels: ["link", "app"],
description: t("templates.evaluate_a_product_idea_description"),
@@ -3055,6 +3097,7 @@ const understandLowEngagement = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.understand_low_engagement_name"),
role: "productManager",
industries: ["saas"],
channels: ["link"],
description: t("templates.understand_low_engagement_description"),
@@ -3140,6 +3183,7 @@ const employeeWellBeing = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.employee_well_being_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.employee_well_being_description"),
@@ -3189,6 +3233,7 @@ const longTermRetentionCheckIn = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.long_term_retention_check_in_name"),
role: "peopleManager",
industries: ["saas", "other"],
channels: ["app", "link"],
description: t("templates.long_term_retention_check_in_description"),
@@ -3297,6 +3342,7 @@ const professionalDevelopmentGrowth = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.professional_development_growth_survey_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.professional_development_growth_survey_description"),
@@ -3346,6 +3392,7 @@ const recognitionAndReward = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.recognition_and_reward_survey_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.recognition_and_reward_survey_description"),
@@ -3394,6 +3441,7 @@ const alignmentAndEngagement = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.alignment_and_engagement_survey_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.alignment_and_engagement_survey_description"),
@@ -3442,6 +3490,7 @@ const supportiveWorkCulture = (t: TFnType): TTemplate => {
return buildSurvey(
{
name: t("templates.supportive_work_culture_survey_name"),
role: "peopleManager",
industries: ["saas", "eCommerce", "other"],
channels: ["link"],
description: t("templates.supportive_work_culture_survey_description"),

View File

@@ -32,13 +32,7 @@ describe("TemplateFilters", () => {
test("renders all filter categories and options", () => {
const setSelectedFilter = vi.fn();
render(
<TemplateFilters
selectedFilter={[null, null, null]}
setSelectedFilter={setSelectedFilter}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateFilters selectedFilter={[null, null, null]} setSelectedFilter={setSelectedFilter} />);
expect(screen.getByText("environments.surveys.templates.all_channels")).toBeInTheDocument();
expect(screen.getByText("environments.surveys.templates.all_industries")).toBeInTheDocument();
@@ -54,13 +48,7 @@ describe("TemplateFilters", () => {
const setSelectedFilter = vi.fn();
const user = userEvent.setup();
render(
<TemplateFilters
selectedFilter={[null, null, null]}
setSelectedFilter={setSelectedFilter}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateFilters selectedFilter={[null, null, null]} setSelectedFilter={setSelectedFilter} />);
await user.click(screen.getByText("environments.surveys.templates.channel1"));
expect(setSelectedFilter).toHaveBeenCalledWith(["channel1", null, null]);
@@ -74,11 +62,7 @@ describe("TemplateFilters", () => {
const user = userEvent.setup();
render(
<TemplateFilters
selectedFilter={["link", "app", "website"]}
setSelectedFilter={setSelectedFilter}
prefilledFilters={[null, null, null]}
/>
<TemplateFilters selectedFilter={["link", "app", "website"]} setSelectedFilter={setSelectedFilter} />
);
await user.click(screen.getByText("environments.surveys.templates.all_channels"));
@@ -93,7 +77,6 @@ describe("TemplateFilters", () => {
selectedFilter={[null, null, null]}
setSelectedFilter={setSelectedFilter}
templateSearch="search term"
prefilledFilters={[null, null, null]}
/>
);
@@ -102,20 +85,4 @@ describe("TemplateFilters", () => {
expect(button).toBeDisabled();
});
});
test("does not render filter categories that are prefilled", () => {
const setSelectedFilter = vi.fn();
render(
<TemplateFilters
selectedFilter={["link", null, null]}
setSelectedFilter={setSelectedFilter}
prefilledFilters={["link", null, null]}
/>
);
expect(screen.queryByText("environments.surveys.templates.all_channels")).not.toBeInTheDocument();
expect(screen.getByText("environments.surveys.templates.all_industries")).toBeInTheDocument();
expect(screen.getByText("environments.surveys.templates.all_roles")).toBeInTheDocument();
});
});

View File

@@ -9,14 +9,12 @@ interface TemplateFiltersProps {
selectedFilter: TTemplateFilter[];
setSelectedFilter: (filter: TTemplateFilter[]) => void;
templateSearch?: string;
prefilledFilters: TTemplateFilter[];
}
export const TemplateFilters = ({
selectedFilter,
setSelectedFilter,
templateSearch,
prefilledFilters,
}: TemplateFiltersProps) => {
const { t } = useTranslate();
const handleFilterSelect = (filterValue: TTemplateFilter, index: number) => {
@@ -31,7 +29,6 @@ export const TemplateFilters = ({
return (
<div className="mb-6 gap-3">
{allFilters.map((filters, index) => {
if (prefilledFilters[index] !== null) return;
return (
<div key={filters[0]?.value || index} className="mt-2 flex flex-wrap gap-1 last:border-r-0">
<button

View File

@@ -102,41 +102,20 @@ describe("TemplateList", () => {
});
test("renders correctly with default props", () => {
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateList userId="user-id" environmentId="env-id" project={mockProject} />);
expect(screen.getByText("Start from scratch")).toBeInTheDocument();
});
test("renders filters when showFilters is true", () => {
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
showFilters={true}
/>
);
render(<TemplateList userId="user-id" environmentId="env-id" project={mockProject} showFilters={true} />);
expect(screen.queryByTestId("template-filters")).toBeInTheDocument();
});
test("doesn't render filters when showFilters is false", () => {
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
showFilters={false}
/>
<TemplateList userId="user-id" environmentId="env-id" project={mockProject} showFilters={false} />
);
expect(screen.queryByTestId("template-filters")).not.toBeInTheDocument();
@@ -150,7 +129,6 @@ describe("TemplateList", () => {
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
templateSearch="Template 1"
/>
);
@@ -167,7 +145,6 @@ describe("TemplateList", () => {
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
onTemplateClick={onTemplateClickMock}
noPreview={true}
/>
@@ -186,14 +163,7 @@ describe("TemplateList", () => {
const user = userEvent.setup();
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateList userId="user-id" environmentId="env-id" project={mockProject} />);
// First select the template
const selectButton = screen.getAllByText("Select")[0];
@@ -220,14 +190,7 @@ describe("TemplateList", () => {
const user = userEvent.setup();
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateList userId="user-id" environmentId="env-id" project={mockProject} />);
// First select the template
const selectButton = screen.getAllByText("Select")[0];
@@ -250,12 +213,7 @@ describe("TemplateList", () => {
};
const { rerender } = render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mobileProject as Project}
prefilledFilters={[null, null, null]}
/>
<TemplateList userId="user-id" environmentId="env-id" project={mobileProject as Project} />
);
// Test with no channel config
@@ -264,14 +222,7 @@ describe("TemplateList", () => {
config: {},
};
rerender(
<TemplateList
userId="user-id"
environmentId="env-id"
project={noChannelProject as Project}
prefilledFilters={[null, null, null]}
/>
);
rerender(<TemplateList userId="user-id" environmentId="env-id" project={noChannelProject as Project} />);
expect(screen.getByText("Template 1")).toBeInTheDocument();
});
@@ -279,14 +230,7 @@ describe("TemplateList", () => {
test("development mode shows templates correctly", () => {
vi.stubEnv("NODE_ENV", "development");
render(
<TemplateList
userId="user-id"
environmentId="env-id"
project={mockProject}
prefilledFilters={[null, null, null]}
/>
);
render(<TemplateList userId="user-id" environmentId="env-id" project={mockProject} />);
expect(screen.getByText("Template 1")).toBeInTheDocument();
expect(screen.getByText("Template 2")).toBeInTheDocument();

View File

@@ -21,7 +21,6 @@ interface TemplateListProps {
project: Project;
templateSearch?: string;
showFilters?: boolean;
prefilledFilters: TTemplateFilter[];
onTemplateClick?: (template: TTemplate) => void;
noPreview?: boolean; // single click to create survey
}
@@ -32,7 +31,6 @@ export const TemplateList = ({
environmentId,
showFilters = true,
templateSearch,
prefilledFilters,
onTemplateClick = () => {},
noPreview,
}: TemplateListProps) => {
@@ -40,7 +38,7 @@ export const TemplateList = ({
const router = useRouter();
const [activeTemplate, setActiveTemplate] = useState<TTemplate | null>(null);
const [loading, setLoading] = useState(false);
const [selectedFilter, setSelectedFilter] = useState<TTemplateFilter[]>(prefilledFilters);
const [selectedFilter, setSelectedFilter] = useState<TTemplateFilter[]>([null, null, null]);
const surveyType: TSurveyType = useMemo(() => {
if (project.config.channel) {
if (project.config.channel === "website") {
@@ -111,7 +109,6 @@ export const TemplateList = ({
selectedFilter={selectedFilter}
setSelectedFilter={setSelectedFilter}
templateSearch={templateSearch}
prefilledFilters={prefilledFilters}
/>
)}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">

View File

@@ -5,7 +5,6 @@ import { Session } from "next-auth";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { TEnvironment } from "@formbricks/types/environment";
import { TProject } from "@formbricks/types/project";
import { TTemplateRole } from "@formbricks/types/templates";
import { SurveysPage } from "./page";
// Mock all dependencies
@@ -53,19 +52,16 @@ vi.mock("@/modules/survey/list/lib/survey", () => ({
}));
vi.mock("@/modules/survey/templates/components/template-container", () => ({
TemplateContainerWithPreview: vi.fn(
({ userId, environment, project, prefilledFilters, isTemplatePage }) => (
<div
data-testid="template-container"
data-user-id={userId}
data-environment-id={environment.id}
data-project-id={project.id}
data-prefilled-filters={JSON.stringify(prefilledFilters)}
data-is-template-page={isTemplatePage}>
Template Container
</div>
)
),
TemplateContainerWithPreview: vi.fn(({ userId, environment, project, isTemplatePage }) => (
<div
data-testid="template-container"
data-user-id={userId}
data-environment-id={environment.id}
data-project-id={project.id}
data-is-template-page={isTemplatePage}>
Template Container
</div>
)),
}));
vi.mock("@/modules/ui/components/button", () => ({
@@ -207,9 +203,8 @@ describe("SurveysPage", () => {
mockTranslate.mockReturnValue("Project not found");
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
await expect(SurveysPage({ params, searchParams })).rejects.toThrow("Project not found");
await expect(SurveysPage({ params })).rejects.toThrow("Project not found");
expect(mockGetProjectWithTeamIdsByEnvironmentId).toHaveBeenCalledWith("env-123");
expect(mockTranslate).toHaveBeenCalledWith("common.project_not_found");
@@ -225,9 +220,8 @@ describe("SurveysPage", () => {
});
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
await SurveysPage({ params, searchParams });
await SurveysPage({ params });
expect(mockRedirect).toHaveBeenCalledWith("/environments/env-123/settings/billing");
});
@@ -236,9 +230,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(0);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({ role: "product_manager" as TTemplateRole });
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("template-container")).toBeInTheDocument();
@@ -246,20 +239,14 @@ describe("SurveysPage", () => {
expect(screen.getByTestId("template-container")).toHaveAttribute("data-environment-id", "env-123");
expect(screen.getByTestId("template-container")).toHaveAttribute("data-project-id", "project-123");
expect(screen.getByTestId("template-container")).toHaveAttribute("data-is-template-page", "false");
const prefilledFilters = JSON.parse(
screen.getByTestId("template-container").getAttribute("data-prefilled-filters") || "[]"
);
expect(prefilledFilters).toEqual(["website", "other", "product_manager"]);
});
test("renders surveys list when survey count is greater than 0", async () => {
mockGetSurveyCount.mockResolvedValue(5);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("page-content-wrapper")).toBeInTheDocument();
@@ -289,9 +276,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(5);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("page-header")).toBeInTheDocument();
@@ -307,9 +293,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(0);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
// When survey count is 0, it should render TemplateContainer regardless of read-only status
@@ -330,16 +315,11 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(0);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("template-container")).toBeInTheDocument();
const prefilledFilters = JSON.parse(
screen.getByTestId("template-container").getAttribute("data-prefilled-filters") || "[]"
);
expect(prefilledFilters).toEqual([null, null, null]);
});
test("handles project with null styling", async () => {
@@ -351,9 +331,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(0);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("template-container")).toBeInTheDocument();
@@ -365,9 +344,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(5);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("surveys-list")).toHaveAttribute("data-locale", "en-US");
@@ -377,9 +355,8 @@ describe("SurveysPage", () => {
mockGetSurveyCount.mockResolvedValue(5);
const params = Promise.resolve({ environmentId: "env-123" });
const searchParams = Promise.resolve({});
const result = await SurveysPage({ params, searchParams });
const result = await SurveysPage({ params });
render(result);
expect(screen.getByTestId("link")).toHaveAttribute("href", "/environments/env-123/surveys/templates");

View File

@@ -14,7 +14,6 @@ import { PlusIcon } from "lucide-react";
import { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { TTemplateRole } from "@formbricks/types/templates";
export const metadata: Metadata = {
title: "Your Surveys",
@@ -24,17 +23,10 @@ interface SurveyTemplateProps {
params: Promise<{
environmentId: string;
}>;
searchParams: Promise<{
role?: TTemplateRole;
}>;
}
export const SurveysPage = async ({
params: paramsProps,
searchParams: searchParamsProps,
}: SurveyTemplateProps) => {
export const SurveysPage = async ({ params: paramsProps }: SurveyTemplateProps) => {
const publicDomain = getPublicDomain();
const searchParams = await searchParamsProps;
const params = await paramsProps;
const t = await getTranslate();
@@ -46,8 +38,6 @@ export const SurveysPage = async ({
const { session, isBilling, environment, isReadOnly } = await getEnvironmentAuth(params.environmentId);
const prefilledFilters = [project?.config.channel, project.config.industry, searchParams.role ?? null];
if (isBilling) {
return redirect(`/environments/${params.environmentId}/settings/billing`);
}
@@ -79,7 +69,6 @@ export const SurveysPage = async ({
userId={session.user.id}
environment={environment}
project={projectWithRequiredProps}
prefilledFilters={prefilledFilters}
isTemplatePage={false}
/>
);

View File

@@ -3,7 +3,6 @@ import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest";
import { TProjectConfigChannel, TProjectConfigIndustry } from "@formbricks/types/project";
import { TTemplateRole } from "@formbricks/types/templates";
import { TemplateContainerWithPreview } from "./template-container";
// Mock dependencies
@@ -59,8 +58,6 @@ const mockEnvironment = {
appSetupCompleted: true,
};
const mockPrefilledFilters: (TProjectConfigChannel | TProjectConfigIndustry | TTemplateRole | null)[] = [];
describe("TemplateContainerWithPreview", () => {
afterEach(() => {
cleanup();
@@ -72,7 +69,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);
@@ -86,7 +82,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={false}
/>
);
@@ -100,7 +95,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);
@@ -114,7 +108,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={false}
/>
);
@@ -128,7 +121,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);
@@ -144,7 +136,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);
@@ -158,7 +149,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);
@@ -172,7 +162,6 @@ describe("TemplateContainerWithPreview", () => {
project={mockProject}
environment={mockEnvironment}
userId="user1"
prefilledFilters={mockPrefilledFilters}
isTemplatePage={true}
/>
);

View File

@@ -5,19 +5,16 @@ import { TemplateList } from "@/modules/survey/components/template-list";
import { MenuBar } from "@/modules/survey/templates/components/menu-bar";
import { PreviewSurvey } from "@/modules/ui/components/preview-survey";
import { SearchBar } from "@/modules/ui/components/search-bar";
import { Project } from "@prisma/client";
import { Environment } from "@prisma/client";
import type { Environment, Project } from "@prisma/client";
import { useTranslate } from "@tolgee/react";
import { useState } from "react";
import type { TProjectConfigChannel, TProjectConfigIndustry } from "@formbricks/types/project";
import type { TTemplate, TTemplateRole } from "@formbricks/types/templates";
import type { TTemplate } from "@formbricks/types/templates";
import { getMinimalSurvey } from "../lib/minimal-survey";
type TemplateContainerWithPreviewProps = {
project: Project;
environment: Pick<Environment, "id" | "appSetupCompleted">;
userId: string;
prefilledFilters: (TProjectConfigChannel | TProjectConfigIndustry | TTemplateRole | null)[];
isTemplatePage?: boolean;
};
@@ -25,7 +22,6 @@ export const TemplateContainerWithPreview = ({
project,
environment,
userId,
prefilledFilters,
isTemplatePage = true,
}: TemplateContainerWithPreviewProps) => {
const { t } = useTranslate();
@@ -63,7 +59,6 @@ export const TemplateContainerWithPreview = ({
setActiveQuestionId(template.preset.questions[0].id);
setActiveTemplate(template);
}}
prefilledFilters={prefilledFilters}
/>
</div>
<aside className="group hidden flex-1 flex-shrink-0 items-center justify-center overflow-hidden border-l border-slate-100 bg-slate-50 md:flex md:flex-col">

View File

@@ -2,23 +2,15 @@ import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
import { getProjectWithTeamIdsByEnvironmentId } from "@/modules/survey/lib/project";
import { getTranslate } from "@/tolgee/server";
import { redirect } from "next/navigation";
import { TProjectConfigChannel, TProjectConfigIndustry } from "@formbricks/types/project";
import { TTemplateRole } from "@formbricks/types/templates";
import { TemplateContainerWithPreview } from "./components/template-container";
interface SurveyTemplateProps {
params: Promise<{
environmentId: string;
}>;
searchParams: Promise<{
channel?: TProjectConfigChannel;
industry?: TProjectConfigIndustry;
role?: TTemplateRole;
}>;
}
export const SurveyTemplatesPage = async (props: SurveyTemplateProps) => {
const searchParams = await props.searchParams;
const t = await getTranslate();
const params = await props.params;
const environmentId = params.environmentId;
@@ -35,14 +27,7 @@ export const SurveyTemplatesPage = async (props: SurveyTemplateProps) => {
return redirect(`/environments/${environment.id}/surveys`);
}
const prefilledFilters = [project.config.channel, project.config.industry, searchParams.role ?? null];
return (
<TemplateContainerWithPreview
userId={session.user.id}
environment={environment}
project={project}
prefilledFilters={prefilledFilters}
/>
<TemplateContainerWithPreview userId={session.user.id} environment={environment} project={project} />
);
};