diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/emailTemplate.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/emailTemplate.tsx index 476f6f794d..7407833959 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/emailTemplate.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/emailTemplate.tsx @@ -1,10 +1,10 @@ -import { getPreviewEmailTemplateHtml } from "@formbricks/email/components/survey/PreviewEmailTemplste"; +import { getPreviewEmailTemplateHtml } from "@formbricks/email/components/survey/PreviewEmailTemplate"; import { WEBAPP_URL } from "@formbricks/lib/constants"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants"; import { getSurvey } from "@formbricks/lib/survey/service"; +import { getStyling } from "@formbricks/lib/utils/styling"; -export const getEmailTemplateHtml = async (surveyId) => { +export const getEmailTemplateHtml = async (surveyId: string) => { const survey = await getSurvey(surveyId); if (!survey) { throw new Error("Survey not found"); @@ -13,9 +13,10 @@ export const getEmailTemplateHtml = async (surveyId) => { if (!product) { throw new Error("Product not found"); } - const brandColor = product.styling.brandColor?.light || COLOR_DEFAULTS.brandColor; + + const styling = getStyling(product, survey); const surveyUrl = WEBAPP_URL + "/s/" + survey.id; - const html = getPreviewEmailTemplateHtml(survey, surveyUrl, brandColor); + const html = getPreviewEmailTemplateHtml(survey, surveyUrl, styling); const doctype = ''; const htmlCleaned = html.toString().replace(doctype, ""); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx index db2e732fbb..5bb877e998 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx @@ -118,10 +118,31 @@ export const QuestionsView = ({ }; if ("backButtonLabel" in updatedAttributes) { - updatedSurvey.questions.forEach((question) => { - question.backButtonLabel = updatedAttributes.backButtonLabel; - }); - setbackButtonLabel(updatedAttributes.backButtonLabel); + const backButtonLabel = updatedSurvey.questions[questionIdx].backButtonLabel; + // If the value of backbuttonLabel is equal to {default:""}, then delete backButtonLabel key + if ( + backButtonLabel && + Object.keys(backButtonLabel).length === 1 && + backButtonLabel["default"].trim() === "" + ) { + delete updatedSurvey.questions[questionIdx].backButtonLabel; + } else { + updatedSurvey.questions.forEach((question) => { + question.backButtonLabel = updatedAttributes.backButtonLabel; + }); + setbackButtonLabel(updatedAttributes.backButtonLabel); + } + } + // If the value of buttonLabel is equal to {default:""}, then delete buttonLabel key + if ("buttonLabel" in updatedAttributes) { + const currentButtonLabel = updatedSurvey.questions[questionIdx].buttonLabel; + if ( + currentButtonLabel && + Object.keys(currentButtonLabel).length === 1 && + currentButtonLabel["default"].trim() === "" + ) { + delete updatedSurvey.questions[questionIdx].buttonLabel; + } } setLocalSurvey(updatedSurvey); validateSurveyQuestion(updatedSurvey.questions[questionIdx]); diff --git a/packages/email/components/survey/PreviewEmailTemplste.tsx b/packages/email/components/survey/PreviewEmailTemplate.tsx similarity index 60% rename from packages/email/components/survey/PreviewEmailTemplste.tsx rename to packages/email/components/survey/PreviewEmailTemplate.tsx index 71de199e01..848e86b3dc 100644 --- a/packages/email/components/survey/PreviewEmailTemplste.tsx +++ b/packages/email/components/survey/PreviewEmailTemplate.tsx @@ -15,48 +15,52 @@ import React from "react"; import { cn } from "@formbricks/lib/cn"; import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; -import { isLight } from "@formbricks/lib/utils"; -import { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants"; +import { isLight, mixColor } from "@formbricks/lib/utils"; +import { TSurvey, TSurveyQuestionType, TSurveyStyling } from "@formbricks/types/surveys"; import { RatingSmiley } from "@formbricks/ui/RatingSmiley"; interface PreviewEmailTemplateProps { survey: TSurvey; surveyUrl: string; - brandColor: string; + styling: TSurveyStyling; } -export const getPreviewEmailTemplateHtml = (survey: TSurvey, surveyUrl: string, brandColor: string) => { - return render(, { +export const getPreviewEmailTemplateHtml = (survey: TSurvey, surveyUrl: string, styling: TSurveyStyling) => { + return render(, { pretty: true, }); }; -export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewEmailTemplateProps) => { +export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmailTemplateProps) => { const url = `${surveyUrl}?preview=true`; const urlWithPrefilling = `${surveyUrl}?preview=true&`; const defaultLanguageCode = "default"; const firstQuestion = survey.questions[0]; + + const brandColor = styling?.brandColor?.light || COLOR_DEFAULTS.brandColor; + switch (firstQuestion.type) { case TSurveyQuestionType.OpenText: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} -
+
); case TSurveyQuestionType.Consent: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + - - + + {getLocalizedValue(firstQuestion.label, defaultLanguageCode)} @@ -73,14 +77,14 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE {!firstQuestion.required && ( + className="rounded-custom inline-flex cursor-pointer appearance-none px-6 py-3 text-sm font-medium text-black"> Reject )} Accept @@ -91,26 +95,26 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.NPS: return ( - +
- + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} -
+
{Array.from({ length: 11 }, (_, i) => ( + className="border-input-border-color m-0 inline-flex h-10 w-10 items-center justify-center border p-0 text-slate-800"> {i} ))}
-
+
@@ -131,11 +135,11 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.CTA: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + + className="rounded-custom inline-flex cursor-pointer appearance-none px-6 py-3 text-sm font-medium text-black"> {getLocalizedValue(firstQuestion.dismissButtonLabel, defaultLanguageCode) || "Skip"} )} {getLocalizedValue(firstQuestion.buttonLabel, defaultLanguageCode)} @@ -165,17 +169,17 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.Rating: return ( - -
- + +
+ {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)}
@@ -184,7 +188,7 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE key={i} href={`${urlWithPrefilling}${firstQuestion.id}=${i + 1}`} className={cn( - " m-0 h-10 w-full p-0 text-center align-middle leading-10 text-slate-800", + "m-0 h-10 w-full p-0 text-center align-middle leading-10 text-slate-800", { ["border border-solid border-gray-200"]: firstQuestion.scale === "number", } @@ -200,7 +204,7 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ))}
-
+
@@ -221,17 +225,17 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.MultipleChoiceMulti: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} {firstQuestion.choices.map((choice) => (
{getLocalizedValue(choice.label, defaultLanguageCode)}
@@ -242,18 +246,18 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.MultipleChoiceSingle: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} {firstQuestion.choices.map((choice) => ( {getLocalizedValue(choice.label, defaultLanguageCode)} @@ -264,11 +268,11 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.PictureSelection: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)}
@@ -276,14 +280,14 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE firstQuestion.allowMulti ? ( ) : ( - + className="rounded-custom mb-1 mr-1 inline-block h-[110px] w-[220px]"> + ) )} @@ -293,17 +297,17 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.Cal: return ( - + - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} - + You have been invited to schedule a meet via cal.com. Schedule your meeting @@ -314,27 +318,27 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.Date: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} -
- - Select a date +
+ + Select a date
); case TSurveyQuestionType.Matrix: return ( - - + + {getLocalizedValue(firstQuestion.headline, "default")} - + {getLocalizedValue(firstQuestion.subheader, "default")} @@ -345,7 +349,7 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE return ( + className="text-question-color max-w-40 break-words px-4 py-2 text-center"> {getLocalizedValue(column, "default")} ); @@ -353,14 +357,16 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE {firstQuestion.rows.map((row, rowIndex) => { return ( - + {getLocalizedValue(row, "default")} {firstQuestion.columns.map(() => { return ( - -
+ +
); })} @@ -374,17 +380,17 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE ); case TSurveyQuestionType.Address: return ( - - + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} {Array.from({ length: 6 }).map((_, index) => (
))} @@ -393,14 +399,42 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, brandColor }: PreviewE } }; -const EmailTemplateWrapper = ({ children, surveyUrl, brandColor }) => { +const EmailTemplateWrapper = ({ + children, + surveyUrl, + styling, +}: { + children: React.ReactNode; + surveyUrl: string; + styling: TSurveyStyling; +}) => { + let signatureColor = ""; + const colors = { + "brand-color": styling.brandColor?.light ?? COLOR_DEFAULTS.brandColor, + "card-bg-color": styling.cardBackgroundColor?.light ?? COLOR_DEFAULTS.cardBackgroundColor, + "input-color": styling.inputColor?.light ?? COLOR_DEFAULTS.inputColor, + "input-border-color": styling.inputBorderColor?.light ?? COLOR_DEFAULTS.inputBorderColor, + "card-border-color": styling.cardBorderColor?.light ?? COLOR_DEFAULTS.cardBorderColor, + "question-color": styling.questionColor?.light ?? COLOR_DEFAULTS.questionColor, + }; + + if (isLight(colors["question-color"])) { + signatureColor = mixColor(colors["question-color"], "#000000", 0.2); + } else { + signatureColor = mixColor(colors["question-color"], "#ffffff", 0.2); + } + return ( { + className="bg-card-bg-color border-card-border-color rounded-custom mx-0 my-2 block overflow-auto border border-solid p-8 font-sans text-inherit"> {children} @@ -417,8 +451,8 @@ const EmailTemplateWrapper = ({ children, surveyUrl, brandColor }) => { const EmailFooter = () => { return ( - - + + Powered by Formbricks diff --git a/packages/js-core/src/app/lib/widget.ts b/packages/js-core/src/app/lib/widget.ts index f4b662137a..28b9485969 100644 --- a/packages/js-core/src/app/lib/widget.ts +++ b/packages/js-core/src/app/lib/widget.ts @@ -1,6 +1,7 @@ import { FormbricksAPI } from "@formbricks/api"; import { ResponseQueue } from "@formbricks/lib/responseQueue"; import SurveyState from "@formbricks/lib/surveyState"; +import { getStyling } from "@formbricks/lib/utils/styling"; import { TResponseUpdate } from "@formbricks/types/responses"; import { TSurvey } from "@formbricks/types/surveys"; @@ -92,26 +93,6 @@ const renderWidget = async (survey: TSurvey, action?: string) => { const isBrandingEnabled = product.inAppSurveyBranding; const formbricksSurveys = await loadFormbricksSurveysExternally(); - const getStyling = () => { - // allow style overwrite is disabled from the product - if (!product.styling.allowStyleOverwrite) { - return product.styling; - } - - // allow style overwrite is enabled from the product - if (product.styling.allowStyleOverwrite) { - // survey style overwrite is disabled - if (!survey.styling?.overwriteThemeStyling) { - return product.styling; - } - - // survey style overwrite is enabled - return survey.styling; - } - - return product.styling; - }; - setTimeout(() => { formbricksSurveys.renderSurveyModal({ survey: survey, @@ -120,7 +101,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => { darkOverlay, languageCode, placement, - styling: getStyling(), + styling: getStyling(product, survey), getSetIsError: (f: (value: boolean) => void) => { setIsError = f; }, diff --git a/packages/js-core/src/website/lib/widget.ts b/packages/js-core/src/website/lib/widget.ts index 95bb22ca60..1a24b377b5 100644 --- a/packages/js-core/src/website/lib/widget.ts +++ b/packages/js-core/src/website/lib/widget.ts @@ -1,6 +1,7 @@ import { FormbricksAPI } from "@formbricks/api"; import { ResponseQueue } from "@formbricks/lib/responseQueue"; import SurveyState from "@formbricks/lib/surveyState"; +import { getStyling } from "@formbricks/lib/utils/styling"; import { TJSWebsiteStateDisplay } from "@formbricks/types/js"; import { TResponseUpdate } from "@formbricks/types/responses"; import { TSurvey } from "@formbricks/types/surveys"; @@ -91,26 +92,6 @@ const renderWidget = async (survey: TSurvey, action?: string) => { const isBrandingEnabled = product.inAppSurveyBranding; const formbricksSurveys = await loadFormbricksSurveysExternally(); - const getStyling = () => { - // allow style overwrite is disabled from the product - if (!product.styling.allowStyleOverwrite) { - return product.styling; - } - - // allow style overwrite is enabled from the product - if (product.styling.allowStyleOverwrite) { - // survey style overwrite is disabled - if (!survey.styling?.overwriteThemeStyling) { - return product.styling; - } - - // survey style overwrite is enabled - return survey.styling; - } - - return product.styling; - }; - setTimeout(() => { formbricksSurveys.renderSurveyModal({ survey: survey, @@ -119,7 +100,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => { darkOverlay, languageCode, placement, - styling: getStyling(), + styling: getStyling(product, survey), getSetIsError: (f: (value: boolean) => void) => { setIsError = f; }, diff --git a/packages/lib/utils/styling.ts b/packages/lib/utils/styling.ts new file mode 100644 index 0000000000..93cdcc8cb8 --- /dev/null +++ b/packages/lib/utils/styling.ts @@ -0,0 +1,22 @@ +import { TProduct } from "@formbricks/types/product"; +import { TSurvey } from "@formbricks/types/surveys"; + +export const getStyling = (product: TProduct, survey: TSurvey) => { + // allow style overwrite is disabled from the product + if (!product.styling.allowStyleOverwrite) { + return product.styling; + } + + // allow style overwrite is enabled from the product + if (product.styling.allowStyleOverwrite) { + // survey style overwrite is disabled + if (!survey.styling?.overwriteThemeStyling) { + return product.styling; + } + + // survey style overwrite is enabled + return survey.styling; + } + + return product.styling; +};