From c73d4e82b572b9a0fd2943ea1e5be6979e97c837 Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Fri, 7 Jun 2024 15:05:46 +0200 Subject: [PATCH] chore: fix eslint issues in ee & email packages (#2742) --- .../edit/components/CTAQuestionForm.tsx | 2 +- .../edit/components/ConsentQuestionForm.tsx | 2 +- .../edit/components/EditWelcomeCard.tsx | 2 +- .../edit/components/LogicEditor.tsx | 5 +- .../components/MultipleChoiceQuestionForm.tsx | 14 +- .../edit/components/QuestionCard.tsx | 44 ++- .../edit/components/QuestionMenu.tsx | 25 +- .../edit/components/QuestionsView.tsx | 2 +- .../edit/components/SettingsView.tsx | 2 +- .../edit/components/SurveyMenuBar.tsx | 2 +- .../surveys/[surveyId]/edit/lib/validation.ts | 7 +- .../components/BasicCreateSegmentModal.tsx | 2 +- .../segments/components/EditSegmentModal.tsx | 2 +- .../(people)/segments/page.tsx | 4 +- .../notion/components/AddIntegrationModal.tsx | 5 +- .../integrations/notion/constants.ts | 28 +- .../product/languages/page.tsx | 2 +- .../(organization)/billing/actions.ts | 6 +- .../EditMemberships/MembersInfo.tsx | 2 +- .../components/IndividualInviteTab.tsx | 2 +- .../summary/components/SummaryList.tsx | 30 +- .../(analysis)/summary/lib/emailTemplate.tsx | 2 +- .../components/QuestionFilterComboBox.tsx | 13 +- .../components/QuestionsComboBox.tsx | 25 +- .../[surveyId]/components/ResponseFilter.tsx | 6 +- apps/web/app/api/cron/report-usage/route.ts | 2 +- .../api/pipeline/lib/handleIntegrations.ts | 6 +- apps/web/app/lib/questions.tsx | 10 +- apps/web/app/lib/surveys/surveys.ts | 33 +- apps/web/app/s/[surveyId]/lib/prefilling.ts | 34 +- apps/web/package.json | 1 - package.json | 3 +- packages/config-prettier/prettier-preset.js | 2 +- .../components/add-filter-modal.tsx} | 113 ++++--- .../components/advanced-targeting-card.tsx} | 232 +++++++------ .../components/create-segment-modal.tsx} | 81 +++-- .../components/segment-editor.tsx | 266 +++++++++++++++ .../components/segment-filter.tsx} | 308 +++++++++-------- .../components/segment-settings.tsx | 252 ++++++++++++++ .../lib/actions.ts | 3 +- .../lib/constants.ts | 0 .../components/SegmentEditor.tsx | 263 --------------- .../components/SegmentSettings.tsx | 248 -------------- packages/ee/billing/api/stripe-webhook.ts | 23 +- ...leted.ts => checkout-session-completed.ts} | 16 +- ....ts => subscription-created-or-updated.ts} | 23 +- ...tionDeleted.ts => subscription-deleted.ts} | 15 +- ...n.ts => create-customer-portal-session.ts} | 11 +- ...Subscription.ts => create-subscription.ts} | 45 ++- .../{downgradePlan.ts => downgrade-plan.ts} | 0 ...Subscription.ts => remove-subscription.ts} | 28 +- .../lib/{reportUsage.ts => report-usage.ts} | 23 +- packages/ee/lib/service.ts | 36 +- .../components/default-language-select.tsx} | 24 +- .../components/edit-language.tsx} | 70 ++-- .../components/language-indicator.tsx} | 40 ++- .../components/language-row.tsx} | 22 +- .../components/language-select.tsx} | 35 +- .../components/language-switch.tsx} | 17 +- .../components/language-toggle.tsx} | 16 +- .../components/localized-editor.tsx} | 45 +-- .../components/multi-language-card.tsx} | 69 ++-- .../components/secondary-language-select.tsx} | 20 +- .../lib/actions.ts | 2 +- .../lib/iso-languages.ts} | 0 packages/ee/package.json | 21 +- .../components/add-member-role.tsx} | 23 +- .../components/edit-membership-role.tsx} | 32 +- .../components/transfer-ownership-modal.tsx} | 29 +- .../lib/actions.ts | 6 +- packages/ee/tsconfig.json | 4 +- packages/email/.eslintrc.js | 2 +- ...ordEmail.tsx => forgot-password-email.tsx} | 11 +- ...il.tsx => password-reset-notify-email.tsx} | 7 +- ...cationEmail.tsx => verification-email.tsx} | 15 +- .../components/general/EmailTemplate.tsx | 81 ----- .../{EmailButton.tsx => email-button.tsx} | 4 +- .../{EmailFooter.tsx => email-footer.tsx} | 7 +- .../components/general/email-template.tsx | 82 +++++ ...tedEmail.tsx => invite-accepted-email.tsx} | 7 +- .../{InviteEmail.tsx => invite-email.tsx} | 11 +- ...eEmail.tsx => onboarding-invite-email.tsx} | 13 +- ...ail.tsx => embed-survey-preview-email.tsx} | 6 +- ...kSurveyEmail.tsx => link-survey-email.tsx} | 11 +- ...emplate.tsx => preview-email-template.tsx} | 159 +++++---- ...dEmail.tsx => response-finished-email.tsx} | 61 ++-- ... => create-reminder-notification-body.tsx} | 14 +- ...ation.tsx => live-survey-notification.tsx} | 19 +- ... => no-live-survey-notification-email.tsx} | 18 +- ...tionFooter.tsx => notification-footer.tsx} | 12 +- ...tionHeader.tsx => notification-header.tsx} | 29 +- ...onInsight.tsx => notification-insight.tsx} | 7 +- ... => weekly-summary-notification-email.tsx} | 24 +- packages/email/index.tsx | 86 +++-- packages/email/package.json | 10 +- packages/email/tsconfig.json | 13 + packages/lib/i18n/i18n.mock.ts | 26 +- .../lib/response/tests/__mocks__/data.mock.ts | 6 +- packages/lib/response/utils.ts | 31 +- packages/lib/segment/service.ts | 6 +- .../lib/survey/tests/__mock__/survey.mock.ts | 6 +- packages/lib/templates.ts | 313 +++++++++--------- packages/surveys/package.json | 2 +- .../general/QuestionConditional.tsx | 29 +- packages/types/LegacySurvey.ts | 25 +- packages/types/surveys.ts | 61 ++-- packages/types/weeklySummary.ts | 7 +- packages/ui/QuestionFormInput/index.tsx | 2 +- .../components/LanguageDropdown.tsx | 2 +- .../components/SingleResponseCardBody.tsx | 20 +- .../components/SingleResponseCardHeader.tsx | 2 +- pnpm-lock.yaml | 84 ++++- 112 files changed, 2173 insertions(+), 1946 deletions(-) rename packages/ee/{advancedTargeting/components/AddFilterModal.tsx => advanced-targeting/components/add-filter-modal.tsx} (87%) rename packages/ee/{advancedTargeting/components/AdvancedTargetingCard.tsx => advanced-targeting/components/advanced-targeting-card.tsx} (75%) rename packages/ee/{advancedTargeting/components/CreateSegmentModal.tsx => advanced-targeting/components/create-segment-modal.tsx} (87%) create mode 100644 packages/ee/advanced-targeting/components/segment-editor.tsx rename packages/ee/{advancedTargeting/components/SegmentFilter.tsx => advanced-targeting/components/segment-filter.tsx} (89%) create mode 100644 packages/ee/advanced-targeting/components/segment-settings.tsx rename packages/ee/{advancedTargeting => advanced-targeting}/lib/actions.ts (96%) rename packages/ee/{advancedTargeting => advanced-targeting}/lib/constants.ts (100%) delete mode 100644 packages/ee/advancedTargeting/components/SegmentEditor.tsx delete mode 100644 packages/ee/advancedTargeting/components/SegmentSettings.tsx rename packages/ee/billing/handlers/{checkoutSessionCompleted.ts => checkout-session-completed.ts} (91%) rename packages/ee/billing/handlers/{subscriptionCreatedOrUpdated.ts => subscription-created-or-updated.ts} (92%) rename packages/ee/billing/handlers/{subscriptionDeleted.ts => subscription-deleted.ts} (88%) rename packages/ee/billing/lib/{createCustomerPortalSession.ts => create-customer-portal-session.ts} (66%) rename packages/ee/billing/lib/{createSubscription.ts => create-subscription.ts} (84%) rename packages/ee/billing/lib/{downgradePlan.ts => downgrade-plan.ts} (100%) rename packages/ee/billing/lib/{removeSubscription.ts => remove-subscription.ts} (84%) rename packages/ee/billing/lib/{reportUsage.ts => report-usage.ts} (74%) rename packages/ee/{multiLanguage/components/DefaultLanguageSelect.tsx => multi-language/components/default-language-select.tsx} (80%) rename packages/ee/{multiLanguage/components/EditLanguage.tsx => multi-language/components/edit-language.tsx} (86%) rename packages/ee/{multiLanguage/components/LanguageIndicator.tsx => multi-language/components/language-indicator.tsx} (75%) rename packages/ee/{multiLanguage/components/LanguageRow.tsx => multi-language/components/language-row.tsx} (60%) rename packages/ee/{multiLanguage/components/LanguageSelect.tsx => multi-language/components/language-select.tsx} (75%) rename packages/ee/{multiLanguage/components/LanguageSwitch.tsx => multi-language/components/language-switch.tsx} (90%) rename packages/ee/{multiLanguage/components/LanguageToggle.tsx => multi-language/components/language-toggle.tsx} (69%) rename packages/ee/{multiLanguage/components/LocalizedEditor.tsx => multi-language/components/localized-editor.tsx} (80%) rename packages/ee/{multiLanguage/components/MultiLanguageCard.tsx => multi-language/components/multi-language-card.tsx} (89%) rename packages/ee/{multiLanguage/components/SecondaryLanguageSelect.tsx => multi-language/components/secondary-language-select.tsx} (74%) rename packages/ee/{multiLanguage => multi-language}/lib/actions.ts (97%) rename packages/ee/{multiLanguage/lib/isoLanguages.ts => multi-language/lib/iso-languages.ts} (100%) rename packages/ee/{RoleManagement/components/AddMemberRole.tsx => role-management/components/add-member-role.tsx} (73%) rename packages/ee/{RoleManagement/components/EditMembershipRole.tsx => role-management/components/edit-membership-role.tsx} (85%) rename packages/ee/{RoleManagement/components/TransferOwnershipModal.tsx => role-management/components/transfer-ownership-modal.tsx} (86%) rename packages/ee/{RoleManagement => role-management}/lib/actions.ts (93%) rename packages/email/components/auth/{ForgotPasswordEmail.tsx => forgot-password-email.tsx} (68%) rename packages/email/components/auth/{PasswordResetNotifyEmail.tsx => password-reset-notify-email.tsx} (72%) rename packages/email/components/auth/{VerificationEmail.tsx => verification-email.tsx} (61%) delete mode 100644 packages/email/components/general/EmailTemplate.tsx rename packages/email/components/general/{EmailButton.tsx => email-button.tsx} (78%) rename packages/email/components/general/{EmailFooter.tsx => email-footer.tsx} (55%) create mode 100644 packages/email/components/general/email-template.tsx rename packages/email/components/invite/{InviteAcceptedEmail.tsx => invite-accepted-email.tsx} (71%) rename packages/email/components/invite/{InviteEmail.tsx => invite-email.tsx} (63%) rename packages/email/components/invite/{OnboardingInviteEmail.tsx => onboarding-invite-email.tsx} (71%) rename packages/email/components/survey/{EmbedSurveyPreviewEmail.tsx => embed-survey-preview-email.tsx} (78%) rename packages/email/components/survey/{LinkSurveyEmail.tsx => link-survey-email.tsx} (67%) rename packages/email/components/survey/{PreviewEmailTemplate.tsx => preview-email-template.tsx} (81%) rename packages/email/components/survey/{ResponseFinishedEmail.tsx => response-finished-email.tsx} (76%) rename packages/email/components/weekly-summary/{CreateReminderNotificationBody.tsx => create-reminder-notification-body.tsx} (77%) rename packages/email/components/weekly-summary/{LiveSurveyNotification.tsx => live-survey-notification.tsx} (89%) rename packages/email/components/weekly-summary/{NoLiveSurveyNotificationEmail.tsx => no-live-survey-notification-email.tsx} (64%) rename packages/email/components/weekly-summary/{NotificationFooter.tsx => notification-footer.tsx} (74%) rename packages/email/components/weekly-summary/{NotificationHeader.tsx => notification-header.tsx} (62%) rename packages/email/components/weekly-summary/{NotificationInsight.tsx => notification-insight.tsx} (90%) rename packages/email/components/weekly-summary/{WeeklySummaryNotificationEmail.tsx => weekly-summary-notification-email.tsx} (64%) create mode 100644 packages/email/tsconfig.json diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx index d0c16d1900..85bd297022 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; -import { LocalizedEditor } from "@formbricks/ee/multiLanguage/components/LocalizedEditor"; +import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys"; import { Input } from "@formbricks/ui/Input"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx index a788c3ea9e..47f40c94ba 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; -import { LocalizedEditor } from "@formbricks/ee/multiLanguage/components/LocalizedEditor"; +import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys"; import { Label } from "@formbricks/ui/Label"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx index ce60eff541..efdf18c040 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx @@ -4,7 +4,7 @@ import * as Collapsible from "@radix-ui/react-collapsible"; import { usePathname } from "next/navigation"; import { useState } from "react"; -import { LocalizedEditor } from "@formbricks/ee/multiLanguage/components/LocalizedEditor"; +import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor"; import { cn } from "@formbricks/lib/cn"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TSurvey } from "@formbricks/types/surveys"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx index 14d78a3cb9..1d889e9bb2 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx @@ -8,7 +8,6 @@ import { } from "lucide-react"; import { useMemo, useState } from "react"; import { toast } from "react-hot-toast"; - import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall"; @@ -18,7 +17,7 @@ import { TSurveyLogic, TSurveyLogicCondition, TSurveyQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, } from "@formbricks/types/surveys"; import { Button } from "@formbricks/ui/Button"; import { @@ -66,7 +65,7 @@ export const LogicEditor = ({ return question.choices.map((choice) => getLocalizedValue(choice.label, "default")); } else if ("range" in question) { return Array.from({ length: question.range ? question.range : 0 }, (_, i) => (i + 1).toString()); - } else if (question.type === TSurveyQuestionType.NPS) { + } else if (question.type === TSurveyQuestionTypeEnum.NPS) { return Array.from({ length: 11 }, (_, i) => (i + 0).toString()); } return []; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx index 1e9d49d238..3c8eb7817d 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx @@ -5,7 +5,6 @@ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable" import { createId } from "@paralleldrive/cuid2"; import { PlusIcon, TrashIcon } from "lucide-react"; import { useEffect, useRef, useState } from "react"; - import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils"; import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; @@ -14,13 +13,12 @@ import { TShuffleOption, TSurvey, TSurveyMultipleChoiceQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, } from "@formbricks/types/surveys"; import { Button } from "@formbricks/ui/Button"; import { Label } from "@formbricks/ui/Label"; import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select"; - import { SelectQuestionChoice } from "./SelectQuestionChoice"; interface OpenQuestionFormProps { @@ -309,13 +307,13 @@ export const MultipleChoiceQuestionForm = ({ onClick={() => { updateQuestion(questionIdx, { type: - question.type === TSurveyQuestionType.MultipleChoiceMulti - ? TSurveyQuestionType.MultipleChoiceSingle - : TSurveyQuestionType.MultipleChoiceMulti, + question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti + ? TSurveyQuestionTypeEnum.MultipleChoiceSingle + : TSurveyQuestionTypeEnum.MultipleChoiceMulti, }); }}> - Convert to {question.type === TSurveyQuestionType.MultipleChoiceSingle ? "Multiple" : "Single"}{" "} - Select + Convert to{" "} + {question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ? "Multiple" : "Single"} Select
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionCard.tsx index 0c7cc69753..32900b2011 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionCard.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionCard.tsx @@ -1,21 +1,19 @@ "use client"; -import { QUESTIONS_ICON_MAP, getTSurveyQuestionTypeName } from "@/app/lib/questions"; +import { QUESTIONS_ICON_MAP, getTSurveyQuestionTypeEnumName } from "@/app/lib/questions"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import * as Collapsible from "@radix-ui/react-collapsible"; import { ChevronDownIcon, ChevronRightIcon, GripIcon } from "lucide-react"; import { useState } from "react"; - import { cn } from "@formbricks/lib/cn"; import { recallToHeadline } from "@formbricks/lib/utils/recall"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TProduct } from "@formbricks/types/product"; -import { TI18nString, TSurvey, TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TI18nString, TSurvey, TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { Label } from "@formbricks/ui/Label"; import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput"; import { Switch } from "@formbricks/ui/Switch"; - import { AddressQuestionForm } from "./AddressQuestionForm"; import { AdvancedSettings } from "./AdvancedSettings"; import { CTAQuestionForm } from "./CTAQuestionForm"; @@ -192,7 +190,7 @@ export const QuestionCard = ({ attributeClasses )[selectedLanguageCode] ?? "" ) - : getTSurveyQuestionTypeName(question.type)} + : getTSurveyQuestionTypeEnumName(question.type)}

{!open && question?.required && (

{question?.required && "Required"}

@@ -216,7 +214,7 @@ export const QuestionCard = ({
- {question.type === TSurveyQuestionType.OpenText ? ( + {question.type === TSurveyQuestionTypeEnum.OpenText ? ( - ) : question.type === TSurveyQuestionType.MultipleChoiceSingle ? ( + ) : question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ? ( - ) : question.type === TSurveyQuestionType.MultipleChoiceMulti ? ( + ) : question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti ? ( - ) : question.type === TSurveyQuestionType.NPS ? ( + ) : question.type === TSurveyQuestionTypeEnum.NPS ? ( - ) : question.type === TSurveyQuestionType.CTA ? ( + ) : question.type === TSurveyQuestionTypeEnum.CTA ? ( - ) : question.type === TSurveyQuestionType.Rating ? ( + ) : question.type === TSurveyQuestionTypeEnum.Rating ? ( - ) : question.type === TSurveyQuestionType.Consent ? ( + ) : question.type === TSurveyQuestionTypeEnum.Consent ? ( - ) : question.type === TSurveyQuestionType.Date ? ( + ) : question.type === TSurveyQuestionTypeEnum.Date ? ( - ) : question.type === TSurveyQuestionType.PictureSelection ? ( + ) : question.type === TSurveyQuestionTypeEnum.PictureSelection ? ( - ) : question.type === TSurveyQuestionType.FileUpload ? ( + ) : question.type === TSurveyQuestionTypeEnum.FileUpload ? ( - ) : question.type === TSurveyQuestionType.Cal ? ( + ) : question.type === TSurveyQuestionTypeEnum.Cal ? ( - ) : question.type === TSurveyQuestionType.Matrix ? ( + ) : question.type === TSurveyQuestionTypeEnum.Matrix ? ( - ) : question.type === TSurveyQuestionType.Address ? ( + ) : question.type === TSurveyQuestionTypeEnum.Address ? ( - {question.type !== TSurveyQuestionType.NPS && - question.type !== TSurveyQuestionType.Rating && - question.type !== TSurveyQuestionType.CTA ? ( + {question.type !== TSurveyQuestionTypeEnum.NPS && + question.type !== TSurveyQuestionTypeEnum.Rating && + question.type !== TSurveyQuestionTypeEnum.CTA ? (
) : null} - {(question.type === TSurveyQuestionType.Rating || - question.type === TSurveyQuestionType.NPS) && + {(question.type === TSurveyQuestionTypeEnum.Rating || + question.type === TSurveyQuestionTypeEnum.NPS) && questionIdx !== 0 && (
{ + const changeQuestionType = (type: TSurveyQuestionTypeEnum) => { const { headline, required, subheader, imageUrl, videoUrl, buttonLabel, backButtonLabel } = question; const questionDefaults = getQuestionDefaults(type, product); @@ -52,10 +51,10 @@ export const QuestionMenu = ({ // if going from single select to multi select or vice versa, we need to keep the choices as well if ( - (type === TSurveyQuestionType.MultipleChoiceSingle && - question.type === TSurveyQuestionType.MultipleChoiceMulti) || - (type === TSurveyQuestionType.MultipleChoiceMulti && - question.type === TSurveyQuestionType.MultipleChoiceSingle) + (type === TSurveyQuestionTypeEnum.MultipleChoiceSingle && + question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti) || + (type === TSurveyQuestionTypeEnum.MultipleChoiceMulti && + question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle) ) { updateQuestion(questionIdx, { choices: question.choices, @@ -80,7 +79,7 @@ export const QuestionMenu = ({ }); }; - const addQuestionBelow = (type: TSurveyQuestionType) => { + const addQuestionBelow = (type: TSurveyQuestionTypeEnum) => { const questionDefaults = getQuestionDefaults(type, product); addQuestion( @@ -143,15 +142,15 @@ export const QuestionMenu = ({ key={type} className="min-h-8 cursor-pointer text-slate-500" onClick={() => { - setChangeToType(type as TSurveyQuestionType); + setChangeToType(type as TSurveyQuestionTypeEnum); if (question.logic) { setLogicWarningModal(true); return; } - changeQuestionType(type as TSurveyQuestionType); + changeQuestionType(type as TSurveyQuestionTypeEnum); }}> - {QUESTIONS_ICON_MAP[type as TSurveyQuestionType]} + {QUESTIONS_ICON_MAP[type as TSurveyQuestionTypeEnum]} {name} ); @@ -176,9 +175,9 @@ export const QuestionMenu = ({ className="min-h-8 cursor-pointer text-slate-500" onClick={(e) => { e.stopPropagation(); - addQuestionBelow(type as TSurveyQuestionType); + addQuestionBelow(type as TSurveyQuestionTypeEnum); }}> - {QUESTIONS_ICON_MAP[type as TSurveyQuestionType]} + {QUESTIONS_ICON_MAP[type as TSurveyQuestionTypeEnum]} {name} ); diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx index e4749961e1..d328ff4159 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx @@ -12,7 +12,7 @@ import { createId } from "@paralleldrive/cuid2"; import React, { SetStateAction, useEffect, useMemo, useState } from "react"; import toast from "react-hot-toast"; -import { MultiLanguageCard } from "@formbricks/ee/multiLanguage/components/MultiLanguageCard"; +import { MultiLanguageCard } from "@formbricks/ee/multi-language/components/multi-language-card"; import { extractLanguageCodes, getLocalizedValue, translateQuestion } from "@formbricks/lib/i18n/utils"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView.tsx index fd0a87f930..83459dd949 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView.tsx @@ -1,4 +1,4 @@ -import { AdvancedTargetingCard } from "@formbricks/ee/advancedTargeting/components/AdvancedTargetingCard"; +import { AdvancedTargetingCard } from "@formbricks/ee/advanced-targeting/components/advanced-targeting-card"; import { TActionClass } from "@formbricks/types/actionClasses"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TEnvironment } from "@formbricks/types/environment"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx index 8f73d43fbe..1f611fcf28 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx @@ -7,7 +7,7 @@ import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import toast from "react-hot-toast"; -import { createSegmentAction } from "@formbricks/ee/advancedTargeting/lib/actions"; +import { createSegmentAction } from "@formbricks/ee/advanced-targeting/lib/actions"; import { TEnvironment } from "@formbricks/types/environment"; import { TProduct } from "@formbricks/types/product"; import { TSegment } from "@formbricks/types/segment"; diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation.ts b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation.ts index f153a34790..c3567789f8 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation.ts +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation.ts @@ -1,7 +1,6 @@ // extend this object in order to add more validation rules import { isEqual } from "lodash"; import { toast } from "react-hot-toast"; - import { extractLanguageCodes, getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { checkForEmptyFallBackValue } from "@formbricks/lib/utils/recall"; import { ZSegmentFilters } from "@formbricks/types/segment"; @@ -16,7 +15,7 @@ import { TSurveyOpenTextQuestion, TSurveyPictureSelectionQuestion, TSurveyQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveyQuestions, TSurveyThankYouCard, TSurveyWelcomeCard, @@ -363,8 +362,8 @@ export const isSurveyValid = ( existingQuestionIds.add(question.id); if ( - question.type === TSurveyQuestionType.MultipleChoiceSingle || - question.type === TSurveyQuestionType.MultipleChoiceMulti + question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle || + question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti ) { const haveSameChoices = question.choices.some((element) => element.label[selectedLanguageCode]?.trim() === "") || diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal.tsx index 827cd77777..08ba79a0f9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import toast from "react-hot-toast"; -import { createSegmentAction } from "@formbricks/ee/advancedTargeting/lib/actions"; +import { createSegmentAction } from "@formbricks/ee/advanced-targeting/lib/actions"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TBaseFilter, TSegment, ZSegmentFilters } from "@formbricks/types/segment"; diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/EditSegmentModal.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/EditSegmentModal.tsx index 5b3ae64645..4f17581941 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/EditSegmentModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/EditSegmentModal.tsx @@ -2,7 +2,7 @@ import { UsersIcon } from "lucide-react"; -import { SegmentSettings } from "@formbricks/ee/advancedTargeting/components/SegmentSettings"; +import { SegmentSettings } from "@formbricks/ee/advanced-targeting/components/segment-settings"; import { TActionClass } from "@formbricks/types/actionClasses"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TSegment, TSegmentWithSurveyNames } from "@formbricks/types/segment"; diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx index 09ef8b2bfc..64ac8a7ad9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx @@ -2,8 +2,8 @@ import { PeopleSecondaryNavigation } from "@/app/(app)/environments/[environment import { BasicCreateSegmentModal } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal"; import { SegmentTable } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/SegmentTable"; -import { CreateSegmentModal } from "@formbricks/ee/advancedTargeting/components/CreateSegmentModal"; -import { ACTIONS_TO_EXCLUDE } from "@formbricks/ee/advancedTargeting/lib/constants"; +import { CreateSegmentModal } from "@formbricks/ee/advanced-targeting/components/create-segment-modal"; +import { ACTIONS_TO_EXCLUDE } from "@formbricks/ee/advanced-targeting/lib/constants"; import { getAdvancedTargetingPermission } from "@formbricks/ee/lib/service"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { getAttributeClasses } from "@formbricks/lib/attributeClass/service"; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/notion/components/AddIntegrationModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/notion/components/AddIntegrationModal.tsx index 675d047db5..442915169c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/notion/components/AddIntegrationModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/notion/components/AddIntegrationModal.tsx @@ -11,7 +11,6 @@ import Image from "next/image"; import React, { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; - import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall"; @@ -22,7 +21,7 @@ import { TIntegrationNotionConfigData, TIntegrationNotionDatabase, } from "@formbricks/types/integration/notion"; -import { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { Button } from "@formbricks/ui/Button"; import { DropdownSelector } from "@formbricks/ui/DropdownSelector"; import { Label } from "@formbricks/ui/Label"; @@ -123,7 +122,7 @@ export const AddIntegrationModal = ({ ? selectedSurvey?.hiddenFields.fieldIds?.map((fId) => ({ id: fId, name: fId, - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, })) || [] : []; return [...questions, ...hiddenFields]; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/notion/constants.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/notion/constants.ts index 16bece7040..7962ec4531 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/notion/constants.ts +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/notion/constants.ts @@ -1,10 +1,10 @@ -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; export const TYPE_MAPPING = { - [TSurveyQuestionType.CTA]: ["checkbox"], - [TSurveyQuestionType.MultipleChoiceMulti]: ["multi_select"], - [TSurveyQuestionType.MultipleChoiceSingle]: ["select", "status"], - [TSurveyQuestionType.OpenText]: [ + [TSurveyQuestionTypeEnum.CTA]: ["checkbox"], + [TSurveyQuestionTypeEnum.MultipleChoiceMulti]: ["multi_select"], + [TSurveyQuestionTypeEnum.MultipleChoiceSingle]: ["select", "status"], + [TSurveyQuestionTypeEnum.OpenText]: [ "created_by", "created_time", "date", @@ -17,15 +17,15 @@ export const TYPE_MAPPING = { "title", "url", ], - [TSurveyQuestionType.NPS]: ["number"], - [TSurveyQuestionType.Consent]: ["checkbox"], - [TSurveyQuestionType.Rating]: ["number"], - [TSurveyQuestionType.PictureSelection]: ["url"], - [TSurveyQuestionType.FileUpload]: ["url"], - [TSurveyQuestionType.Date]: ["date"], - [TSurveyQuestionType.Address]: ["rich_text"], - [TSurveyQuestionType.Matrix]: ["rich_text"], - [TSurveyQuestionType.Cal]: ["checkbox"], + [TSurveyQuestionTypeEnum.NPS]: ["number"], + [TSurveyQuestionTypeEnum.Consent]: ["checkbox"], + [TSurveyQuestionTypeEnum.Rating]: ["number"], + [TSurveyQuestionTypeEnum.PictureSelection]: ["url"], + [TSurveyQuestionTypeEnum.FileUpload]: ["url"], + [TSurveyQuestionTypeEnum.Date]: ["date"], + [TSurveyQuestionTypeEnum.Address]: ["rich_text"], + [TSurveyQuestionTypeEnum.Matrix]: ["rich_text"], + [TSurveyQuestionTypeEnum.Cal]: ["checkbox"], }; export const UNSUPPORTED_TYPES_BY_NOTION = [ diff --git a/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx index 8e3dbdaf0f..ae4090ecad 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx @@ -3,7 +3,7 @@ import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/ import { notFound } from "next/navigation"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; -import { EditLanguage } from "@formbricks/ee/multiLanguage/components/EditLanguage"; +import { EditLanguage } from "@formbricks/ee/multi-language/components/edit-language"; import { getOrganization } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts index f36165365a..e3a57b33b2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts @@ -3,9 +3,9 @@ import { getServerSession } from "next-auth"; import { StripePriceLookupKeys } from "@formbricks/ee/billing/lib/constants"; -import { createCustomerPortalSession } from "@formbricks/ee/billing/lib/createCustomerPortalSession"; -import { createSubscription } from "@formbricks/ee/billing/lib/createSubscription"; -import { removeSubscription } from "@formbricks/ee/billing/lib/removeSubscription"; +import { createCustomerPortalSession } from "@formbricks/ee/billing/lib/create-customer-portal-session"; +import { createSubscription } from "@formbricks/ee/billing/lib/create-subscription"; +import { removeSubscription } from "@formbricks/ee/billing/lib/remove-subscription"; import { authOptions } from "@formbricks/lib/authOptions"; import { WEBAPP_URL } from "@formbricks/lib/constants"; import { canUserAccessOrganization } from "@formbricks/lib/organization/auth"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx index 6f675e48fa..820e934ecf 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx @@ -1,7 +1,7 @@ import { MemberActions } from "@/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MemberActions"; import { isInviteExpired } from "@/app/lib/utils"; -import { EditMembershipRole } from "@formbricks/ee/RoleManagement/components/EditMembershipRole"; +import { EditMembershipRole } from "@formbricks/ee/role-management/components/edit-membership-role"; import { TInvite } from "@formbricks/types/invites"; import { TMember, TMembershipRole } from "@formbricks/types/memberships"; import { TOrganization } from "@formbricks/types/organizations"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx index c5cab47e2b..64a14fecd8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx @@ -2,7 +2,7 @@ import { useForm } from "react-hook-form"; -import { AddMemberRole } from "@formbricks/ee/RoleManagement/components/AddMemberRole"; +import { AddMemberRole } from "@formbricks/ee/role-management/components/add-member-role"; import { Button } from "@formbricks/ui/Button"; import { Input } from "@formbricks/ui/Input"; import { Label } from "@formbricks/ui/Label"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx index 673514ed61..038c9f8559 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx @@ -11,15 +11,13 @@ import { NPSSummary } from "@/app/(app)/environments/[environmentId]/surveys/[su import { OpenTextSummary } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/OpenTextSummary"; import { PictureChoiceSummary } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary"; import { RatingSummary } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary"; - import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TEnvironment } from "@formbricks/types/environment"; import { TSurveySummary } from "@formbricks/types/surveys"; -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { TSurvey } from "@formbricks/types/surveys"; import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller"; import { SkeletonLoader } from "@formbricks/ui/SkeletonLoader"; - import { AddressSummary } from "./AddressSummary"; interface SummaryListProps { @@ -58,7 +56,7 @@ export const SummaryList = ({ /> ) : ( summary.map((questionSummary) => { - if (questionSummary.type === TSurveyQuestionType.OpenText) { + if (questionSummary.type === TSurveyQuestionTypeEnum.OpenText) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.NPS) { + if (questionSummary.type === TSurveyQuestionTypeEnum.NPS) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.CTA) { + if (questionSummary.type === TSurveyQuestionTypeEnum.CTA) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Rating) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Rating) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Consent) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Consent) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.PictureSelection) { + if (questionSummary.type === TSurveyQuestionTypeEnum.PictureSelection) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Date) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Date) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.FileUpload) { + if (questionSummary.type === TSurveyQuestionTypeEnum.FileUpload) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Cal) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Cal) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Matrix) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Matrix) { return ( ); } - if (questionSummary.type === TSurveyQuestionType.Address) { + if (questionSummary.type === TSurveyQuestionTypeEnum.Address) { return ( void; onChangeFilterComboBoxValue: (o: string | string[]) => void; - type?: TSurveyQuestionType | Omit; + type?: TSurveyQuestionTypeEnum | Omit; handleRemoveMultiSelect: (value: string[]) => void; disabled?: boolean; }; @@ -47,9 +46,9 @@ export const QuestionFilterComboBox = ({ // multiple when question type is multi selection const isMultiple = - type === TSurveyQuestionType.MultipleChoiceMulti || - type === TSurveyQuestionType.MultipleChoiceSingle || - type === TSurveyQuestionType.PictureSelection; + type === TSurveyQuestionTypeEnum.MultipleChoiceMulti || + type === TSurveyQuestionTypeEnum.MultipleChoiceSingle || + type === TSurveyQuestionTypeEnum.PictureSelection; // when question type is multi selection so we remove the option from the options which has been already selected const options = isMultiple @@ -63,7 +62,7 @@ export const QuestionFilterComboBox = ({ // disable the combo box for selection of value when question type is nps or rating and selected value is submitted or skipped const isDisabledComboBox = - (type === TSurveyQuestionType.NPS || type === TSurveyQuestionType.Rating) && + (type === TSurveyQuestionTypeEnum.NPS || type === TSurveyQuestionTypeEnum.Rating) && (filterValue === "Submitted" || filterValue === "Skipped"); return ( diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox.tsx index b004fced89..1f20ee5004 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox.tsx @@ -22,10 +22,9 @@ import { User, } from "lucide-react"; import * as React from "react"; - import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside"; -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { Command, CommandEmpty, @@ -47,7 +46,7 @@ export enum OptionsType { export type QuestionOption = { label: string; - questionType?: TSurveyQuestionType; + questionType?: TSurveyQuestionTypeEnum; type: OptionsType; id: string; }; @@ -67,25 +66,25 @@ const SelectedCommandItem = ({ label, questionType, type }: Partial; - case TSurveyQuestionType.Rating: + case TSurveyQuestionTypeEnum.Rating: return ; - case TSurveyQuestionType.CTA: + case TSurveyQuestionTypeEnum.CTA: return ; - case TSurveyQuestionType.OpenText: + case TSurveyQuestionTypeEnum.OpenText: return ; - case TSurveyQuestionType.MultipleChoiceMulti: + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: return ; - case TSurveyQuestionType.MultipleChoiceSingle: + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: return ; - case TSurveyQuestionType.NPS: + case TSurveyQuestionTypeEnum.NPS: return ; - case TSurveyQuestionType.Consent: + case TSurveyQuestionTypeEnum.Consent: return ; - case TSurveyQuestionType.PictureSelection: + case TSurveyQuestionTypeEnum.PictureSelection: return ; - case TSurveyQuestionType.Matrix: + case TSurveyQuestionTypeEnum.Matrix: return ; } case OptionsType.ATTRIBUTES: diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter.tsx index dd8c5b67b0..cf72148eb6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter.tsx @@ -14,16 +14,14 @@ import { TrashIcon } from "lucide-react"; import { ChevronDown, ChevronUp, Plus } from "lucide-react"; import { useParams } from "next/navigation"; import { useEffect, useState } from "react"; - -import { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { Button } from "@formbricks/ui/Button"; import { Checkbox } from "@formbricks/ui/Checkbox"; import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover"; - import { OptionsType, QuestionOption, QuestionsComboBox } from "./QuestionsComboBox"; export type QuestionFilterOptions = { - type: TSurveyQuestionType | "Attributes" | "Tags" | "Languages"; + type: TSurveyQuestionTypeEnum | "Attributes" | "Tags" | "Languages"; filterOptions: string[]; filterComboBoxOptions: string[]; id: string; diff --git a/apps/web/app/api/cron/report-usage/route.ts b/apps/web/app/api/cron/report-usage/route.ts index 005cdb68b4..f3682b58fd 100644 --- a/apps/web/app/api/cron/report-usage/route.ts +++ b/apps/web/app/api/cron/report-usage/route.ts @@ -2,7 +2,7 @@ import { responses } from "@/app/lib/api/response"; import { headers } from "next/headers"; import { ProductFeatureKeys } from "@formbricks/ee/billing/lib/constants"; -import { reportUsageToStripe } from "@formbricks/ee/billing/lib/reportUsage"; +import { reportUsageToStripe } from "@formbricks/ee/billing/lib/report-usage"; import { CRON_SECRET, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { getMonthlyActiveOrganizationPeopleCount, diff --git a/apps/web/app/api/pipeline/lib/handleIntegrations.ts b/apps/web/app/api/pipeline/lib/handleIntegrations.ts index b5d0bc8903..ffc3254bab 100644 --- a/apps/web/app/api/pipeline/lib/handleIntegrations.ts +++ b/apps/web/app/api/pipeline/lib/handleIntegrations.ts @@ -10,7 +10,7 @@ import { TIntegrationGoogleSheets } from "@formbricks/types/integration/googleSh import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/types/integration/notion"; import { TIntegrationSlack } from "@formbricks/types/integration/slack"; import { TPipelineInput } from "@formbricks/types/pipelines"; -import { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; export const handleIntegrations = async ( integrations: TIntegration[], @@ -103,7 +103,7 @@ const extractResponses = async ( if (responseValue !== undefined) { let answer: typeof responseValue; - if (question.type === TSurveyQuestionType.PictureSelection) { + if (question.type === TSurveyQuestionTypeEnum.PictureSelection) { const selectedChoiceIds = responseValue as string[]; answer = question?.choices .filter((choice) => selectedChoiceIds.includes(choice.id)) @@ -147,7 +147,7 @@ const buildNotionPayloadProperties = ( const responses = data.response.data; const mappingQIds = mapping - .filter((m) => m.question.type === TSurveyQuestionType.PictureSelection) + .filter((m) => m.question.type === TSurveyQuestionTypeEnum.PictureSelection) .map((m) => m.question.id); Object.keys(responses).forEach((resp) => { diff --git a/apps/web/app/lib/questions.tsx b/apps/web/app/lib/questions.tsx index 9dccf41565..f6858e7ec0 100644 --- a/apps/web/app/lib/questions.tsx +++ b/apps/web/app/lib/questions.tsx @@ -14,9 +14,8 @@ import { Rows3Icon, StarIcon, } from "lucide-react"; - import { - TSurveyQuestionType as QuestionId, + TSurveyQuestionTypeEnum as QuestionId, TSurveyAddressQuestion, TSurveyCTAQuestion, TSurveyCalQuestion, @@ -28,10 +27,9 @@ import { TSurveyNPSQuestion, TSurveyOpenTextQuestion, TSurveyPictureSelectionQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveyRatingQuestion, } from "@formbricks/types/surveys"; - import { replaceQuestionPresetPlaceholders } from "./templates"; export type TQuestion = { @@ -232,7 +230,7 @@ export const QUESTIONS_NAME_MAP = questionTypes.reduce( [curr.id]: curr.label, }), {} -) as Record; +) as Record; export const universalQuestionPresets = { required: true, @@ -243,7 +241,7 @@ export const getQuestionDefaults = (id: string, product: any) => { return replaceQuestionPresetPlaceholders(questionType?.preset, product); }; -export const getTSurveyQuestionTypeName = (id: string) => { +export const getTSurveyQuestionTypeEnumName = (id: string) => { const questionType = questionTypes.find((questionType) => questionType.id === id); return questionType?.label; }; diff --git a/apps/web/app/lib/surveys/surveys.ts b/apps/web/app/lib/surveys/surveys.ts index d60fdbbde3..75e792df34 100644 --- a/apps/web/app/lib/surveys/surveys.ts +++ b/apps/web/app/lib/surveys/surveys.ts @@ -9,14 +9,13 @@ import { QuestionOptions, } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox"; import { QuestionFilterOptions } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter"; - import { TResponseFilterCriteria, TResponseHiddenFieldsFilter, TSurveyMetaFieldFilter, TSurveyPersonAttributes, } from "@formbricks/types/responses"; -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { TSurvey } from "@formbricks/types/surveys"; import { TTag } from "@formbricks/types/tags"; @@ -75,8 +74,8 @@ export const generateQuestionAndFilterOptions = ( survey.questions.forEach((q) => { if (Object.keys(conditionOptions).includes(q.type)) { if ( - q.type === TSurveyQuestionType.MultipleChoiceMulti || - q.type === TSurveyQuestionType.MultipleChoiceSingle + q.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti || + q.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ) { questionFilterOptions.push({ type: q.type, @@ -86,14 +85,14 @@ export const generateQuestionAndFilterOptions = ( : [""], id: q.id, }); - } else if (q.type === TSurveyQuestionType.PictureSelection) { + } else if (q.type === TSurveyQuestionTypeEnum.PictureSelection) { questionFilterOptions.push({ type: q.type, filterOptions: conditionOptions[q.type], filterComboBoxOptions: q?.choices ? q?.choices?.map((_, idx) => `Picture ${idx + 1}`) : [""], id: q.id, }); - } else if (q.type === TSurveyQuestionType.Matrix) { + } else if (q.type === TSurveyQuestionTypeEnum.Matrix) { questionFilterOptions.push({ type: q.type, filterOptions: q.rows.flatMap((row) => Object.values(row)), @@ -268,8 +267,8 @@ export const getFormattedFilters = ( questions.forEach(({ filterType, questionType }) => { if (!filters.data) filters.data = {}; switch (questionType.questionType) { - case TSurveyQuestionType.OpenText: - case TSurveyQuestionType.Address: { + case TSurveyQuestionTypeEnum.OpenText: + case TSurveyQuestionTypeEnum.Address: { if (filterType.filterComboBoxValue === "Filled out") { filters.data[questionType.id ?? ""] = { op: "submitted", @@ -280,8 +279,8 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.MultipleChoiceSingle: - case TSurveyQuestionType.MultipleChoiceMulti: { + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: { if (filterType.filterValue === "Includes either") { filters.data[questionType.id ?? ""] = { op: "includesOne", @@ -294,8 +293,8 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.NPS: - case TSurveyQuestionType.Rating: { + case TSurveyQuestionTypeEnum.NPS: + case TSurveyQuestionTypeEnum.Rating: { if (filterType.filterValue === "Is equal to") { filters.data[questionType.id ?? ""] = { op: "equals", @@ -321,7 +320,7 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.CTA: { + case TSurveyQuestionTypeEnum.CTA: { if (filterType.filterComboBoxValue === "Clicked") { filters.data[questionType.id ?? ""] = { op: "clicked", @@ -332,7 +331,7 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.Consent: { + case TSurveyQuestionTypeEnum.Consent: { if (filterType.filterComboBoxValue === "Accepted") { filters.data[questionType.id ?? ""] = { op: "accepted", @@ -343,12 +342,12 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.PictureSelection: { + case TSurveyQuestionTypeEnum.PictureSelection: { const questionId = questionType.id ?? ""; const question = survey.questions.find((q) => q.id === questionId); if ( - question?.type !== TSurveyQuestionType.PictureSelection || + question?.type !== TSurveyQuestionTypeEnum.PictureSelection || !Array.isArray(filterType.filterComboBoxValue) ) { return; @@ -371,7 +370,7 @@ export const getFormattedFilters = ( }; } } - case TSurveyQuestionType.Matrix: { + case TSurveyQuestionTypeEnum.Matrix: { if ( filterType.filterValue && filterType.filterComboBoxValue && diff --git a/apps/web/app/s/[surveyId]/lib/prefilling.ts b/apps/web/app/s/[surveyId]/lib/prefilling.ts index 62c946e9e7..5ecd358c82 100644 --- a/apps/web/app/s/[surveyId]/lib/prefilling.ts +++ b/apps/web/app/s/[surveyId]/lib/prefilling.ts @@ -1,5 +1,5 @@ import { TResponseData } from "@formbricks/types/responses"; -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys"; export const getPrefillValue = ( @@ -32,10 +32,10 @@ export const checkValidity = (question: TSurveyQuestion, answer: string, languag if (question.required && (!answer || answer === "")) return false; try { switch (question.type) { - case TSurveyQuestionType.OpenText: { + case TSurveyQuestionTypeEnum.OpenText: { return true; } - case TSurveyQuestionType.MultipleChoiceSingle: { + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: { const hasOther = question.choices[question.choices.length - 1].id === "other"; if (!hasOther) { if (!question.choices.find((choice) => choice.label[language] === answer)) return false; @@ -48,7 +48,7 @@ export const checkValidity = (question: TSurveyQuestion, answer: string, languag return true; } - case TSurveyQuestionType.MultipleChoiceMulti: { + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: { const answerChoices = answer.split(","); const hasOther = question.choices[question.choices.length - 1].id === "other"; if (!hasOther) { @@ -62,7 +62,7 @@ export const checkValidity = (question: TSurveyQuestion, answer: string, languag } return true; } - case TSurveyQuestionType.NPS: { + case TSurveyQuestionTypeEnum.NPS: { answer = answer.replace(/&/g, ";"); const answerNumber = Number(JSON.parse(answer)); @@ -70,23 +70,23 @@ export const checkValidity = (question: TSurveyQuestion, answer: string, languag if (answerNumber < 0 || answerNumber > 10) return false; return true; } - case TSurveyQuestionType.CTA: { + case TSurveyQuestionTypeEnum.CTA: { if (question.required && answer === "dismissed") return false; if (answer !== "clicked" && answer !== "dismissed") return false; return true; } - case TSurveyQuestionType.Consent: { + case TSurveyQuestionTypeEnum.Consent: { if (question.required && answer === "dismissed") return false; if (answer !== "accepted" && answer !== "dismissed") return false; return true; } - case TSurveyQuestionType.Rating: { + case TSurveyQuestionTypeEnum.Rating: { answer = answer.replace(/&/g, ";"); const answerNumber = Number(JSON.parse(answer)); if (answerNumber < 1 || answerNumber > question.range) return false; return true; } - case TSurveyQuestionType.PictureSelection: { + case TSurveyQuestionTypeEnum.PictureSelection: { const answerChoices = answer.split(","); return answerChoices.every((ans: string) => !isNaN(Number(ans))); } @@ -104,20 +104,20 @@ export const transformAnswer = ( language: string ): string | number | string[] => { switch (question.type) { - case TSurveyQuestionType.OpenText: - case TSurveyQuestionType.MultipleChoiceSingle: - case TSurveyQuestionType.Consent: - case TSurveyQuestionType.CTA: { + case TSurveyQuestionTypeEnum.OpenText: + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: + case TSurveyQuestionTypeEnum.Consent: + case TSurveyQuestionTypeEnum.CTA: { return answer; } - case TSurveyQuestionType.Rating: - case TSurveyQuestionType.NPS: { + case TSurveyQuestionTypeEnum.Rating: + case TSurveyQuestionTypeEnum.NPS: { answer = answer.replace(/&/g, ";"); return Number(JSON.parse(answer)); } - case TSurveyQuestionType.PictureSelection: { + case TSurveyQuestionTypeEnum.PictureSelection: { const answerChoicesIdx = answer.split(","); const answerArr: string[] = []; @@ -130,7 +130,7 @@ export const transformAnswer = ( return answerArr.slice(0, 1); } - case TSurveyQuestionType.MultipleChoiceMulti: { + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: { let ansArr = answer.split(","); const hasOthers = question.choices[question.choices.length - 1].id === "other"; if (!hasOthers) return ansArr; diff --git a/apps/web/package.json b/apps/web/package.json index 260af3c299..6622799293 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -51,7 +51,6 @@ "lucide-react": "^0.379.0", "mime": "^4.0.3", "next": "15.0.0-rc.0", - "nodemailer": "^6.9.13", "optional": "^0.1.4", "otplib": "^12.0.1", "papaparse": "^5.4.1", diff --git a/package.json b/package.json index 0e5e77e470..21c4bd65d0 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,7 @@ }, "lint-staged": { "(apps|packages)/**/*.{js,ts,jsx,tsx}": [ - "prettier --write", - "eslint --fix" + "prettier --write" ], "*.json": [ "prettier --write" diff --git a/packages/config-prettier/prettier-preset.js b/packages/config-prettier/prettier-preset.js index 019260b88b..6f09a0d70f 100644 --- a/packages/config-prettier/prettier-preset.js +++ b/packages/config-prettier/prettier-preset.js @@ -16,6 +16,6 @@ module.exports = { "^~/(.*)$", "^[./]", ], - importOrderSeparation: true, + importOrderSeparation: false, importOrderSortSpecifiers: true, }; diff --git a/packages/ee/advancedTargeting/components/AddFilterModal.tsx b/packages/ee/advanced-targeting/components/add-filter-modal.tsx similarity index 87% rename from packages/ee/advancedTargeting/components/AddFilterModal.tsx rename to packages/ee/advanced-targeting/components/add-filter-modal.tsx index 04de7853e4..411c049197 100644 --- a/packages/ee/advancedTargeting/components/AddFilterModal.tsx +++ b/packages/ee/advanced-targeting/components/add-filter-modal.tsx @@ -5,9 +5,9 @@ import { FingerprintIcon, MonitorSmartphoneIcon, MousePointerClick, TagIcon, Use import React, { useMemo, useState } from "react"; import { cn } from "@formbricks/lib/cn"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { +import type { TActionClass } from "@formbricks/types/actionClasses"; +import type { TAttributeClass } from "@formbricks/types/attributeClasses"; +import type { TBaseFilter, TSegment, TSegmentAttributeFilter, @@ -17,14 +17,14 @@ import { Input } from "@formbricks/ui/Input"; import { Modal } from "@formbricks/ui/Modal"; import { TabBar } from "@formbricks/ui/TabBar"; -type TAddFilterModalProps = { +interface TAddFilterModalProps { open: boolean; setOpen: (open: boolean) => void; onAddFilter: (filter: TBaseFilter) => void; actionClasses: TActionClass[]; attributeClasses: TAttributeClass[]; segments: TSegment[]; -}; +} type TFilterType = "action" | "attribute" | "segment" | "device" | "person"; @@ -44,7 +44,7 @@ const handleAddFilter = ({ attributeClassName?: string; segmentId?: string; deviceType?: string; -}) => { +}): void => { if (type === "action") { if (!actionClassId) return; @@ -54,7 +54,7 @@ const handleAddFilter = ({ resource: { id: createId(), root: { - type: type, + type, actionClassId, }, qualifier: { @@ -122,7 +122,7 @@ const handleAddFilter = ({ resource: { id: createId(), root: { - type: type, + type, segmentId, }, qualifier: { @@ -145,7 +145,7 @@ const handleAddFilter = ({ resource: { id: createId(), root: { - type: type, + type, deviceType, }, qualifier: { @@ -160,19 +160,20 @@ const handleAddFilter = ({ } }; -type AttributeTabContentProps = { +interface AttributeTabContentProps { attributeClasses: TAttributeClass[]; onAddFilter: (filter: TBaseFilter) => void; setOpen: (open: boolean) => void; -}; +} -const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: AttributeTabContentProps) => { +function AttributeTabContent({ attributeClasses, onAddFilter, setOpen }: AttributeTabContentProps) { return (

Person

{ handleAddFilter({ type: "person", @@ -180,7 +181,16 @@ const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: Attribu setOpen, }); }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleAddFilter({ + type: "person", + onAddFilter, + setOpen, + }); + } + }}>

userId

@@ -192,7 +202,7 @@ const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: Attribu

Attributes

- {attributeClasses?.length === 0 && ( + {attributeClasses.length === 0 && (

There are no attributes yet!

@@ -200,6 +210,8 @@ const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: Attribu {attributeClasses.map((attributeClass) => { return (
{ handleAddFilter({ type: "attribute", @@ -207,8 +219,7 @@ const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: Attribu setOpen, attributeClassName: attributeClass.name, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{attributeClass.name}

@@ -216,16 +227,16 @@ const AttributeTabContent = ({ attributeClasses, onAddFilter, setOpen }: Attribu })}
); -}; +} -export const AddFilterModal = ({ +export function AddFilterModal({ onAddFilter, open, setOpen, actionClasses, attributeClasses, segments, -}: TAddFilterModalProps) => { +}: TAddFilterModalProps) { const [activeTabId, setActiveTabId] = useState("all"); const [searchValue, setSearchValue] = useState(""); @@ -241,15 +252,12 @@ export const AddFilterModal = ({ { id: "devices", label: "Devices", icon: }, ]; - // eslint-disable-next-line react-hooks/exhaustive-deps const devices = [ { id: "phone", name: "Phone" }, { id: "desktop", name: "Desktop" }, ]; const actionClassesFiltered = useMemo(() => { - if (!actionClasses) return []; - if (!searchValue) return actionClasses; return actionClasses.filter((actionClass) => @@ -313,7 +321,7 @@ export const AddFilterModal = ({ const getAllTabContent = () => { return ( <> - {allFiltersFiltered?.every((filterArr) => { + {allFiltersFiltered.every((filterArr) => { return ( filterArr.actions.length === 0 && filterArr.attributes.length === 0 && @@ -321,11 +329,11 @@ export const AddFilterModal = ({ filterArr.devices.length === 0 && filterArr.personAttributes.length === 0 ); - }) && ( + }) ? (

There are no filters yet!

- )} + ) : null} {allFiltersFiltered.map((filters) => { return ( @@ -333,6 +341,8 @@ export const AddFilterModal = ({ {filters.actions.map((actionClass) => { return (
{ handleAddFilter({ type: "action", @@ -340,8 +350,7 @@ export const AddFilterModal = ({ setOpen, actionClassId: actionClass.id, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{actionClass.name}

@@ -351,6 +360,7 @@ export const AddFilterModal = ({ {filters.attributes.map((attributeClass) => { return (
{ handleAddFilter({ type: "attribute", @@ -358,8 +368,7 @@ export const AddFilterModal = ({ setOpen, attributeClassName: attributeClass.name, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{attributeClass.name}

@@ -369,14 +378,14 @@ export const AddFilterModal = ({ {filters.personAttributes.map((personAttribute) => { return (
{ handleAddFilter({ type: "person", onAddFilter, setOpen, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{personAttribute.name}

@@ -386,6 +395,7 @@ export const AddFilterModal = ({ {filters.segments.map((segment) => { return (
{ handleAddFilter({ type: "segment", @@ -393,8 +403,7 @@ export const AddFilterModal = ({ setOpen, segmentId: segment.id, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{segment.title}

@@ -403,8 +412,8 @@ export const AddFilterModal = ({ {filters.devices.map((deviceType) => (
{ handleAddFilter({ type: "device", @@ -427,7 +436,7 @@ export const AddFilterModal = ({ const getActionsTabContent = () => { return ( <> - {actionClassesFiltered?.length === 0 && ( + {actionClassesFiltered.length === 0 && (

There are no actions yet!

@@ -435,6 +444,7 @@ export const AddFilterModal = ({ {actionClassesFiltered.map((actionClass) => { return (
{ handleAddFilter({ type: "action", @@ -442,8 +452,7 @@ export const AddFilterModal = ({ setOpen, actionClassId: actionClass.id, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{actionClass.name}

@@ -466,16 +475,17 @@ export const AddFilterModal = ({ const getSegmentsTabContent = () => { return ( <> - {segmentsFiltered?.length === 0 && ( + {segmentsFiltered.length === 0 && (

You currently have no saved segments.

)} {segmentsFiltered - ?.filter((segment) => !segment.isPrivate) - ?.map((segment) => { + .filter((segment) => !segment.isPrivate) + .map((segment) => { return (
{ handleAddFilter({ type: "segment", @@ -483,8 +493,7 @@ export const AddFilterModal = ({ setOpen, segmentId: segment.id, }); - }} - className="flex cursor-pointer items-center gap-4 rounded-lg px-2 py-1 text-sm hover:bg-slate-50"> + }}>

{segment.title}

@@ -499,8 +508,8 @@ export const AddFilterModal = ({
{deviceTypesFiltered.map((deviceType) => (
{ handleAddFilter({ type: "device", @@ -542,14 +551,20 @@ export const AddFilterModal = ({ return ( + setOpen={setOpen}>
- setSearchValue(e.target.value)} /> - + { + setSearchValue(e.target.value); + }} + placeholder="Browse filters..." + /> +
@@ -557,4 +572,4 @@ export const AddFilterModal = ({
); -}; +} diff --git a/packages/ee/advancedTargeting/components/AdvancedTargetingCard.tsx b/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx similarity index 75% rename from packages/ee/advancedTargeting/components/AdvancedTargetingCard.tsx rename to packages/ee/advanced-targeting/components/advanced-targeting-card.tsx index a9ef735f01..94aeff3638 100644 --- a/packages/ee/advancedTargeting/components/AdvancedTargetingCard.tsx +++ b/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx @@ -9,10 +9,15 @@ import toast from "react-hot-toast"; import { cn } from "@formbricks/lib/cn"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { TBaseFilter, TSegment, TSegmentCreateInput, TSegmentUpdateInput } from "@formbricks/types/segment"; -import { TSurvey } from "@formbricks/types/surveys"; +import type { TActionClass } from "@formbricks/types/actionClasses"; +import type { TAttributeClass } from "@formbricks/types/attributeClasses"; +import type { + TBaseFilter, + TSegment, + TSegmentCreateInput, + TSegmentUpdateInput, +} from "@formbricks/types/segment"; +import type { TSurvey } from "@formbricks/types/surveys"; import { AlertDialog } from "@formbricks/ui/AlertDialog"; import { Button } from "@formbricks/ui/Button"; import { LoadSegmentModal } from "@formbricks/ui/LoadSegmentModal"; @@ -28,8 +33,8 @@ import { updateSegmentAction, } from "../lib/actions"; import { ACTIONS_TO_EXCLUDE } from "../lib/constants"; -import { AddFilterModal } from "./AddFilterModal"; -import { SegmentEditor } from "./SegmentEditor"; +import { AddFilterModal } from "./add-filter-modal"; +import { SegmentEditor } from "./segment-editor"; interface UserTargetingAdvancedCardProps { localSurvey: TSurvey; @@ -41,7 +46,7 @@ interface UserTargetingAdvancedCardProps { initialSegment?: TSegment; } -export const AdvancedTargetingCard = ({ +export function AdvancedTargetingCard({ localSurvey, setLocalSurvey, environmentId, @@ -49,7 +54,7 @@ export const AdvancedTargetingCard = ({ attributeClasses, segments, initialSegment, -}: UserTargetingAdvancedCardProps) => { +}: UserTargetingAdvancedCardProps) { const router = useRouter(); const [open, setOpen] = useState(false); const [segment, setSegment] = useState(localSurvey.segment); @@ -58,7 +63,7 @@ export const AdvancedTargetingCard = ({ const [saveAsNewSegmentModalOpen, setSaveAsNewSegmentModalOpen] = useState(false); const [resetAllFiltersModalOpen, setResetAllFiltersModalOpen] = useState(false); const [loadSegmentModalOpen, setLoadSegmentModalOpen] = useState(false); - const [isSegmentEditorOpen, setIsSegmentEditorOpen] = useState(!!localSurvey.segment?.isPrivate); + const [isSegmentEditorOpen, setIsSegmentEditorOpen] = useState(Boolean(localSurvey.segment?.isPrivate)); const [segmentEditorViewOnly, setSegmentEditorViewOnly] = useState(true); const actionClasses = actionClassesProps.filter((actionClass) => { @@ -76,12 +81,12 @@ export const AdvancedTargetingCard = ({ useEffect(() => { setLocalSurvey((localSurveyOld) => ({ ...localSurveyOld, - segment: segment, + segment, })); }, [setLocalSurvey, segment]); const isSegmentUsedInOtherSurveys = useMemo( - () => (localSurvey?.segment ? localSurvey.segment?.surveys?.length > 1 : false), + () => (localSurvey.segment ? localSurvey.segment.surveys.length > 1 : false), [localSurvey.segment] ); @@ -97,10 +102,10 @@ export const AdvancedTargetingCard = ({ }; useEffect(() => { - if (!!segment && segment?.filters?.length > 0) { + if (segment && segment.filters.length > 0) { setOpen(true); } - }, [segment, segment?.filters?.length]); + }, [segment, segment?.filters.length]); useEffect(() => { if (localSurvey.type === "link") { @@ -110,7 +115,7 @@ export const AdvancedTargetingCard = ({ const handleAddFilterInGroup = (filter: TBaseFilter) => { const updatedSegment = structuredClone(segment); - if (updatedSegment?.filters?.length === 0) { + if (updatedSegment?.filters.length === 0) { updatedSegment.filters.push({ ...filter, connector: null, @@ -144,7 +149,7 @@ export const AdvancedTargetingCard = ({ const handleSaveSegment = async (data: TSegmentUpdateInput) => { try { if (!segment) throw new Error("Invalid segment"); - await updateSegmentAction(environmentId, segment?.id, data); + await updateSegmentAction(environmentId, segment.id, data); toast.success("Segment saved successfully"); setIsSegmentEditorOpen(false); @@ -166,19 +171,23 @@ export const AdvancedTargetingCard = ({ return null; // Hide card completely } + if (!segment) { + throw new Error("Survey segment is missing"); + } + return ( + open={open}>
@@ -194,37 +203,37 @@ export const AdvancedTargetingCard = ({
- {!!segment && ( + {Boolean(segment) && ( )} {isSegmentEditorOpen ? (
- {!!segment?.filters?.length && ( + {Boolean(segment?.filters.length) && (
)} @@ -232,27 +241,30 @@ export const AdvancedTargetingCard = ({
- - {isSegmentEditorOpen && !segment?.isPrivate && ( + {isSegmentEditorOpen && !segment?.isPrivate ? ( - )} + ) : null} - {isSegmentEditorOpen && !segment?.isPrivate && ( + {isSegmentEditorOpen && !segment?.isPrivate ? ( - )} + ) : null}
- <> - { - handleAddFilterInGroup(filter); - }} - open={addFilterModalOpen} - setOpen={setAddFilterModalOpen} - actionClasses={actionClasses} - attributeClasses={attributeClasses} - segments={segments} + { + handleAddFilterInGroup(filter); + }} + open={addFilterModalOpen} + segments={segments} + setOpen={setAddFilterModalOpen} + /> + {Boolean(segment) && ( + - {!!segment && ( - - )} - + )}
) : (
- {segmentEditorViewOnly && segment && ( + {segmentEditorViewOnly && segment ? (
- )} + ) : null}
- {isSegmentUsedInOtherSurveys && ( - - )} + ) : null} {!isSegmentUsedInOtherSurveys && ( )}
- {isSegmentUsedInOtherSurveys && ( + {isSegmentUsedInOtherSurveys ? (

This segment is used in other surveys. Make changes{" "} + target="_blank"> here.

- )} + ) : null}
)}
- - {!segment?.isPrivate && !!segment?.filters?.length && ( - )} - {isSegmentEditorOpen && !!segment?.filters?.length && ( + {isSegmentEditorOpen && Boolean(segment?.filters.length) ? ( - )} + ) : null} { - setResetAllFiltersModalOpen(false); - }} confirmBtnLabel="Remove all filters" + declineBtnLabel="Cancel" + headerText="Are you sure?" + mainText="This action resets all filters in this survey." onConfirm={async () => { const segment = await handleResetAllFilters(); if (segment) { @@ -407,10 +426,15 @@ export const AdvancedTargetingCard = ({ router.refresh(); } }} + onDecline={() => { + setResetAllFiltersModalOpen(false); + }} + open={resetAllFiltersModalOpen} + setOpen={setResetAllFiltersModalOpen} />
); -}; +} diff --git a/packages/ee/advancedTargeting/components/CreateSegmentModal.tsx b/packages/ee/advanced-targeting/components/create-segment-modal.tsx similarity index 87% rename from packages/ee/advancedTargeting/components/CreateSegmentModal.tsx rename to packages/ee/advanced-targeting/components/create-segment-modal.tsx index b91707731e..af214c731f 100644 --- a/packages/ee/advancedTargeting/components/CreateSegmentModal.tsx +++ b/packages/ee/advanced-targeting/components/create-segment-modal.tsx @@ -6,30 +6,31 @@ import { useMemo, useState } from "react"; import toast from "react-hot-toast"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { TBaseFilter, TSegment, ZSegmentFilters } from "@formbricks/types/segment"; +import type { TActionClass } from "@formbricks/types/actionClasses"; +import type { TAttributeClass } from "@formbricks/types/attributeClasses"; +import type { TBaseFilter, TSegment } from "@formbricks/types/segment"; +import { ZSegmentFilters } from "@formbricks/types/segment"; import { Button } from "@formbricks/ui/Button"; import { Input } from "@formbricks/ui/Input"; import { Modal } from "@formbricks/ui/Modal"; import { createSegmentAction } from "../lib/actions"; -import { AddFilterModal } from "./AddFilterModal"; -import { SegmentEditor } from "./SegmentEditor"; +import { AddFilterModal } from "./add-filter-modal"; +import { SegmentEditor } from "./segment-editor"; -type TCreateSegmentModalProps = { +interface TCreateSegmentModalProps { environmentId: string; segments: TSegment[]; attributeClasses: TAttributeClass[]; actionClasses: TActionClass[]; -}; +} -export const CreateSegmentModal = ({ +export function CreateSegmentModal({ environmentId, actionClasses, attributeClasses, segments, -}: TCreateSegmentModalProps) => { +}: TCreateSegmentModalProps) { const router = useRouter(); const initialSegmentState = { title: "", @@ -55,13 +56,13 @@ export const CreateSegmentModal = ({ const handleAddFilterInGroup = (filter: TBaseFilter) => { const updatedSegment = structuredClone(segment); - if (updatedSegment?.filters?.length === 0) { + if (updatedSegment.filters.length === 0) { updatedSegment.filters.push({ ...filter, connector: null, }); } else { - updatedSegment?.filters.push(filter); + updatedSegment.filters.push(filter); } setSegment(updatedSegment); @@ -121,18 +122,24 @@ export const CreateSegmentModal = ({ return ( <> - { handleResetState(); }} - noPadding - closeOnOutsideClick={false} - className="md:w-full" size="lg">
@@ -157,14 +164,14 @@ export const CreateSegmentModal = ({
{ setSegment((prev) => ({ ...prev, title: e.target.value, })); }} - className="w-auto" + placeholder="Ex. Power Users" />
@@ -172,20 +179,20 @@ export const CreateSegmentModal = ({
{ setSegment((prev) => ({ ...prev, description: e.target.value, })); }} + placeholder="Ex. Fully activated recurring users" />
- {segment?.filters?.length === 0 && ( + {segment.filters.length === 0 && (

Add your first filter to get started

@@ -193,53 +200,55 @@ export const CreateSegmentModal = ({ )} { handleAddFilterInGroup(filter); }} open={addFilterModalOpen} - setOpen={setAddFilterModalOpen} - actionClasses={actionClasses} - attributeClasses={attributeClasses} segments={segments} + setOpen={setAddFilterModalOpen} />
@@ -249,4 +258,4 @@ export const CreateSegmentModal = ({ ); -}; +} diff --git a/packages/ee/advanced-targeting/components/segment-editor.tsx b/packages/ee/advanced-targeting/components/segment-editor.tsx new file mode 100644 index 0000000000..42045a188a --- /dev/null +++ b/packages/ee/advanced-targeting/components/segment-editor.tsx @@ -0,0 +1,266 @@ +import { MoreVertical, Trash2 } from "lucide-react"; +import { useState } from "react"; + +import { cn } from "@formbricks/lib/cn"; +import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; +import { + addFilterBelow, + addFilterInGroup, + createGroupFromResource, + deleteResource, + isResourceFilter, + moveResource, + toggleGroupConnector, +} from "@formbricks/lib/segment/utils"; +import type { TActionClass } from "@formbricks/types/actionClasses"; +import type { TAttributeClass } from "@formbricks/types/attributeClasses"; +import type { TBaseFilter, TBaseFilters, TSegment, TSegmentConnector } from "@formbricks/types/segment"; +import { Button } from "@formbricks/ui/Button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@formbricks/ui/DropdownMenu"; + +import { AddFilterModal } from "./add-filter-modal"; +import { SegmentFilter } from "./segment-filter"; + +interface TSegmentEditorProps { + group: TBaseFilters; + environmentId: string; + segment: TSegment; + segments: TSegment[]; + actionClasses: TActionClass[]; + attributeClasses: TAttributeClass[]; + setSegment: React.Dispatch>; + viewOnly?: boolean; +} + +export function SegmentEditor({ + group, + environmentId, + setSegment, + segment, + actionClasses, + attributeClasses, + segments, + viewOnly = false, +}: TSegmentEditorProps) { + const [addFilterModalOpen, setAddFilterModalOpen] = useState(false); + const [addFilterModalOpenedFromBelow, setAddFilterModalOpenedFromBelow] = useState(false); + + const handleAddFilterBelow = (resourceId: string, filter: TBaseFilter) => { + const localSegmentCopy = structuredClone(segment); + + if (localSegmentCopy.filters) { + addFilterBelow(localSegmentCopy.filters, resourceId, filter); + } + + setSegment(localSegmentCopy); + }; + + const handleCreateGroup = (resourceId: string) => { + const localSegmentCopy = structuredClone(segment); + if (localSegmentCopy.filters) { + createGroupFromResource(localSegmentCopy.filters, resourceId); + } + + setSegment(localSegmentCopy); + }; + + const handleMoveResource = (resourceId: string, direction: "up" | "down") => { + const localSegmentCopy = structuredClone(segment); + if (localSegmentCopy.filters) { + moveResource(localSegmentCopy.filters, resourceId, direction); + } + + setSegment(localSegmentCopy); + }; + + const handleDeleteResource = (resourceId: string) => { + const localSegmentCopy = structuredClone(segment); + + if (localSegmentCopy.filters) { + deleteResource(localSegmentCopy.filters, resourceId); + } + + setSegment(localSegmentCopy); + }; + + const handleToggleGroupConnector = (groupId: string, newConnectorValue: TSegmentConnector) => { + const localSegmentCopy = structuredClone(segment); + if (localSegmentCopy.filters) { + toggleGroupConnector(localSegmentCopy.filters, groupId, newConnectorValue); + } + + setSegment(localSegmentCopy); + }; + + const onConnectorChange = (groupId: string, connector: TSegmentConnector) => { + if (!connector) return; + + if (connector === "and") { + handleToggleGroupConnector(groupId, "or"); + } else { + handleToggleGroupConnector(groupId, "and"); + } + }; + + const handleAddFilterInGroup = (groupId: string, filter: TBaseFilter) => { + const localSegmentCopy = structuredClone(segment); + + if (localSegmentCopy.filters) { + addFilterInGroup(localSegmentCopy.filters, groupId, filter); + } + setSegment(localSegmentCopy); + }; + + return ( +
+ {group.map((groupItem) => { + const { connector, resource, id: groupId } = groupItem; + + if (isResourceFilter(resource)) { + return ( + { + handleCreateGroup(filterId); + }} + onDeleteFilter={(filterId: string) => { + handleDeleteResource(filterId); + }} + onMoveFilter={(filterId: string, direction: "up" | "down") => { + handleMoveResource(filterId, direction); + }} + resource={resource} + segment={segment} + segments={segments} + setSegment={setSegment} + viewOnly={viewOnly} + /> + ); + } + return ( +
+
+
+ { + if (viewOnly) return; + onConnectorChange(groupId, connector); + }}> + {connector ? connector : "Where"} + +
+ +
+ + +
+ +
+ + { + if (addFilterModalOpenedFromBelow) { + handleAddFilterBelow(groupId, filter); + setAddFilterModalOpenedFromBelow(false); + } else { + handleAddFilterInGroup(groupId, filter); + } + }} + open={addFilterModalOpen} + segments={segments} + setOpen={setAddFilterModalOpen} + /> +
+ +
+ + + + + + + { + setAddFilterModalOpenedFromBelow(true); + setAddFilterModalOpen(true); + }}> + Add filter below + + + { + handleCreateGroup(groupId); + }}> + Create group + + + { + handleMoveResource(groupId, "up"); + }}> + Move up + + + { + if (viewOnly) return; + handleMoveResource(groupId, "down"); + }}> + Move down + + + + + +
+
+
+ ); + })} +
+ ); +} diff --git a/packages/ee/advancedTargeting/components/SegmentFilter.tsx b/packages/ee/advanced-targeting/components/segment-filter.tsx similarity index 89% rename from packages/ee/advancedTargeting/components/SegmentFilter.tsx rename to packages/ee/advanced-targeting/components/segment-filter.tsx index 24583251bf..5813b5dc87 100644 --- a/packages/ee/advancedTargeting/components/SegmentFilter.tsx +++ b/packages/ee/advanced-targeting/components/segment-filter.tsx @@ -27,15 +27,9 @@ import { updateSegmentIdInFilter, } from "@formbricks/lib/segment/utils"; import { isCapitalized } from "@formbricks/lib/utils/strings"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { - ACTION_METRICS, - ARITHMETIC_OPERATORS, - ATTRIBUTE_OPERATORS, - BASE_OPERATORS, - DEVICE_OPERATORS, - PERSON_OPERATORS, +import type { TActionClass } from "@formbricks/types/actionClasses"; +import type { TAttributeClass } from "@formbricks/types/attributeClasses"; +import type { TActionMetric, TArithmeticOperator, TAttributeOperator, @@ -53,6 +47,14 @@ import { TSegmentPersonFilter, TSegmentSegmentFilter, } from "@formbricks/types/segment"; +import { + ACTION_METRICS, + ARITHMETIC_OPERATORS, + ATTRIBUTE_OPERATORS, + BASE_OPERATORS, + DEVICE_OPERATORS, + PERSON_OPERATORS, +} from "@formbricks/types/segment"; import { Button } from "@formbricks/ui/Button"; import { DropdownMenu, @@ -63,9 +65,9 @@ import { import { Input } from "@formbricks/ui/Input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select"; -import { AddFilterModal } from "./AddFilterModal"; +import { AddFilterModal } from "./add-filter-modal"; -type TSegmentFilterProps = { +interface TSegmentFilterProps { connector: TSegmentConnector; resource: TSegmentFilter; environmentId: string; @@ -79,9 +81,9 @@ type TSegmentFilterProps = { onDeleteFilter: (filterId: string) => void; onMoveFilter: (filterId: string, direction: "up" | "down") => void; viewOnly?: boolean; -}; +} -const SegmentFilterItemConnector = ({ +function SegmentFilterItemConnector({ connector, segment, setSegment, @@ -93,7 +95,7 @@ const SegmentFilterItemConnector = ({ setSegment: (segment: TSegment) => void; filterId: string; viewOnly?: boolean; -}) => { +}) { const updateLocalSurvey = (newConnector: TSegmentConnector) => { const updatedSegment = structuredClone(segment); if (updatedSegment.filters) { @@ -116,18 +118,18 @@ const SegmentFilterItemConnector = ({ return (
{ if (viewOnly) return; onConnectorChange(); }}> - {!!connector ? connector : "Where"} + {connector ? connector : "Where"}
); -}; +} -const SegmentFilterItemContextMenu = ({ +function SegmentFilterItemContextMenu({ filterId, onAddFilterBelow, onCreateGroup, @@ -141,7 +143,7 @@ const SegmentFilterItemContextMenu = ({ onDeleteFilter: (filterId: string) => void; onMoveFilter: (filterId: string, direction: "up" | "down") => void; viewOnly?: boolean; -}) => { +}) { return (
@@ -150,27 +152,47 @@ const SegmentFilterItemContextMenu = ({ - onAddFilterBelow()}>Add filter below + { + onAddFilterBelow(); + }}> + Add filter below + - onCreateGroup(filterId)}>Create group - onMoveFilter(filterId, "up")}>Move up - onMoveFilter(filterId, "down")}>Move down + { + onCreateGroup(filterId); + }}> + Create group + + { + onMoveFilter(filterId, "up"); + }}> + Move up + + { + onMoveFilter(filterId, "down"); + }}> + Move down +
); -}; +} type TAttributeSegmentFilterProps = TSegmentFilterProps & { onAddFilterBelow: () => void; @@ -178,7 +200,7 @@ type TAttributeSegmentFilterProps = TSegmentFilterProps & { updateValueInLocalSurvey: (filterId: string, newValue: TSegmentFilterValue) => void; }; -const AttributeSegmentFilter = ({ +function AttributeSegmentFilter({ connector, resource, onAddFilterBelow, @@ -190,7 +212,7 @@ const AttributeSegmentFilter = ({ setSegment, attributeClasses, viewOnly, -}: TAttributeSegmentFilterProps) => { +}: TAttributeSegmentFilterProps) { const { attributeClassName } = resource.root; const operatorText = convertOperatorToText(resource.qualifier.operator); @@ -218,7 +240,7 @@ const AttributeSegmentFilter = ({ }; }); - const attributeClass = attributeClasses?.find((attrClass) => attrClass?.name === attributeClassName)?.name; + const attributeClass = attributeClasses.find((attrClass) => attrClass.name === attributeClassName)?.name; const updateOperatorInLocalSurvey = (filterId: string, newOperator: TAttributeOperator) => { const updatedSegment = structuredClone(segment); @@ -270,20 +292,20 @@ const AttributeSegmentFilter = ({ return (
{ if (viewOnly) return; checkValueAndUpdate(e); }} - className={cn("w-auto bg-white", valueError && "border border-red-500 focus:border-red-500")} + value={resource.value} /> - {valueError && ( + {valueError ? (

{valueError}

- )} + ) : null}
)} @@ -358,7 +380,7 @@ const AttributeSegmentFilter = ({ />
); -}; +} type TPersonSegmentFilterProps = TSegmentFilterProps & { onAddFilterBelow: () => void; @@ -366,7 +388,7 @@ type TPersonSegmentFilterProps = TSegmentFilterProps & { updateValueInLocalSurvey: (filterId: string, newValue: TSegmentFilterValue) => void; }; -const PersonSegmentFilter = ({ +function PersonSegmentFilter({ connector, resource, onAddFilterBelow, @@ -377,7 +399,7 @@ const PersonSegmentFilter = ({ segment, setSegment, viewOnly, -}: TPersonSegmentFilterProps) => { +}: TPersonSegmentFilterProps) { const { personIdentifier } = resource.root; const operatorText = convertOperatorToText(resource.qualifier.operator); @@ -455,20 +477,20 @@ const PersonSegmentFilter = ({ return (
{ if (viewOnly) return; checkValueAndUpdate(e); }} - className={cn("w-auto bg-white", valueError && "border border-red-500 focus:border-red-500")} + value={resource.value} /> - {valueError && ( + {valueError ? (

{valueError}

- )} + ) : null}
)} @@ -538,14 +560,14 @@ const PersonSegmentFilter = ({ />
); -}; +} type TActionSegmentFilterProps = TSegmentFilterProps & { onAddFilterBelow: () => void; resource: TSegmentActionFilter; updateValueInLocalSurvey: (filterId: string, newValue: TSegmentFilterValue) => void; }; -const ActionSegmentFilter = ({ +function ActionSegmentFilter({ connector, resource, segment, @@ -557,7 +579,7 @@ const ActionSegmentFilter = ({ updateValueInLocalSurvey, actionClasses, viewOnly, -}: TActionSegmentFilterProps) => { +}: TActionSegmentFilterProps) { const { actionClassId } = resource.root; const operatorText = convertOperatorToText(resource.qualifier.operator); const qualifierMetric = resource.qualifier.metric; @@ -626,20 +648,20 @@ const ActionSegmentFilter = ({ return (
{ if (viewOnly) return; checkValueAndUpdate(e); }} - className={cn("w-auto bg-white", valueError && "border border-red-500 focus:border-red-500")} + value={resource.value} /> - {valueError && ( + {valueError ? (

{valueError}

- )} + ) : null}
); -}; +} type TSegmentSegmentFilterProps = TSegmentFilterProps & { onAddFilterBelow: () => void; resource: TSegmentSegmentFilter; }; -const SegmentSegmentFilter = ({ +function SegmentSegmentFilter({ connector, onAddFilterBelow, onCreateGroup, @@ -742,11 +764,11 @@ const SegmentSegmentFilter = ({ segments, setSegment, viewOnly, -}: TSegmentSegmentFilterProps) => { +}: TSegmentSegmentFilterProps) { const { segmentId } = resource.root; const operatorText = convertOperatorToText(resource.qualifier.operator); - const currentSegment = segments?.find((segment) => segment.id === segmentId); + const currentSegment = segments.find((segment) => segment.id === segmentId); const updateOperatorInSegment = (filterId: string, newOperator: TSegmentOperator) => { const updatedSegment = structuredClone(segment); @@ -780,9 +802,9 @@ const SegmentSegmentFilter = ({ return (
@@ -831,13 +855,13 @@ const SegmentSegmentFilter = ({ />
); -}; +} type TDeviceFilterProps = TSegmentFilterProps & { onAddFilterBelow: () => void; resource: TSegmentDeviceFilter; }; -const DeviceFilter = ({ +function DeviceFilter({ connector, onAddFilterBelow, onCreateGroup, @@ -847,7 +871,7 @@ const DeviceFilter = ({ segment, setSegment, viewOnly, -}: TDeviceFilterProps) => { +}: TDeviceFilterProps) { const { value } = resource; const operatorText = convertOperatorToText(resource.qualifier.operator); @@ -877,9 +901,9 @@ const DeviceFilter = ({ return (
{ + setSegment((prev) => ({ + ...prev, + title: e.target.value, + })); + }} + placeholder="Ex. Power Users" + value={segment.title} + /> +
+
+ +
+ +
+ { + setSegment((prev) => ({ + ...prev, + description: e.target.value, + })); + }} + placeholder="Ex. Power Users" + value={segment.description ?? ""} + /> +
+
+
+ + +
+ {segment.filters.length === 0 && ( +
+ +

Add your first filter to get started

+
+ )} + + + +
+ +
+ + { + handleAddFilterInGroup(filter); + }} + open={addFilterModalOpen} + segments={segments} + setOpen={setAddFilterModalOpen} + /> +
+ +
+ + + + {isDeleteSegmentModalOpen ? ( + + ) : null} +
+
+
+
+ ); +} diff --git a/packages/ee/advancedTargeting/lib/actions.ts b/packages/ee/advanced-targeting/lib/actions.ts similarity index 96% rename from packages/ee/advancedTargeting/lib/actions.ts rename to packages/ee/advanced-targeting/lib/actions.ts index bc14bf3a4c..3b1b1f6e3e 100644 --- a/packages/ee/advancedTargeting/lib/actions.ts +++ b/packages/ee/advanced-targeting/lib/actions.ts @@ -15,7 +15,8 @@ import { import { canUserAccessSurvey } from "@formbricks/lib/survey/auth"; import { loadNewSegmentInSurvey } from "@formbricks/lib/survey/service"; import { AuthorizationError } from "@formbricks/types/errors"; -import { TSegmentCreateInput, TSegmentUpdateInput, ZSegmentFilters } from "@formbricks/types/segment"; +import type { TSegmentCreateInput, TSegmentUpdateInput } from "@formbricks/types/segment"; +import { ZSegmentFilters } from "@formbricks/types/segment"; export const createSegmentAction = async ({ description, diff --git a/packages/ee/advancedTargeting/lib/constants.ts b/packages/ee/advanced-targeting/lib/constants.ts similarity index 100% rename from packages/ee/advancedTargeting/lib/constants.ts rename to packages/ee/advanced-targeting/lib/constants.ts diff --git a/packages/ee/advancedTargeting/components/SegmentEditor.tsx b/packages/ee/advancedTargeting/components/SegmentEditor.tsx deleted file mode 100644 index 584c28385f..0000000000 --- a/packages/ee/advancedTargeting/components/SegmentEditor.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { MoreVertical, Trash2 } from "lucide-react"; -import { useState } from "react"; - -import { cn } from "@formbricks/lib/cn"; -import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; -import { - addFilterBelow, - addFilterInGroup, - createGroupFromResource, - deleteResource, - isResourceFilter, - moveResource, - toggleGroupConnector, -} from "@formbricks/lib/segment/utils"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { TBaseFilter, TBaseFilters, TSegment, TSegmentConnector } from "@formbricks/types/segment"; -import { Button } from "@formbricks/ui/Button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@formbricks/ui/DropdownMenu"; - -import { AddFilterModal } from "./AddFilterModal"; -import { SegmentFilter } from "./SegmentFilter"; - -type TSegmentEditorProps = { - group: TBaseFilters; - environmentId: string; - segment: TSegment; - segments: TSegment[]; - actionClasses: TActionClass[]; - attributeClasses: TAttributeClass[]; - setSegment: React.Dispatch>; - viewOnly?: boolean; -}; - -export const SegmentEditor = ({ - group, - environmentId, - setSegment, - segment, - actionClasses, - attributeClasses, - segments, - viewOnly = false, -}: TSegmentEditorProps) => { - const [addFilterModalOpen, setAddFilterModalOpen] = useState(false); - const [addFilterModalOpenedFromBelow, setAddFilterModalOpenedFromBelow] = useState(false); - - const handleAddFilterBelow = (resourceId: string, filter: TBaseFilter) => { - const localSegmentCopy = structuredClone(segment); - - if (localSegmentCopy.filters) { - addFilterBelow(localSegmentCopy.filters, resourceId, filter); - } - - setSegment(localSegmentCopy); - }; - - const handleCreateGroup = (resourceId: string) => { - const localSegmentCopy = structuredClone(segment); - if (localSegmentCopy.filters) { - createGroupFromResource(localSegmentCopy.filters, resourceId); - } - - setSegment(localSegmentCopy); - }; - - const handleMoveResource = (resourceId: string, direction: "up" | "down") => { - const localSegmentCopy = structuredClone(segment); - if (localSegmentCopy.filters) { - moveResource(localSegmentCopy.filters, resourceId, direction); - } - - setSegment(localSegmentCopy); - }; - - const handleDeleteResource = (resourceId: string) => { - const localSegmentCopy = structuredClone(segment); - - if (localSegmentCopy.filters) { - deleteResource(localSegmentCopy.filters, resourceId); - } - - setSegment(localSegmentCopy); - }; - - const handleToggleGroupConnector = (groupId: string, newConnectorValue: TSegmentConnector) => { - const localSegmentCopy = structuredClone(segment); - if (localSegmentCopy.filters) { - toggleGroupConnector(localSegmentCopy.filters, groupId, newConnectorValue); - } - - setSegment(localSegmentCopy); - }; - - const onConnectorChange = (groupId: string, connector: TSegmentConnector) => { - if (!connector) return; - - if (connector === "and") { - handleToggleGroupConnector(groupId, "or"); - } else { - handleToggleGroupConnector(groupId, "and"); - } - }; - - const handleAddFilterInGroup = (groupId: string, filter: TBaseFilter) => { - const localSegmentCopy = structuredClone(segment); - - if (localSegmentCopy.filters) { - addFilterInGroup(localSegmentCopy.filters, groupId, filter); - } - setSegment(localSegmentCopy); - }; - - return ( -
- {group?.map((groupItem) => { - const { connector, resource, id: groupId } = groupItem; - - if (isResourceFilter(resource)) { - return ( - handleCreateGroup(filterId)} - onDeleteFilter={(filterId: string) => handleDeleteResource(filterId)} - onMoveFilter={(filterId: string, direction: "up" | "down") => - handleMoveResource(filterId, direction) - } - viewOnly={viewOnly} - /> - ); - } else { - return ( -
-
-
- { - if (viewOnly) return; - onConnectorChange(groupId, connector); - }}> - {!!connector ? connector : "Where"} - -
- -
- - -
- -
- - { - if (addFilterModalOpenedFromBelow) { - handleAddFilterBelow(groupId, filter); - setAddFilterModalOpenedFromBelow(false); - } else { - handleAddFilterInGroup(groupId, filter); - } - }} - actionClasses={actionClasses} - attributeClasses={attributeClasses} - segments={segments} - /> -
- -
- - - - - - - { - setAddFilterModalOpenedFromBelow(true); - setAddFilterModalOpen(true); - }}> - Add filter below - - - { - handleCreateGroup(groupId); - }}> - Create group - - - { - handleMoveResource(groupId, "up"); - }}> - Move up - - - { - if (viewOnly) return; - handleMoveResource(groupId, "down"); - }}> - Move down - - - - - -
-
-
- ); - } - })} -
- ); -}; diff --git a/packages/ee/advancedTargeting/components/SegmentSettings.tsx b/packages/ee/advancedTargeting/components/SegmentSettings.tsx deleted file mode 100644 index d69e9aa761..0000000000 --- a/packages/ee/advancedTargeting/components/SegmentSettings.tsx +++ /dev/null @@ -1,248 +0,0 @@ -"use client"; - -import { FilterIcon, Trash2 } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useMemo, useState } from "react"; -import toast from "react-hot-toast"; - -import { cn } from "@formbricks/lib/cn"; -import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; -import { TActionClass } from "@formbricks/types/actionClasses"; -import { TAttributeClass } from "@formbricks/types/attributeClasses"; -import { TBaseFilter, TSegment, TSegmentWithSurveyNames, ZSegmentFilters } from "@formbricks/types/segment"; -import { Button } from "@formbricks/ui/Button"; -import { ConfirmDeleteSegmentModal } from "@formbricks/ui/ConfirmDeleteSegmentModal"; -import { Input } from "@formbricks/ui/Input"; - -import { deleteSegmentAction, updateSegmentAction } from "../lib/actions"; -import { AddFilterModal } from "./AddFilterModal"; -import { SegmentEditor } from "./SegmentEditor"; - -type TSegmentSettingsTabProps = { - environmentId: string; - setOpen: (open: boolean) => void; - initialSegment: TSegmentWithSurveyNames; - segments: TSegment[]; - attributeClasses: TAttributeClass[]; - actionClasses: TActionClass[]; -}; - -export const SegmentSettings = ({ - environmentId, - initialSegment, - setOpen, - actionClasses, - attributeClasses, - segments, -}: TSegmentSettingsTabProps) => { - const router = useRouter(); - - const [addFilterModalOpen, setAddFilterModalOpen] = useState(false); - const [segment, setSegment] = useState(initialSegment); - - const [isUpdatingSegment, setIsUpdatingSegment] = useState(false); - const [isDeletingSegment, setIsDeletingSegment] = useState(false); - - const [isDeleteSegmentModalOpen, setIsDeleteSegmentModalOpen] = useState(false); - - const handleResetState = () => { - setSegment(initialSegment); - setOpen(false); - - router.refresh(); - }; - - const handleAddFilterInGroup = (filter: TBaseFilter) => { - const updatedSegment = structuredClone(segment); - if (updatedSegment?.filters?.length === 0) { - updatedSegment.filters.push({ - ...filter, - connector: null, - }); - } else { - updatedSegment?.filters.push(filter); - } - - setSegment(updatedSegment); - }; - - const handleUpdateSegment = async () => { - if (!segment.title) { - toast.error("Title is required"); - return; - } - - try { - setIsUpdatingSegment(true); - await updateSegmentAction(segment.environmentId, segment.id, { - title: segment.title, - description: segment.description ?? "", - isPrivate: segment.isPrivate, - filters: segment.filters, - }); - - setIsUpdatingSegment(false); - toast.success("Segment updated successfully!"); - } catch (err: any) { - const parsedFilters = ZSegmentFilters.safeParse(segment.filters); - if (!parsedFilters.success) { - toast.error("Invalid filters. Please check the filters and try again."); - } else { - toast.error("Something went wrong. Please try again."); - } - setIsUpdatingSegment(false); - return; - } - - setIsUpdatingSegment(false); - handleResetState(); - router.refresh(); - }; - - const handleDeleteSegment = async () => { - try { - setIsDeletingSegment(true); - await deleteSegmentAction(segment.environmentId, segment.id); - - setIsDeletingSegment(false); - toast.success("Segment deleted successfully!"); - handleResetState(); - } catch (err: any) { - toast.error("Something went wrong. Please try again."); - } - - setIsDeletingSegment(false); - }; - - const isSaveDisabled = useMemo(() => { - // check if title is empty - - if (!segment.title) { - return true; - } - - // parse the filters to check if they are valid - const parsedFilters = ZSegmentFilters.safeParse(segment.filters); - if (!parsedFilters.success) { - return true; - } - - return false; - }, [segment]); - - return ( - <> -
-
-
-
-
- -
- { - setSegment((prev) => ({ - ...prev, - title: e.target.value, - })); - }} - className="w-auto" - /> -
-
- -
- -
- { - setSegment((prev) => ({ - ...prev, - description: e.target.value, - })); - }} - className={cn("w-auto")} - /> -
-
-
- - -
- {segment?.filters?.length === 0 && ( -
- -

Add your first filter to get started

-
- )} - - - -
- -
- - { - handleAddFilterInGroup(filter); - }} - open={addFilterModalOpen} - setOpen={setAddFilterModalOpen} - actionClasses={actionClasses} - attributeClasses={attributeClasses} - segments={segments} - /> -
- -
- - - - {isDeleteSegmentModalOpen && ( - - )} -
-
-
-
- - ); -}; diff --git a/packages/ee/billing/api/stripe-webhook.ts b/packages/ee/billing/api/stripe-webhook.ts index 6402e9caa9..0a42c3ad99 100644 --- a/packages/ee/billing/api/stripe-webhook.ts +++ b/packages/ee/billing/api/stripe-webhook.ts @@ -3,21 +3,24 @@ import Stripe from "stripe"; import { STRIPE_API_VERSION } from "@formbricks/lib/constants"; import { env } from "@formbricks/lib/env"; -import { handleCheckoutSessionCompleted } from "../handlers/checkoutSessionCompleted"; -import { handleSubscriptionUpdatedOrCreated } from "../handlers/subscriptionCreatedOrUpdated"; -import { handleSubscriptionDeleted } from "../handlers/subscriptionDeleted"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - apiVersion: STRIPE_API_VERSION, -}); - -const webhookSecret: string = env.STRIPE_WEBHOOK_SECRET!; +import { handleCheckoutSessionCompleted } from "../handlers/checkout-session-completed"; +import { handleSubscriptionUpdatedOrCreated } from "../handlers/subscription-created-or-updated"; +import { handleSubscriptionDeleted } from "../handlers/subscription-deleted"; export const webhookHandler = async (requestBody: string, stripeSignature: string) => { let event: Stripe.Event; + if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET) { + console.error("Stripe is not enabled, skipping webhook"); + return { status: 400, message: "Stripe is not enabled, skipping webhook" }; + } + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + try { - event = stripe.webhooks.constructEvent(requestBody, stripeSignature, webhookSecret); + event = stripe.webhooks.constructEvent(requestBody, stripeSignature, env.STRIPE_WEBHOOK_SECRET); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; if (err! instanceof Error) console.error(err); diff --git a/packages/ee/billing/handlers/checkoutSessionCompleted.ts b/packages/ee/billing/handlers/checkout-session-completed.ts similarity index 91% rename from packages/ee/billing/handlers/checkoutSessionCompleted.ts rename to packages/ee/billing/handlers/checkout-session-completed.ts index 070c620b50..53b8229f32 100644 --- a/packages/ee/billing/handlers/checkoutSessionCompleted.ts +++ b/packages/ee/billing/handlers/checkout-session-completed.ts @@ -10,15 +10,17 @@ import { } from "@formbricks/lib/organization/service"; import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants"; -import { reportUsage } from "../lib/reportUsage"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - // https://github.com/stripe/stripe-node#configuration - apiVersion: STRIPE_API_VERSION, -}); +import { reportUsage } from "../lib/report-usage"; export const handleCheckoutSessionCompleted = async (event: Stripe.Event) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + const checkoutSession = event.data.object as Stripe.Checkout.Session; + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + const stripeSubscriptionObject = await stripe.subscriptions.retrieve( checkoutSession.subscription as string ); @@ -29,7 +31,7 @@ export const handleCheckoutSessionCompleted = async (event: Stripe.Event) => { const organization = await getOrganization(stripeSubscriptionObject.metadata.organizationId); if (!organization) throw new Error("Organization not found."); - let updatedFeatures = organization.billing.features; + const updatedFeatures = organization.billing.features; for (const item of stripeSubscriptionObject.items.data) { const product = await stripe.products.retrieve(item.price.product as string); diff --git a/packages/ee/billing/handlers/subscriptionCreatedOrUpdated.ts b/packages/ee/billing/handlers/subscription-created-or-updated.ts similarity index 92% rename from packages/ee/billing/handlers/subscriptionCreatedOrUpdated.ts rename to packages/ee/billing/handlers/subscription-created-or-updated.ts index 440ec221b2..b4d862d5bf 100644 --- a/packages/ee/billing/handlers/subscriptionCreatedOrUpdated.ts +++ b/packages/ee/billing/handlers/subscription-created-or-updated.ts @@ -10,17 +10,18 @@ import { } from "@formbricks/lib/organization/service"; import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants"; -import { reportUsage } from "../lib/reportUsage"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - // https://github.com/stripe/stripe-node#configuration - apiVersion: STRIPE_API_VERSION, -}); +import { reportUsage } from "../lib/report-usage"; const isProductScheduled = async ( scheduledSubscriptions: Stripe.SubscriptionSchedule[], productName: StripeProductNames ) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + for (const scheduledSub of scheduledSubscriptions) { if (scheduledSub.phases && scheduledSub.phases.length > 0) { const firstPhase = scheduledSub.phases[0]; @@ -38,6 +39,12 @@ const isProductScheduled = async ( }; export const handleSubscriptionUpdatedOrCreated = async (event: Stripe.Event) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + const stripeSubscriptionObject = event.data.object as Stripe.Subscription; const organizationId = stripeSubscriptionObject.metadata.organizationId; @@ -52,12 +59,12 @@ export const handleSubscriptionUpdatedOrCreated = async (event: Stripe.Event) => const organization = await getOrganization(organizationId); if (!organization) throw new Error("Organization not found."); - let updatedFeatures = organization.billing.features; + const updatedFeatures = organization.billing.features; let scheduledSubscriptions: Stripe.SubscriptionSchedule[] = []; if (stripeSubscriptionObject.cancel_at_period_end) { const allScheduledSubscriptions = await stripe.subscriptionSchedules.list({ - customer: organization.billing.stripeCustomerId as string, + customer: organization.billing.stripeCustomerId!, }); scheduledSubscriptions = allScheduledSubscriptions.data.filter( (scheduledSub) => scheduledSub.status === "not_started" diff --git a/packages/ee/billing/handlers/subscriptionDeleted.ts b/packages/ee/billing/handlers/subscription-deleted.ts similarity index 88% rename from packages/ee/billing/handlers/subscriptionDeleted.ts rename to packages/ee/billing/handlers/subscription-deleted.ts index 1ffafc24b6..8f7acae72c 100644 --- a/packages/ee/billing/handlers/subscriptionDeleted.ts +++ b/packages/ee/billing/handlers/subscription-deleted.ts @@ -5,14 +5,15 @@ import { env } from "@formbricks/lib/env"; import { getOrganization, updateOrganization } from "@formbricks/lib/organization/service"; import { ProductFeatureKeys, StripeProductNames } from "../lib/constants"; -import { unsubscribeCoreAndAppSurveyFeatures, unsubscribeLinkSurveyProFeatures } from "../lib/downgradePlan"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - // https://github.com/stripe/stripe-node#configuration - apiVersion: STRIPE_API_VERSION, -}); +import { unsubscribeCoreAndAppSurveyFeatures, unsubscribeLinkSurveyProFeatures } from "../lib/downgrade-plan"; export const handleSubscriptionDeleted = async (event: Stripe.Event) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + const stripeSubscriptionObject = event.data.object as Stripe.Subscription; const organizationId = stripeSubscriptionObject.metadata.organizationId; if (!organizationId) { @@ -23,7 +24,7 @@ export const handleSubscriptionDeleted = async (event: Stripe.Event) => { const organization = await getOrganization(organizationId); if (!organization) throw new Error("Organization not found."); - let updatedFeatures = organization.billing.features; + const updatedFeatures = organization.billing.features; for (const item of stripeSubscriptionObject.items.data) { const product = await stripe.products.retrieve(item.price.product as string); diff --git a/packages/ee/billing/lib/createCustomerPortalSession.ts b/packages/ee/billing/lib/create-customer-portal-session.ts similarity index 66% rename from packages/ee/billing/lib/createCustomerPortalSession.ts rename to packages/ee/billing/lib/create-customer-portal-session.ts index 6a3459ff5b..2e68e3016d 100644 --- a/packages/ee/billing/lib/createCustomerPortalSession.ts +++ b/packages/ee/billing/lib/create-customer-portal-session.ts @@ -3,12 +3,13 @@ import Stripe from "stripe"; import { STRIPE_API_VERSION } from "@formbricks/lib/constants"; import { env } from "@formbricks/lib/env"; -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - // https://github.com/stripe/stripe-node#configuration - apiVersion: STRIPE_API_VERSION, -}); - export const createCustomerPortalSession = async (stripeCustomerId: string, returnUrl: string) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + const session = await stripe.billingPortal.sessions.create({ customer: stripeCustomerId, return_url: returnUrl, diff --git a/packages/ee/billing/lib/createSubscription.ts b/packages/ee/billing/lib/create-subscription.ts similarity index 84% rename from packages/ee/billing/lib/createSubscription.ts rename to packages/ee/billing/lib/create-subscription.ts index f2b2bf054a..8a17da9f21 100644 --- a/packages/ee/billing/lib/createSubscription.ts +++ b/packages/ee/billing/lib/create-subscription.ts @@ -4,13 +4,7 @@ import { STRIPE_API_VERSION, WEBAPP_URL } from "@formbricks/lib/constants"; import { env } from "@formbricks/lib/env"; import { getOrganization } from "@formbricks/lib/organization/service"; -import { StripePriceLookupKeys } from "./constants"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - apiVersion: STRIPE_API_VERSION, -}); - -const baseUrl = process.env.NODE_ENV === "production" ? WEBAPP_URL : "http://localhost:3000"; +import type { StripePriceLookupKeys } from "./constants"; export const getFirstOfNextMonthTimestamp = (): number => { const nextMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1); @@ -22,14 +16,20 @@ export const createSubscription = async ( environmentId: string, priceLookupKeys: StripePriceLookupKeys[] ) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + try { const organization = await getOrganization(organizationId); if (!organization) throw new Error("Organization not found."); - let isNewOrganization = + const isNewOrganization = !organization.billing.stripeCustomerId || !(await stripe.customers.retrieve(organization.billing.stripeCustomerId)); - let lineItems: { price: string; quantity?: number }[] = []; + const lineItems: { price: string; quantity?: number }[] = []; const prices = ( await stripe.prices.list({ @@ -50,8 +50,8 @@ export const createSubscription = async ( const session = await stripe.checkout.sessions.create({ mode: "subscription", line_items: lineItems, - success_url: `${baseUrl}/billing-confirmation?environmentId=${environmentId}`, - cancel_url: `${baseUrl}/environments/${environmentId}/settings/billing`, + success_url: `${WEBAPP_URL}/billing-confirmation?environmentId=${environmentId}`, + cancel_url: `${WEBAPP_URL}/environments/${environmentId}/settings/billing`, allow_promotion_codes: true, subscription_data: { billing_cycle_anchor: getFirstOfNextMonthTimestamp(), @@ -64,7 +64,7 @@ export const createSubscription = async ( } const existingSubscription = ( - (await stripe.customers.retrieve(organization.billing.stripeCustomerId as string, { + (await stripe.customers.retrieve(organization.billing.stripeCustomerId!, { expand: ["subscriptions"], })) as any ).subscriptions.data[0] as Stripe.Subscription; @@ -75,7 +75,7 @@ export const createSubscription = async ( // this is a case where the organization cancelled an already purchased product if (existingSubscription.cancel_at_period_end) { const allScheduledSubscriptions = await stripe.subscriptionSchedules.list({ - customer: organization.billing.stripeCustomerId as string, + customer: organization.billing.stripeCustomerId!, }); const scheduledSubscriptions = allScheduledSubscriptions.data.filter( (scheduledSub) => scheduledSub.status === "not_started" @@ -95,13 +95,12 @@ export const createSubscription = async ( const combinedLineItems = [...lineItems, ...existingItemsInScheduledSubscription]; - const uniqueItemsMap = combinedLineItems.reduce( - (acc, item) => { - acc[item.price] = item; // This will overwrite duplicate items based on price - return acc; - }, - {} as { [key: string]: { price: string; quantity?: number } } - ); + const uniqueItemsMap = combinedLineItems.reduce< + Record + >((acc, item) => { + acc[item.price] = item; // This will overwrite duplicate items based on price + return acc; + }, {}); const lineItemsForScheduledSubscription = Object.values(uniqueItemsMap); @@ -122,7 +121,7 @@ export const createSubscription = async ( // we create one since the current one with other products is expiring // so the new schedule only has the new product the organization has subscribed to await stripe.subscriptionSchedules.create({ - customer: organization.billing.stripeCustomerId as string, + customer: organization.billing.stripeCustomerId!, start_date: getFirstOfNextMonthTimestamp(), end_behavior: "release", phases: [ @@ -165,7 +164,7 @@ export const createSubscription = async ( // case where organization does not have a subscription but has a stripe customer id // so we just attach that to a new subscription await stripe.subscriptions.create({ - customer: organization.billing.stripeCustomerId as string, + customer: organization.billing.stripeCustomerId!, items: lineItems, billing_cycle_anchor: getFirstOfNextMonthTimestamp(), metadata: { organizationId }, @@ -184,7 +183,7 @@ export const createSubscription = async ( status: 500, data: "Something went wrong!", newPlan: true, - url: `${baseUrl}/environments/${environmentId}/settings/billing`, + url: `${WEBAPP_URL}/environments/${environmentId}/settings/billing`, }; } }; diff --git a/packages/ee/billing/lib/downgradePlan.ts b/packages/ee/billing/lib/downgrade-plan.ts similarity index 100% rename from packages/ee/billing/lib/downgradePlan.ts rename to packages/ee/billing/lib/downgrade-plan.ts diff --git a/packages/ee/billing/lib/removeSubscription.ts b/packages/ee/billing/lib/remove-subscription.ts similarity index 84% rename from packages/ee/billing/lib/removeSubscription.ts rename to packages/ee/billing/lib/remove-subscription.ts index 51a6723bab..4beeaf6e7f 100644 --- a/packages/ee/billing/lib/removeSubscription.ts +++ b/packages/ee/billing/lib/remove-subscription.ts @@ -4,22 +4,32 @@ import { STRIPE_API_VERSION, WEBAPP_URL } from "@formbricks/lib/constants"; import { env } from "@formbricks/lib/env"; import { getOrganization, updateOrganization } from "@formbricks/lib/organization/service"; -import { StripePriceLookupKeys } from "./constants"; -import { getFirstOfNextMonthTimestamp } from "./createSubscription"; - -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - apiVersion: STRIPE_API_VERSION, -}); +import type { StripePriceLookupKeys } from "./constants"; +import { getFirstOfNextMonthTimestamp } from "./create-subscription"; const baseUrl = process.env.NODE_ENV === "production" ? WEBAPP_URL : "http://localhost:3000"; -const retrievePriceLookup = async (priceId: string) => (await stripe.prices.retrieve(priceId)).lookup_key; +const retrievePriceLookup = async (priceId: string) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + + return (await stripe.prices.retrieve(priceId)).lookup_key; +}; export const removeSubscription = async ( organizationId: string, environmentId: string, priceLookupKeys: StripePriceLookupKeys[] ) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + try { const organization = await getOrganization(organizationId); if (!organization) throw new Error("Organization not found."); @@ -30,7 +40,7 @@ export const removeSubscription = async ( const existingCustomer = (await stripe.customers.retrieve(organization.billing.stripeCustomerId, { expand: ["subscriptions"], })) as Stripe.Customer; - const existingSubscription = existingCustomer.subscriptions?.data[0] as Stripe.Subscription; + const existingSubscription = existingCustomer.subscriptions?.data[0]!; const allScheduledSubscriptions = await stripe.subscriptionSchedules.list({ customer: organization.billing.stripeCustomerId, @@ -93,7 +103,7 @@ export const removeSubscription = async ( await stripe.subscriptions.update(existingSubscription.id, { cancel_at_period_end: true }); - let updatedFeatures = organization.billing.features; + const updatedFeatures = organization.billing.features; for (const priceLookupKey of priceLookupKeys) { updatedFeatures[priceLookupKey as keyof typeof updatedFeatures].status = "cancelled"; } diff --git a/packages/ee/billing/lib/reportUsage.ts b/packages/ee/billing/lib/report-usage.ts similarity index 74% rename from packages/ee/billing/lib/reportUsage.ts rename to packages/ee/billing/lib/report-usage.ts index 41050dd289..2fdaff7faa 100644 --- a/packages/ee/billing/lib/reportUsage.ts +++ b/packages/ee/billing/lib/report-usage.ts @@ -5,16 +5,17 @@ import { env } from "@formbricks/lib/env"; import { ProductFeatureKeys } from "./constants"; -const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - // https://github.com/stripe/stripe-node#configuration - apiVersion: STRIPE_API_VERSION, -}); - export const reportUsage = async ( items: Stripe.SubscriptionItem[], lookupKey: ProductFeatureKeys, quantity: number ) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + const subscriptionItem = items.find( (subItem) => subItem.price.lookup_key === ProductFeatureKeys[lookupKey] ); @@ -25,7 +26,7 @@ export const reportUsage = async ( await stripe.subscriptionItems.createUsageRecord(subscriptionItem.id, { action: "set", - quantity: quantity, + quantity, timestamp: Math.floor(Date.now() / 1000), }); }; @@ -36,6 +37,12 @@ export const reportUsageToStripe = async ( lookupKey: ProductFeatureKeys, timestamp: number ) => { + if (!env.STRIPE_SECRET_KEY) throw new Error("Stripe is not enabled; STRIPE_SECRET_KEY is not set."); + + const stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: STRIPE_API_VERSION, + }); + try { const subscription = await stripe.subscriptions.list({ customer: stripeCustomerId, @@ -53,11 +60,11 @@ export const reportUsageToStripe = async ( const usageRecord = await stripe.subscriptionItems.createUsageRecord(subId, { action: "set", quantity: usage, - timestamp: timestamp, + timestamp, }); return { status: 200, data: usageRecord.quantity }; } catch (error) { - return { status: 500, data: "Something went wrong: " + error }; + return { status: 500, data: `Something went wrong: ${error}` }; } }; diff --git a/packages/ee/lib/service.ts b/packages/ee/lib/service.ts index dc105a43ac..26454f0c2b 100644 --- a/packages/ee/lib/service.ts +++ b/packages/ee/lib/service.ts @@ -2,12 +2,11 @@ import "server-only"; import axios from "axios"; +import { prisma } from "@formbricks/database"; import { cache, revalidateTag } from "@formbricks/lib/cache"; import { E2E_TESTING, ENTERPRISE_LICENSE_KEY, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { hashString } from "@formbricks/lib/hashString"; -import { TOrganization } from "@formbricks/types/organizations"; - -import { prisma } from "../../database/src"; +import type { TOrganization } from "@formbricks/types/organizations"; const hashedKey = ENTERPRISE_LICENSE_KEY ? hashString(ENTERPRISE_LICENSE_KEY) : undefined; const PREVIOUS_RESULTS_CACHE_TAG_KEY = `getPreviousResult-${hashedKey}` as const; @@ -86,7 +85,7 @@ export const getIsEnterpriseEdition = async (): Promise => { const response = await axios.post("https://ee.formbricks.com/api/licenses/check", { licenseKey: process.env.ENTERPRISE_LICENSE_KEY, - usage: { responseCount: responseCount }, + usage: { responseCount }, }); if (response.status === 200) { @@ -116,45 +115,44 @@ export const getIsEnterpriseEdition = async (): Promise => { if (isValid !== null) { await setPreviousResult({ active: isValid, lastChecked: new Date() }); return isValid; - } else { - // if result is undefined -> error - // if the last check was less than 72 hours, return the previous value: - if (new Date().getTime() - previousResult.lastChecked.getTime() <= 3 * 24 * 60 * 60 * 1000) { - return previousResult.active !== null ? previousResult.active : false; - } - - // if the last check was more than 72 hours, return false and log the error - console.error("Error while checking license: The license check failed"); - return false; } + // if result is undefined -> error + // if the last check was less than 72 hours, return the previous value: + if (new Date().getTime() - previousResult.lastChecked.getTime() <= 3 * 24 * 60 * 60 * 1000) { + return previousResult.active !== null ? previousResult.active : false; + } + + // if the last check was more than 72 hours, return false and log the error + console.error("Error while checking license: The license check failed"); + return false; }; export const getRemoveInAppBrandingPermission = (organization: TOrganization): boolean => { if (IS_FORMBRICKS_CLOUD) return organization.billing.features.inAppSurvey.status !== "inactive"; else if (!IS_FORMBRICKS_CLOUD) return true; - else return false; + return false; }; export const getRemoveLinkBrandingPermission = (organization: TOrganization): boolean => { if (IS_FORMBRICKS_CLOUD) return organization.billing.features.linkSurvey.status !== "inactive"; else if (!IS_FORMBRICKS_CLOUD) return true; - else return false; + return false; }; export const getRoleManagementPermission = async (organization: TOrganization): Promise => { if (IS_FORMBRICKS_CLOUD) return organization.billing.features.inAppSurvey.status !== "inactive"; else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); - else return false; + return false; }; export const getAdvancedTargetingPermission = async (organization: TOrganization): Promise => { if (IS_FORMBRICKS_CLOUD) return organization.billing.features.userTargeting.status !== "inactive"; else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); - else return false; + return false; }; export const getMultiLanguagePermission = async (organization: TOrganization): Promise => { if (IS_FORMBRICKS_CLOUD) return organization.billing.features.inAppSurvey.status !== "inactive"; else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); - else return false; + return false; }; diff --git a/packages/ee/multiLanguage/components/DefaultLanguageSelect.tsx b/packages/ee/multi-language/components/default-language-select.tsx similarity index 80% rename from packages/ee/multiLanguage/components/DefaultLanguageSelect.tsx rename to packages/ee/multi-language/components/default-language-select.tsx index 7d176cde90..0602d7adc3 100644 --- a/packages/ee/multiLanguage/components/DefaultLanguageSelect.tsx +++ b/packages/ee/multi-language/components/default-language-select.tsx @@ -1,9 +1,9 @@ -import { TLanguage, TProduct } from "@formbricks/types/product"; +import type { TLanguage, TProduct } from "@formbricks/types/product"; import { DefaultTag } from "@formbricks/ui/DefaultTag"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select"; -import { getLanguageLabel } from "../lib/isoLanguages"; -import { ConfirmationModalProps } from "./MultiLanguageCard"; +import { getLanguageLabel } from "../lib/iso-languages"; +import type { ConfirmationModalProps } from "./multi-language-card"; interface DefaultLanguageSelectProps { defaultLanguage?: TLanguage; @@ -12,39 +12,41 @@ interface DefaultLanguageSelectProps { setConfirmationModalInfo: (confirmationModal: ConfirmationModalProps) => void; } -export const DefaultLanguageSelect = ({ +export function DefaultLanguageSelect({ defaultLanguage, handleDefaultLanguageChange, product, setConfirmationModalInfo, -}: DefaultLanguageSelectProps) => { +}: DefaultLanguageSelectProps) { return (

1. Choose the default language for this survey:

{ + onLanguageChange({ ...language, alias: e.target.value }); + }} placeholder="e.g. en_us" - onChange={(e) => onLanguageChange({ ...language, alias: e.target.value })} + value={language.alias || ""} /> - {language.id !== "new" && isEditing && ( - - )} + ) : null}
); -}; +} diff --git a/packages/ee/multiLanguage/components/LanguageSelect.tsx b/packages/ee/multi-language/components/language-select.tsx similarity index 75% rename from packages/ee/multiLanguage/components/LanguageSelect.tsx rename to packages/ee/multi-language/components/language-select.tsx index 8bb3a9b6ba..3cb4930eff 100644 --- a/packages/ee/multiLanguage/components/LanguageSelect.tsx +++ b/packages/ee/multi-language/components/language-select.tsx @@ -2,11 +2,12 @@ import { ChevronDown } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside"; -import { TLanguage } from "@formbricks/types/product"; +import type { TLanguage } from "@formbricks/types/product"; import { Button } from "@formbricks/ui/Button"; import { Input } from "@formbricks/ui/Input"; -import { TIso639Language, iso639Languages } from "../lib/isoLanguages"; +import type { TIso639Language } from "../lib/iso-languages"; +import { iso639Languages } from "../lib/iso-languages"; interface LanguageSelectProps { language: TLanguage; @@ -14,7 +15,7 @@ interface LanguageSelectProps { disabled: boolean; } -export const LanguageSelect = ({ language, onLanguageChange, disabled }: LanguageSelectProps) => { +export function LanguageSelect({ language, onLanguageChange, disabled }: LanguageSelectProps) { const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [selectedOption, setSelectedOption] = useState( @@ -29,11 +30,13 @@ export const LanguageSelect = ({ language, onLanguageChange, disabled }: Languag setIsOpen(!isOpen); }; - useClickOutside(languageSelectRef, () => setIsOpen(false)); + useClickOutside(languageSelectRef, () => { + setIsOpen(false); + }); const handleOptionSelect = (option: TIso639Language) => { setSelectedOption(option); - onLanguageChange({ ...language, code: option?.alpha2 || "" }); + onLanguageChange({ ...language, code: option.alpha2 || "" }); setIsOpen(false); }; @@ -49,29 +52,33 @@ export const LanguageSelect = ({ language, onLanguageChange, disabled }: Languag return (
setSearchTerm(e.target.value)} autoComplete="off" + onChange={(e) => { + setSearchTerm(e.target.value); + }} + placeholder="Search items" ref={inputRef} + type="text" + value={searchTerm} />
{filteredItems.map((item, index) => (
handleOptionSelect(item)} - className="block cursor-pointer rounded-md px-4 py-2 text-gray-700 hover:bg-gray-100 active:bg-blue-100"> + onClick={() => { + handleOptionSelect(item); + }}> {item.english}
))} @@ -79,4 +86,4 @@ export const LanguageSelect = ({ language, onLanguageChange, disabled }: Languag
); -}; +} diff --git a/packages/ee/multiLanguage/components/LanguageSwitch.tsx b/packages/ee/multi-language/components/language-switch.tsx similarity index 90% rename from packages/ee/multiLanguage/components/LanguageSwitch.tsx rename to packages/ee/multi-language/components/language-switch.tsx index e47d87f31e..5074a1cae1 100644 --- a/packages/ee/multiLanguage/components/LanguageSwitch.tsx +++ b/packages/ee/multi-language/components/language-switch.tsx @@ -2,7 +2,7 @@ import { ChevronDownIcon } from "lucide-react"; -import { TSurveyLanguage } from "@formbricks/types/surveys"; +import type { TSurveyLanguage } from "@formbricks/types/surveys"; import { DropdownMenu, DropdownMenuContent, @@ -10,22 +10,22 @@ import { DropdownMenuTrigger, } from "@formbricks/ui/DropdownMenu"; -import { getLanguageLabel } from "../lib/isoLanguages"; +import { getLanguageLabel } from "../lib/iso-languages"; interface LanguageSwitchProps { surveyLanguages: TSurveyLanguage[]; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; } -export const LanguageSwitch = ({ +export function LanguageSwitch({ surveyLanguages, selectedLanguageCode, setSelectedLanguageCode, -}: LanguageSwitchProps) => { +}: LanguageSwitchProps) { if (selectedLanguageCode === "default") { selectedLanguageCode = surveyLanguages.find((surveyLanguage) => { - return surveyLanguage.default === true; + return surveyLanguage.default; })?.language.code ?? "default"; } return ( @@ -45,14 +45,15 @@ export const LanguageSwitch = ({ {surveyLanguages.length > 0 ? ( surveyLanguages.map((surveyLanguage) => ( { setSelectedLanguageCode(surveyLanguage.language.code); }}>
+ className={`h-4 w-4 rounded-full border ${surveyLanguage.language.code === selectedLanguageCode ? "bg-brand-dark outline-brand-dark border-black outline" : "border-white"}`} + />

{getLanguageLabel(surveyLanguage.language.code)}

@@ -64,4 +65,4 @@ export const LanguageSwitch = ({
); -}; +} diff --git a/packages/ee/multiLanguage/components/LanguageToggle.tsx b/packages/ee/multi-language/components/language-toggle.tsx similarity index 69% rename from packages/ee/multiLanguage/components/LanguageToggle.tsx rename to packages/ee/multi-language/components/language-toggle.tsx index c188da70d4..1023c0afdb 100644 --- a/packages/ee/multiLanguage/components/LanguageToggle.tsx +++ b/packages/ee/multi-language/components/language-toggle.tsx @@ -1,8 +1,8 @@ -import { TLanguage } from "@formbricks/types/product"; +import type { TLanguage } from "@formbricks/types/product"; import { Label } from "@formbricks/ui/Label"; import { Switch } from "@formbricks/ui/Switch"; -import { getLanguageLabel } from "../lib/isoLanguages"; +import { getLanguageLabel } from "../lib/iso-languages"; interface LanguageToggleProps { language: TLanguage; @@ -11,27 +11,27 @@ interface LanguageToggleProps { onEdit: () => void; } -export const LanguageToggle = ({ language, isChecked, onToggle, onEdit }: LanguageToggleProps) => { +export function LanguageToggle({ language, isChecked, onToggle, onEdit }: LanguageToggleProps) { return (
{ e.stopPropagation(); onToggle(); }} /> -
); -}; +} diff --git a/packages/ee/multiLanguage/components/LocalizedEditor.tsx b/packages/ee/multi-language/components/localized-editor.tsx similarity index 80% rename from packages/ee/multiLanguage/components/LocalizedEditor.tsx rename to packages/ee/multi-language/components/localized-editor.tsx index d080a6ecfd..3720f31cc4 100644 --- a/packages/ee/multiLanguage/components/LocalizedEditor.tsx +++ b/packages/ee/multi-language/components/localized-editor.tsx @@ -5,10 +5,10 @@ import { useMemo } from "react"; import { extractLanguageCodes, isLabelValidForAllLanguages } from "@formbricks/lib/i18n/utils"; import { md } from "@formbricks/lib/markdownIt"; import { recallToHeadline } from "@formbricks/lib/utils/recall"; -import { TI18nString, TSurvey } from "@formbricks/types/surveys"; +import type { TI18nString, TSurvey } from "@formbricks/types/surveys"; import { Editor } from "@formbricks/ui/Editor"; -import { LanguageIndicator } from "./LanguageIndicator"; +import { LanguageIndicator } from "./language-indicator"; interface LocalizedEditorProps { id: string; @@ -31,11 +31,11 @@ const checkIfValueIsIncomplete = ( ) => { const labelIds = ["subheader"]; if (value === undefined) return false; - const isDefaultIncomplete = labelIds.includes(id) ? value["default"]?.trim() !== "" : false; + const isDefaultIncomplete = labelIds.includes(id) ? value.default.trim() !== "" : false; return isInvalid && !isLabelValidForAllLanguages(value, surveyLanguageCodes) && isDefaultIncomplete; }; -export const LocalizedEditor = ({ +export function LocalizedEditor({ id, value, localSurvey, @@ -46,24 +46,28 @@ export const LocalizedEditor = ({ questionIdx, firstRender, setFirstRender, -}: LocalizedEditorProps) => { +}: LocalizedEditorProps) { const surveyLanguageCodes = useMemo( () => extractLanguageCodes(localSurvey.languages), [localSurvey.languages] ); const isInComplete = useMemo( () => checkIfValueIsIncomplete(id, isInvalid, surveyLanguageCodes, value), - [id, isInvalid, surveyLanguageCodes, value, selectedLanguageCode] + [id, isInvalid, surveyLanguageCodes, value] ); return (
md.render(value ? value[selectedLanguageCode] ?? "" : "")} + key={`${questionIdx}-${selectedLanguageCode}`} + setFirstRender={setFirstRender} setText={(v: string) => { if (!value) return; - let translatedHtml = { + const translatedHtml = { ...value, [selectedLanguageCode]: v, }; @@ -74,36 +78,35 @@ export const LocalizedEditor = ({ } updateQuestion(questionIdx, { html: translatedHtml }); }} - excludedToolbarItems={["blockType"]} - disableLists - firstRender={firstRender} - setFirstRender={setFirstRender} /> - {localSurvey.languages?.length > 1 && ( + {localSurvey.languages.length > 1 && (
- {value && selectedLanguageCode !== "default" && value["default"] && ( + {value && selectedLanguageCode !== "default" && value.default ? (
Translate: + }} + />
- )} + ) : null}
)} - {isInComplete &&
Contains Incomplete translations
} + {isInComplete ? ( +
Contains Incomplete translations
+ ) : null}
); -}; +} diff --git a/packages/ee/multiLanguage/components/MultiLanguageCard.tsx b/packages/ee/multi-language/components/multi-language-card.tsx similarity index 89% rename from packages/ee/multiLanguage/components/MultiLanguageCard.tsx rename to packages/ee/multi-language/components/multi-language-card.tsx index c8bdde7792..4efd3b4944 100644 --- a/packages/ee/multiLanguage/components/MultiLanguageCard.tsx +++ b/packages/ee/multi-language/components/multi-language-card.tsx @@ -3,21 +3,23 @@ import * as Collapsible from "@radix-ui/react-collapsible"; import { ArrowUpRight, Languages } from "lucide-react"; import Link from "next/link"; -import { FC, useState } from "react"; +import type { FC } from "react"; +import { useState } from "react"; import toast from "react-hot-toast"; import { cn } from "@formbricks/lib/cn"; import { extractLanguageCodes, translateSurvey } from "@formbricks/lib/i18n/utils"; -import { TLanguage, TProduct } from "@formbricks/types/product"; -import { TSurvey, TSurveyLanguage, ZSurvey } from "@formbricks/types/surveys"; +import type { TLanguage, TProduct } from "@formbricks/types/product"; +import type { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys"; +import { ZSurvey } from "@formbricks/types/surveys"; import { Button } from "@formbricks/ui/Button"; import { ConfirmationModal } from "@formbricks/ui/ConfirmationModal"; import { Label } from "@formbricks/ui/Label"; import { Switch } from "@formbricks/ui/Switch"; import { UpgradePlanNotice } from "@formbricks/ui/UpgradePlanNotice"; -import { DefaultLanguageSelect } from "./DefaultLanguageSelect"; -import { SecondaryLanguageSelect } from "./SecondaryLanguageSelect"; +import { DefaultLanguageSelect } from "./default-language-select"; +import { SecondaryLanguageSelect } from "./secondary-language-select"; interface MultiLanguageCardProps { localSurvey: TSurvey; @@ -50,8 +52,8 @@ export const MultiLanguageCard: FC = ({ setSelectedLanguageCode, }) => { const environmentId = localSurvey.environmentId; - const open = activeQuestionId == "multiLanguage"; - const [isMultiLanguageActivated, setIsMultiLanguageActivated] = useState(localSurvey.languages?.length > 1); + const open = activeQuestionId === "multiLanguage"; + const [isMultiLanguageActivated, setIsMultiLanguageActivated] = useState(localSurvey.languages.length > 1); const [confirmationModalInfo, setConfirmationModalInfo] = useState({ title: "", open: false, @@ -61,8 +63,8 @@ export const MultiLanguageCard: FC = ({ }); const [defaultLanguage, setDefaultLanguage] = useState( - localSurvey.languages?.find((language) => { - return language.default === true; + localSurvey.languages.find((language) => { + return language.default; })?.language ); @@ -121,13 +123,12 @@ export const MultiLanguageCard: FC = ({ // Update all languages and check if the new default language already exists const newLanguages = - localSurvey.languages?.map((lang) => { + localSurvey.languages.map((lang) => { if (lang.language.code === language.code) { languageExists = true; return { ...lang, default: true }; - } else { - return { ...lang, default: false }; } + return { ...lang, default: false }; }) ?? []; if (!languageExists) { @@ -147,7 +148,7 @@ export const MultiLanguageCard: FC = ({ const handleActivationSwitchLogic = () => { if (isMultiLanguageActivated) { - if (localSurvey.languages?.length > 0) { + if (localSurvey.languages.length > 0) { setConfirmationModalInfo({ open: true, title: "Remove translations", @@ -185,9 +186,9 @@ export const MultiLanguageCard: FC = ({

+ open={open}> @@ -202,12 +203,12 @@ export const MultiLanguageCard: FC = ({ { handleActivationSwitchLogic(); }} - disabled={!isMultiLanguageAllowed || product.languages.length === 0} />
@@ -217,14 +218,14 @@ export const MultiLanguageCard: FC = ({ {!isMultiLanguageAllowed && !isFormbricksCloud && !isMultiLanguageActivated ? ( ) : !isMultiLanguageAllowed && isFormbricksCloud && !isMultiLanguageActivated ? ( ) : ( <> @@ -238,14 +239,14 @@ export const MultiLanguageCard: FC = ({ {product.languages.length > 1 && (
- {isMultiLanguageAllowed && !isMultiLanguageActivated && ( + {isMultiLanguageAllowed && !isMultiLanguageActivated ? (
Switch multi-lanugage on to get started ๐Ÿ‘‰
- )} + ) : null}
- {isMultiLanguageActivated && ( + {isMultiLanguageActivated ? (
= ({ product={product} setConfirmationModalInfo={setConfirmationModalInfo} /> - {defaultLanguage && ( + {defaultLanguage ? ( - )} + ) : null}
- )} + ) : null}
)} - @@ -277,13 +278,15 @@ export const MultiLanguageCard: FC = ({ )} setConfirmationModalInfo((prev) => ({ ...prev, open: !prev.open }))} - text={confirmationModalInfo.text} - onConfirm={confirmationModalInfo.onConfirm} buttonText={confirmationModalInfo.buttonText} buttonVariant={confirmationModalInfo.buttonVariant} + onConfirm={confirmationModalInfo.onConfirm} + open={confirmationModalInfo.open} + setOpen={() => { + setConfirmationModalInfo((prev) => ({ ...prev, open: !prev.open })); + }} + text={confirmationModalInfo.text} + title={confirmationModalInfo.title} />
diff --git a/packages/ee/multiLanguage/components/SecondaryLanguageSelect.tsx b/packages/ee/multi-language/components/secondary-language-select.tsx similarity index 74% rename from packages/ee/multiLanguage/components/SecondaryLanguageSelect.tsx rename to packages/ee/multi-language/components/secondary-language-select.tsx index f5f700a981..6d85e3b6dc 100644 --- a/packages/ee/multiLanguage/components/SecondaryLanguageSelect.tsx +++ b/packages/ee/multi-language/components/secondary-language-select.tsx @@ -1,9 +1,9 @@ -import { TLanguage, TProduct } from "@formbricks/types/product"; -import { TSurvey } from "@formbricks/types/surveys"; +import type { TLanguage, TProduct } from "@formbricks/types/product"; +import type { TSurvey } from "@formbricks/types/surveys"; -import { LanguageToggle } from "./LanguageToggle"; +import { LanguageToggle } from "./language-toggle"; -interface secondaryLanguageSelectProps { +interface SecondaryLanguageSelectProps { product: TProduct; defaultLanguage: TLanguage; setSelectedLanguageCode: (languageCode: string) => void; @@ -12,14 +12,14 @@ interface secondaryLanguageSelectProps { updateSurveyLanguages: (language: TLanguage) => void; } -export const SecondaryLanguageSelect = ({ +export function SecondaryLanguageSelect({ product, defaultLanguage, setSelectedLanguageCode, setActiveQuestionId, localSurvey, updateSurveyLanguages, -}: secondaryLanguageSelectProps) => { +}: SecondaryLanguageSelectProps) { const isLanguageToggled = (language: TLanguage) => { return localSurvey.languages.some( (surveyLanguage) => surveyLanguage.language.code === language.code && surveyLanguage.enabled @@ -33,16 +33,18 @@ export const SecondaryLanguageSelect = ({ .filter((lang) => lang.id !== defaultLanguage.id) .map((language) => ( updateSurveyLanguages(language)} onEdit={() => { setSelectedLanguageCode(language.code); setActiveQuestionId(localSurvey.questions[0]?.id); }} + onToggle={() => { + updateSurveyLanguages(language); + }} /> ))}
); -}; +} diff --git a/packages/ee/multiLanguage/lib/actions.ts b/packages/ee/multi-language/lib/actions.ts similarity index 97% rename from packages/ee/multiLanguage/lib/actions.ts rename to packages/ee/multi-language/lib/actions.ts index 27c84e9440..8bbeb7ece9 100644 --- a/packages/ee/multiLanguage/lib/actions.ts +++ b/packages/ee/multi-language/lib/actions.ts @@ -12,7 +12,7 @@ import { import { canUserAccessProduct, verifyUserRoleAccess } from "@formbricks/lib/product/auth"; import { getProduct } from "@formbricks/lib/product/service"; import { AuthorizationError } from "@formbricks/types/errors"; -import { TLanguageInput } from "@formbricks/types/product"; +import type { TLanguageInput } from "@formbricks/types/product"; export const createLanguageAction = async ( productId: string, diff --git a/packages/ee/multiLanguage/lib/isoLanguages.ts b/packages/ee/multi-language/lib/iso-languages.ts similarity index 100% rename from packages/ee/multiLanguage/lib/isoLanguages.ts rename to packages/ee/multi-language/lib/iso-languages.ts diff --git a/packages/ee/package.json b/packages/ee/package.json index b11fe619aa..b71fcf8a68 100644 --- a/packages/ee/package.json +++ b/packages/ee/package.json @@ -7,18 +7,31 @@ "version": "1.0.0", "main": "index.ts", "scripts": { - "clean": "rimraf node_modules .turbo" + "clean": "rimraf node_modules .turbo", + "lint": "eslint --ext .ts,.tsx --fix ." }, "devDependencies": { - "@formbricks/lib": "*", "@formbricks/config-typescript": "*", + "@formbricks/eslint-config": "workspace:*", + "@formbricks/lib": "*", "@formbricks/types": "*", "@formbricks/ui": "*", - "@formbricks/eslint-config": "workspace:*" + "@types/dompurify": "^3.0.5", + "@types/react": "18.3.3" }, "dependencies": { + "@formbricks/database": "workspace:*", "@formbricks/lib": "workspace:*", + "@paralleldrive/cuid2": "^2.2.2", + "@radix-ui/react-collapsible": "^1.0.3", "axios": "^1.7.2", - "stripe": "^15.8.0" + "lucide-react": "^0.390.0", + "next": "^14.2.3", + "next-auth": "^4.24.7", + "react-hook-form": "^7.51.5", + "react-hot-toast": "^2.4.1", + "server-only": "^0.0.1", + "stripe": "^15.8.0", + "zod": "^3.23.8" } } diff --git a/packages/ee/RoleManagement/components/AddMemberRole.tsx b/packages/ee/role-management/components/add-member-role.tsx similarity index 73% rename from packages/ee/RoleManagement/components/AddMemberRole.tsx rename to packages/ee/role-management/components/add-member-role.tsx index 7b94d49e2d..abea27025c 100644 --- a/packages/ee/RoleManagement/components/AddMemberRole.tsx +++ b/packages/ee/role-management/components/add-member-role.tsx @@ -1,4 +1,5 @@ -import { Control, Controller } from "react-hook-form"; +import type { Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; import { Label } from "@formbricks/ui/Label"; import { @@ -17,24 +18,26 @@ enum MembershipRole { Viewer = "viewer", } -type AddMemberRoleProps = { - control: Control<{ name: string; email: string; role: MembershipRole }, any>; +interface AddMemberRoleProps { + control: Control<{ name: string; email: string; role: MembershipRole }>; canDoRoleManagement: boolean; -}; +} -export const AddMemberRole = ({ control, canDoRoleManagement }: AddMemberRoleProps) => { +export function AddMemberRole({ control, canDoRoleManagement }: AddMemberRoleProps) { return ( (
); -}; +} diff --git a/packages/ee/RoleManagement/lib/actions.ts b/packages/ee/role-management/lib/actions.ts similarity index 93% rename from packages/ee/RoleManagement/lib/actions.ts rename to packages/ee/role-management/lib/actions.ts index 93bf5e0b8f..2f233c2324 100644 --- a/packages/ee/RoleManagement/lib/actions.ts +++ b/packages/ee/role-management/lib/actions.ts @@ -11,9 +11,9 @@ import { updateMembership, } from "@formbricks/lib/membership/service"; import { AuthenticationError, AuthorizationError, ValidationError } from "@formbricks/types/errors"; -import { TInviteUpdateInput } from "@formbricks/types/invites"; -import { TMembershipUpdateInput } from "@formbricks/types/memberships"; -import { TUser } from "@formbricks/types/user"; +import type { TInviteUpdateInput } from "@formbricks/types/invites"; +import type { TMembershipUpdateInput } from "@formbricks/types/memberships"; +import type { TUser } from "@formbricks/types/user"; export const transferOwnershipAction = async (organizationId: string, newOwnerId: string) => { const session = await getServerSession(authOptions); diff --git a/packages/ee/tsconfig.json b/packages/ee/tsconfig.json index 98f957b9d2..9f78a0463e 100644 --- a/packages/ee/tsconfig.json +++ b/packages/ee/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@formbricks/config-typescript/react-library.json", + "extends": "@formbricks/config-typescript/nextjs.json", "compilerOptions": { "baseUrl": ".", "paths": { @@ -7,6 +7,6 @@ }, "resolveJsonModule": true }, - "include": [".", "../ui/TargetingIndicator.tsx"], + "include": [".", "../../packages/types/*.d.ts"], "exclude": ["dist", "build", "node_modules"] } diff --git a/packages/email/.eslintrc.js b/packages/email/.eslintrc.js index 64a6e29852..d4b8f9e08c 100644 --- a/packages/email/.eslintrc.js +++ b/packages/email/.eslintrc.js @@ -1,3 +1,3 @@ module.exports = { - extends: ["@formbricks/eslint-config/legacy-next.js"], + extends: ["@formbricks/eslint-config/react.js"], }; diff --git a/packages/email/components/auth/ForgotPasswordEmail.tsx b/packages/email/components/auth/forgot-password-email.tsx similarity index 68% rename from packages/email/components/auth/ForgotPasswordEmail.tsx rename to packages/email/components/auth/forgot-password-email.tsx index d44cc5aded..05a6d03264 100644 --- a/packages/email/components/auth/ForgotPasswordEmail.tsx +++ b/packages/email/components/auth/forgot-password-email.tsx @@ -1,24 +1,23 @@ import { Container, Heading, Text } from "@react-email/components"; import React from "react"; - -import { EmailButton } from "../general/EmailButton"; -import { EmailFooter } from "../general/EmailFooter"; +import { EmailButton } from "../general/email-button"; +import { EmailFooter } from "../general/email-footer"; interface ForgotPasswordEmailProps { verifyLink: string; } -export const ForgotPasswordEmail = ({ verifyLink }: ForgotPasswordEmailProps) => { +export function ForgotPasswordEmail({ verifyLink }: ForgotPasswordEmailProps) { return ( Change password You have requested a link to change your password. You can do this by clicking the link below: - + The link is valid for 24 hours. If you didn't request this, please ignore this email. ); -}; +} diff --git a/packages/email/components/auth/PasswordResetNotifyEmail.tsx b/packages/email/components/auth/password-reset-notify-email.tsx similarity index 72% rename from packages/email/components/auth/PasswordResetNotifyEmail.tsx rename to packages/email/components/auth/password-reset-notify-email.tsx index 428079b7ac..b4c7abf081 100644 --- a/packages/email/components/auth/PasswordResetNotifyEmail.tsx +++ b/packages/email/components/auth/password-reset-notify-email.tsx @@ -1,9 +1,8 @@ import { Container, Heading, Text } from "@react-email/components"; import React from "react"; +import { EmailFooter } from "../general/email-footer"; -import { EmailFooter } from "../general/EmailFooter"; - -export const PasswordResetNotifyEmail = () => { +export function PasswordResetNotifyEmail() { return ( Password changed @@ -11,4 +10,4 @@ export const PasswordResetNotifyEmail = () => { ); -}; +} diff --git a/packages/email/components/auth/VerificationEmail.tsx b/packages/email/components/auth/verification-email.tsx similarity index 61% rename from packages/email/components/auth/VerificationEmail.tsx rename to packages/email/components/auth/verification-email.tsx index 13b0479529..ff08477831 100644 --- a/packages/email/components/auth/VerificationEmail.tsx +++ b/packages/email/components/auth/verification-email.tsx @@ -1,32 +1,31 @@ import { Container, Heading, Link, Text } from "@react-email/components"; import React from "react"; - -import { EmailButton } from "../general/EmailButton"; -import { EmailFooter } from "../general/EmailFooter"; +import { EmailButton } from "../general/email-button"; +import { EmailFooter } from "../general/email-footer"; interface VerificationEmailProps { verifyLink: string; verificationRequestLink: string; } -export const VerificationEmail = ({ verifyLink, verificationRequestLink }: VerificationEmailProps) => { +export function VerificationEmail({ verifyLink, verificationRequestLink }: VerificationEmailProps) { return ( Almost there! To start using Formbricks please verify your email below: - + You can also click on this link: - + {verifyLink} The link is valid for 24h. If it has expired please request a new token here:{" "} - + Request new verification ); -}; +} diff --git a/packages/email/components/general/EmailTemplate.tsx b/packages/email/components/general/EmailTemplate.tsx deleted file mode 100644 index 829aa0f946..0000000000 --- a/packages/email/components/general/EmailTemplate.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Body, Column, Container, Html, Img, Link, Row, Section, Tailwind } from "@react-email/components"; -import React, { Fragment } from "react"; - -interface EmailTemplateProps { - content: JSX.Element; -} - -export const EmailTemplate = ({ content }: EmailTemplateProps) => ( - - - - -
- - Formbricks Logo - -
- {content} - -
- - - - Tw - - - - - GitHub - - - - - Discord - - - -
-
- Formbricks {new Date().getFullYear()}. All rights reserved. -
- - Imprint - {" "} - |{" "} - - Privacy Policy - -
- -
-
- -); diff --git a/packages/email/components/general/EmailButton.tsx b/packages/email/components/general/email-button.tsx similarity index 78% rename from packages/email/components/general/EmailButton.tsx rename to packages/email/components/general/email-button.tsx index d0eb45f77f..f8c7df901c 100644 --- a/packages/email/components/general/EmailButton.tsx +++ b/packages/email/components/general/email-button.tsx @@ -6,10 +6,10 @@ interface EmailButtonProps { href: string; } -export const EmailButton = ({ label, href }: EmailButtonProps) => { +export function EmailButton({ label, href }: EmailButtonProps) { return ( ); -}; +} diff --git a/packages/email/components/general/EmailFooter.tsx b/packages/email/components/general/email-footer.tsx similarity index 55% rename from packages/email/components/general/EmailFooter.tsx rename to packages/email/components/general/email-footer.tsx index a8a1c229b5..89421688fe 100644 --- a/packages/email/components/general/EmailFooter.tsx +++ b/packages/email/components/general/email-footer.tsx @@ -1,10 +1,11 @@ import { Text } from "@react-email/components"; import React from "react"; -export const EmailFooter = () => { +export function EmailFooter() { return ( - Have a great day!

The Formbricks Team! + Have a great day! +
The Formbricks Team!
); -}; +} diff --git a/packages/email/components/general/email-template.tsx b/packages/email/components/general/email-template.tsx new file mode 100644 index 0000000000..c267ef1319 --- /dev/null +++ b/packages/email/components/general/email-template.tsx @@ -0,0 +1,82 @@ +import { Body, Column, Container, Html, Img, Link, Row, Section, Tailwind } from "@react-email/components"; + +interface EmailTemplateProps { + content: JSX.Element; +} + +export function EmailTemplate({ content }: EmailTemplateProps) { + return ( + + + <> + +
+ + Formbricks Logo + +
+ {content} + +
+ + + + Tw + + + + + GitHub + + + + + Discord + + + +
+
+ Formbricks {new Date().getFullYear()}. All rights reserved. +
+ + Imprint + {" "} + |{" "} + + Privacy Policy + +
+ + +
+ + ); +} diff --git a/packages/email/components/invite/InviteAcceptedEmail.tsx b/packages/email/components/invite/invite-accepted-email.tsx similarity index 71% rename from packages/email/components/invite/InviteAcceptedEmail.tsx rename to packages/email/components/invite/invite-accepted-email.tsx index dbc5d8d83f..e3cb76812b 100644 --- a/packages/email/components/invite/InviteAcceptedEmail.tsx +++ b/packages/email/components/invite/invite-accepted-email.tsx @@ -1,14 +1,13 @@ import { Container, Text } from "@react-email/components"; import React from "react"; - -import { EmailFooter } from "../general/EmailFooter"; +import { EmailFooter } from "../general/email-footer"; interface InviteAcceptedEmailProps { inviterName: string; inviteeName: string; } -export const InviteAcceptedEmail = ({ inviterName, inviteeName }: InviteAcceptedEmailProps) => { +export function InviteAcceptedEmail({ inviterName, inviteeName }: InviteAcceptedEmailProps) { return ( Hey {inviterName}, @@ -16,4 +15,4 @@ export const InviteAcceptedEmail = ({ inviterName, inviteeName }: InviteAccepted ); -}; +} diff --git a/packages/email/components/invite/InviteEmail.tsx b/packages/email/components/invite/invite-email.tsx similarity index 63% rename from packages/email/components/invite/InviteEmail.tsx rename to packages/email/components/invite/invite-email.tsx index 336a2d6294..5143786d8a 100644 --- a/packages/email/components/invite/InviteEmail.tsx +++ b/packages/email/components/invite/invite-email.tsx @@ -1,8 +1,7 @@ import { Container, Text } from "@react-email/components"; import React from "react"; - -import { EmailButton } from "../general/EmailButton"; -import { EmailFooter } from "../general/EmailFooter"; +import { EmailButton } from "../general/email-button"; +import { EmailFooter } from "../general/email-footer"; interface InviteEmailProps { inviteeName: string; @@ -10,7 +9,7 @@ interface InviteEmailProps { verifyLink: string; } -export const InviteEmail = ({ inviteeName, inviterName, verifyLink }: InviteEmailProps) => { +export function InviteEmail({ inviteeName, inviterName, verifyLink }: InviteEmailProps) { return ( Hey {inviteeName}, @@ -18,8 +17,8 @@ export const InviteEmail = ({ inviteeName, inviterName, verifyLink }: InviteEmai Your colleague {inviterName} invited you to join them at Formbricks. To accept the invitation, please click the link below: - + ); -}; +} diff --git a/packages/email/components/invite/OnboardingInviteEmail.tsx b/packages/email/components/invite/onboarding-invite-email.tsx similarity index 71% rename from packages/email/components/invite/OnboardingInviteEmail.tsx rename to packages/email/components/invite/onboarding-invite-email.tsx index 54bfeff3ec..65172c377b 100644 --- a/packages/email/components/invite/OnboardingInviteEmail.tsx +++ b/packages/email/components/invite/onboarding-invite-email.tsx @@ -1,8 +1,7 @@ import { Container, Heading, Text } from "@react-email/components"; import React from "react"; - -import { EmailButton } from "../general/EmailButton"; -import { EmailFooter } from "../general/EmailFooter"; +import { EmailButton } from "../general/email-button"; +import { EmailFooter } from "../general/email-footer"; interface OnboardingInviteEmailProps { inviteMessage: string; @@ -10,11 +9,11 @@ interface OnboardingInviteEmailProps { verifyLink: string; } -export const OnboardingInviteEmail = ({ +export function OnboardingInviteEmail({ inviteMessage, inviterName, verifyLink, -}: OnboardingInviteEmailProps) => { +}: OnboardingInviteEmailProps) { return ( Hey ๐Ÿ‘‹ @@ -25,8 +24,8 @@ export const OnboardingInviteEmail = ({
  • Connect Formbricks to your app or website via HTML Snippet or NPM in just a few minutes.
  • Done โœ…
  • - +
    ); -}; +} diff --git a/packages/email/components/survey/EmbedSurveyPreviewEmail.tsx b/packages/email/components/survey/embed-survey-preview-email.tsx similarity index 78% rename from packages/email/components/survey/EmbedSurveyPreviewEmail.tsx rename to packages/email/components/survey/embed-survey-preview-email.tsx index e8fa499356..19c558f7e3 100644 --- a/packages/email/components/survey/EmbedSurveyPreviewEmail.tsx +++ b/packages/email/components/survey/embed-survey-preview-email.tsx @@ -6,7 +6,7 @@ interface EmbedSurveyPreviewEmailProps { environmentId: string; } -export const EmbedSurveyPreviewEmail = ({ html, environmentId }: EmbedSurveyPreviewEmailProps) => { +export function EmbedSurveyPreviewEmail({ html, environmentId }: EmbedSurveyPreviewEmailProps) { return ( Preview Email Embed @@ -14,8 +14,8 @@ export const EmbedSurveyPreviewEmail = ({ html, environmentId }: EmbedSurveyPrev Didn't request this? Help us fight spam and forward this mail to hola@formbricks.com -
    +
    Environment ID: {environmentId} ); -}; +} diff --git a/packages/email/components/survey/LinkSurveyEmail.tsx b/packages/email/components/survey/link-survey-email.tsx similarity index 67% rename from packages/email/components/survey/LinkSurveyEmail.tsx rename to packages/email/components/survey/link-survey-email.tsx index f2eedc2cfd..0b2670dd57 100644 --- a/packages/email/components/survey/LinkSurveyEmail.tsx +++ b/packages/email/components/survey/link-survey-email.tsx @@ -1,8 +1,7 @@ import { Container, Heading, Text } from "@react-email/components"; import React from "react"; - -import { EmailButton } from "../general/EmailButton"; -import { EmailFooter } from "../general/EmailFooter"; +import { EmailButton } from "../general/email-button"; +import { EmailFooter } from "../general/email-footer"; interface LinkSurveyEmailProps { surveyData?: @@ -15,15 +14,15 @@ interface LinkSurveyEmailProps { getSurveyLink: () => string; } -export const LinkSurveyEmail = ({ surveyData, getSurveyLink }: LinkSurveyEmailProps) => { +export function LinkSurveyEmail({ surveyData, getSurveyLink }: LinkSurveyEmailProps) { return ( Hey ๐Ÿ‘‹ Thanks for validating your email. Here is your Survey. {surveyData?.name} {surveyData?.subheading} - + ); -}; +} diff --git a/packages/email/components/survey/PreviewEmailTemplate.tsx b/packages/email/components/survey/preview-email-template.tsx similarity index 81% rename from packages/email/components/survey/PreviewEmailTemplate.tsx rename to packages/email/components/survey/preview-email-template.tsx index 53898236c2..2d6c74adf5 100644 --- a/packages/email/components/survey/PreviewEmailTemplate.tsx +++ b/packages/email/components/survey/preview-email-template.tsx @@ -12,12 +12,12 @@ import { import { render } from "@react-email/render"; import { CalendarDaysIcon } from "lucide-react"; import React from "react"; - import { cn } from "@formbricks/lib/cn"; import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants"; import { isLight, mixColor } from "@formbricks/lib/utils/colors"; -import { TSurvey, TSurveyQuestionType, TSurveyStyling } from "@formbricks/types/surveys"; +import type { TSurvey, TSurveyStyling } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { RatingSmiley } from "@formbricks/ui/RatingSmiley"; interface PreviewEmailTemplateProps { @@ -27,23 +27,23 @@ interface PreviewEmailTemplateProps { } export const getPreviewEmailTemplateHtml = (survey: TSurvey, surveyUrl: string, styling: TSurveyStyling) => { - return render(, { + return render(, { pretty: true, }); }; -export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmailTemplateProps) => { +export function PreviewEmailTemplate({ survey, surveyUrl, styling }: PreviewEmailTemplateProps) { const url = `${surveyUrl}?preview=true`; const urlWithPrefilling = `${surveyUrl}?preview=true&skipPrefilled=true&`; const defaultLanguageCode = "default"; const firstQuestion = survey.questions[0]; - const brandColor = styling?.brandColor?.light || COLOR_DEFAULTS.brandColor; + const brandColor = styling.brandColor?.light || COLOR_DEFAULTS.brandColor; switch (firstQuestion.type) { - case TSurveyQuestionType.OpenText: + case TSurveyQuestionTypeEnum.OpenText: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -54,9 +54,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.Consent: + case TSurveyQuestionTypeEnum.Consent: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -65,7 +65,8 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai className="m-0 p-0" dangerouslySetInnerHTML={{ __html: getLocalizedValue(firstQuestion.html, defaultLanguageCode) || "", - }}> + }} + /> @@ -76,26 +77,26 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai {!firstQuestion.required && ( + className="rounded-custom inline-flex cursor-pointer appearance-none px-6 py-3 text-sm font-medium text-black" + href={`${urlWithPrefilling}${firstQuestion.id}=dismissed`}> Reject )} + )} + href={`${urlWithPrefilling}${firstQuestion.id}=accepted`}> Accept ); - case TSurveyQuestionType.NPS: + case TSurveyQuestionTypeEnum.NPS: return ( - +
    {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -107,9 +108,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai
    {Array.from({ length: 11 }, (_, i) => ( + key={i}> {i} ))} @@ -133,9 +134,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai
    ); - case TSurveyQuestionType.CTA: + case TSurveyQuestionTypeEnum.CTA: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -144,32 +145,33 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai className="m-0 p-0" dangerouslySetInnerHTML={{ __html: getLocalizedValue(firstQuestion.html, defaultLanguageCode) || "", - }}>
    + }} + /> {!firstQuestion.required && ( + className="rounded-custom inline-flex cursor-pointer appearance-none px-6 py-3 text-sm font-medium text-black" + href={`${urlWithPrefilling}${firstQuestion.id}=dismissed`}> {getLocalizedValue(firstQuestion.dismissButtonLabel, defaultLanguageCode) || "Skip"} )} + )} + href={`${urlWithPrefilling}${firstQuestion.id}=clicked`}> {getLocalizedValue(firstQuestion.buttonLabel, defaultLanguageCode)} ); - case TSurveyQuestionType.Rating: + case TSurveyQuestionTypeEnum.Rating: return ( - +
    {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -180,19 +182,19 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai
    {Array.from({ length: firstQuestion.range }, (_, i) => ( + )} + href={`${urlWithPrefilling}${firstQuestion.id}=${i + 1}`} + key={i}> {firstQuestion.scale === "smiley" && ( )} @@ -223,9 +225,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai
    ); - case TSurveyQuestionType.MultipleChoiceMulti: + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -244,9 +246,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.MultipleChoiceSingle: + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -256,9 +258,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai {firstQuestion.choices.map((choice) => ( + href={`${urlWithPrefilling}${firstQuestion.id}=${getLocalizedValue(choice.label, defaultLanguageCode)}`} + key={choice.id}> {getLocalizedValue(choice.label, defaultLanguageCode)} ))} @@ -266,9 +268,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.PictureSelection: + case TSurveyQuestionTypeEnum.PictureSelection: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -279,15 +281,17 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai {firstQuestion.choices.map((choice) => firstQuestion.allowMulti ? ( ) : ( - + key={choice.id} + target="_blank"> + ) )} @@ -295,9 +299,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.Cal: + case TSurveyQuestionTypeEnum.Cal: return ( - + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} @@ -316,9 +320,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.Date: + case TSurveyQuestionTypeEnum.Date: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -332,9 +336,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.Matrix: + case TSurveyQuestionTypeEnum.Matrix: return ( - + {getLocalizedValue(firstQuestion.headline, "default")} @@ -344,12 +348,12 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai
    - + {firstQuestion.columns.map((column, columnIndex) => { return ( + className="text-question-color max-w-40 break-words px-4 py-2 text-center" + key={columnIndex}> {getLocalizedValue(column, "default")} ); @@ -358,15 +362,15 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai {firstQuestion.rows.map((row, rowIndex) => { return ( + className={`${rowIndex % 2 === 0 ? "bg-input-color" : ""} rounded-custom`} + key={rowIndex}> {getLocalizedValue(row, "default")} - {firstQuestion.columns.map(() => { + {firstQuestion.columns.map((_, index) => { return ( - -
    + +
    ); })} @@ -378,9 +382,9 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai ); - case TSurveyQuestionType.Address: + case TSurveyQuestionTypeEnum.Address: return ( - + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} @@ -389,17 +393,30 @@ export const PreviewEmailTemplate = ({ survey, surveyUrl, styling }: PreviewEmai {Array.from({ length: 6 }).map((_, index) => (
    ))} ); + case TSurveyQuestionTypeEnum.FileUpload: + return ( + + + {getLocalizedValue(firstQuestion.headline, defaultLanguageCode)} + + + {getLocalizedValue(firstQuestion.subheader, defaultLanguageCode)} + +
    + + + ); } -}; +} -const EmailTemplateWrapper = ({ +function EmailTemplateWrapper({ children, surveyUrl, styling, @@ -407,7 +424,7 @@ const EmailTemplateWrapper = ({ children: React.ReactNode; surveyUrl: string; styling: TSurveyStyling; -}) => { +}) { let signatureColor = ""; const colors = { "brand-color": styling.brandColor?.light ?? COLOR_DEFAULTS.brandColor, @@ -434,27 +451,27 @@ const EmailTemplateWrapper = ({ "signature-color": signatureColor, }, borderRadius: { - custom: (styling.roundness ?? 8).toString() + "px", + custom: `${(styling.roundness ?? 8).toString()}px`, }, }, }, }}> + target="_blank"> {children} ); -}; +} -const EmailFooter = () => { +function EmailFooter() { return ( - + Powered by Formbricks ); -}; +} diff --git a/packages/email/components/survey/ResponseFinishedEmail.tsx b/packages/email/components/survey/response-finished-email.tsx similarity index 76% rename from packages/email/components/survey/ResponseFinishedEmail.tsx rename to packages/email/components/survey/response-finished-email.tsx index 534af80443..91f7d1497d 100644 --- a/packages/email/components/survey/ResponseFinishedEmail.tsx +++ b/packages/email/components/survey/response-finished-email.tsx @@ -1,39 +1,42 @@ import { Column, Container, Hr, Img, Link, Row, Section, Text } from "@react-email/components"; -import React from "react"; - import { getQuestionResponseMapping } from "@formbricks/lib/responses"; import { getOriginalFileNameFromUrl } from "@formbricks/lib/storage/utils"; -import { TOrganization } from "@formbricks/types/organizations"; -import { TResponse } from "@formbricks/types/responses"; -import { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import type { TOrganization } from "@formbricks/types/organizations"; +import type { TResponse } from "@formbricks/types/responses"; +import type { TSurvey, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; +import { EmailButton } from "../general/email-button"; -import { EmailButton } from "../general/EmailButton"; - -export const renderEmailResponseValue = (response: string | string[], questionType: string) => { +export const renderEmailResponseValue = (response: string | string[], questionType: TSurveyQuestionType) => { switch (questionType) { - case TSurveyQuestionType.FileUpload: + case TSurveyQuestionTypeEnum.FileUpload: return ( {typeof response !== "string" && - response.map((response) => ( + response.map((responseItem) => ( + className="mt-2 flex flex-col items-center justify-center rounded-lg bg-gray-200 p-2 text-black shadow-sm" + href={responseItem} + key={responseItem}> - {getOriginalFileNameFromUrl(response)} + {getOriginalFileNameFromUrl(responseItem)} ))} ); - case TSurveyQuestionType.PictureSelection: + case TSurveyQuestionTypeEnum.PictureSelection: return ( {typeof response !== "string" && - response.map((response) => ( - - {response.split("/").pop()} + response.map((responseItem) => ( + + {responseItem.split("/").pop()} ))} @@ -54,14 +57,14 @@ interface ResponseFinishedEmailProps { organization: TOrganization | null; } -export const ResponseFinishedEmail = ({ +export function ResponseFinishedEmail({ survey, responseCount, response, WEBAPP_URL, environmentId, organization, -}: ResponseFinishedEmailProps) => { +}: ResponseFinishedEmailProps) { const questions = getQuestionResponseMapping(survey, response); return ( @@ -117,23 +120,23 @@ export const ResponseFinishedEmail = ({ ); -}; +} -const FileIcon = () => { +function FileIcon() { return ( + strokeWidth="2" + viewBox="0 0 24 24" + width="24" + xmlns="http://www.w3.org/2000/svg"> ); -}; +} diff --git a/packages/email/components/weekly-summary/CreateReminderNotificationBody.tsx b/packages/email/components/weekly-summary/create-reminder-notification-body.tsx similarity index 77% rename from packages/email/components/weekly-summary/CreateReminderNotificationBody.tsx rename to packages/email/components/weekly-summary/create-reminder-notification-body.tsx index 14f3dfb036..9a1c7434d0 100644 --- a/packages/email/components/weekly-summary/CreateReminderNotificationBody.tsx +++ b/packages/email/components/weekly-summary/create-reminder-notification-body.tsx @@ -1,17 +1,15 @@ import { Container, Text } from "@react-email/components"; import React from "react"; - import { WEBAPP_URL } from "@formbricks/lib/constants"; -import { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; - -import { EmailButton } from "../general/EmailButton"; -import { NotificationFooter } from "./NotificationFooter"; +import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; +import { EmailButton } from "../general/email-button"; +import { NotificationFooter } from "./notification-footer"; interface CreateReminderNotificationBodyProps { notificationData: TWeeklySummaryNotificationResponse; } -export const CreateReminderNotificationBody = ({ notificationData }: CreateReminderNotificationBodyProps) => { +export function CreateReminderNotificationBody({ notificationData }: CreateReminderNotificationBodyProps) { return ( @@ -20,8 +18,8 @@ export const CreateReminderNotificationBody = ({ notificationData }: CreateRemin Donโ€™t let a week pass without learning about your users: Need help finding the right survey for your product? Pick a 15-minute slot{" "} @@ -30,4 +28,4 @@ export const CreateReminderNotificationBody = ({ notificationData }: CreateRemin ); -}; +} diff --git a/packages/email/components/weekly-summary/LiveSurveyNotification.tsx b/packages/email/components/weekly-summary/live-survey-notification.tsx similarity index 89% rename from packages/email/components/weekly-summary/LiveSurveyNotification.tsx rename to packages/email/components/weekly-summary/live-survey-notification.tsx index 4b8f9c7231..8bfd1eb748 100644 --- a/packages/email/components/weekly-summary/LiveSurveyNotification.tsx +++ b/packages/email/components/weekly-summary/live-survey-notification.tsx @@ -1,14 +1,13 @@ import { Container, Hr, Link, Tailwind, Text } from "@react-email/components"; import React from "react"; - import { WEBAPP_URL } from "@formbricks/lib/constants"; -import { +import type { TSurveyStatus } from "@formbricks/types/surveys"; +import type { TWeeklySummaryNotificationDataSurvey, TWeeklySummarySurveyResponseData, } from "@formbricks/types/weeklySummary"; - -import { EmailButton } from "../general/EmailButton"; -import { renderEmailResponseValue } from "../survey/ResponseFinishedEmail"; +import { EmailButton } from "../general/email-button"; +import { renderEmailResponseValue } from "../survey/response-finished-email"; const getButtonLabel = (count: number): string => { if (count === 1) { @@ -17,7 +16,7 @@ const getButtonLabel = (count: number): string => { return `View ${count > 2 ? count - 1 : "1"} more Response${count > 2 ? "s" : ""}`; }; -const convertSurveyStatus = (status: string): string => { +const convertSurveyStatus = (status: TSurveyStatus): string => { const statusMap = { inProgress: "In Progress", paused: "Paused", @@ -43,7 +42,7 @@ export const LiveSurveyNotification = ({ environmentId, surveys }: LiveSurveyNot ); } - let surveyFields: JSX.Element[] = []; + const surveyFields: JSX.Element[] = []; const responseCount = surveyResponses.length; surveyResponses.forEach((surveyResponse, index) => { @@ -78,8 +77,8 @@ export const LiveSurveyNotification = ({ environmentId, surveys }: LiveSurveyNot + className="text-xl text-black underline" + href={`${WEBAPP_URL}/environments/${environmentId}/surveys/${survey.id}/responses?utm_source=weekly&utm_medium=email&utm_content=ViewResponsesCTA`}> {survey.name} @@ -96,8 +95,8 @@ export const LiveSurveyNotification = ({ environmentId, surveys }: LiveSurveyNot {survey.responseCount > 0 && ( )} diff --git a/packages/email/components/weekly-summary/NoLiveSurveyNotificationEmail.tsx b/packages/email/components/weekly-summary/no-live-survey-notification-email.tsx similarity index 64% rename from packages/email/components/weekly-summary/NoLiveSurveyNotificationEmail.tsx rename to packages/email/components/weekly-summary/no-live-survey-notification-email.tsx index 503eef3d97..262461211f 100644 --- a/packages/email/components/weekly-summary/NoLiveSurveyNotificationEmail.tsx +++ b/packages/email/components/weekly-summary/no-live-survey-notification-email.tsx @@ -1,9 +1,7 @@ import React from "react"; - -import { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; - -import { CreateReminderNotificationBody } from "./CreateReminderNotificationBody"; -import { NotificationHeader } from "./NotificationHeader"; +import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; +import { CreateReminderNotificationBody } from "./create-reminder-notification-body"; +import { NotificationHeader } from "./notification-header"; interface NoLiveSurveyNotificationEmailProps { notificationData: TWeeklySummaryNotificationResponse; @@ -13,23 +11,23 @@ interface NoLiveSurveyNotificationEmailProps { endYear: number; } -export const NoLiveSurveyNotificationEmail = ({ +export function NoLiveSurveyNotificationEmail({ notificationData, startDate, endDate, startYear, endYear, -}: NoLiveSurveyNotificationEmailProps) => { +}: NoLiveSurveyNotificationEmailProps) { return (
    ); -}; +} diff --git a/packages/email/components/weekly-summary/NotificationFooter.tsx b/packages/email/components/weekly-summary/notification-footer.tsx similarity index 74% rename from packages/email/components/weekly-summary/NotificationFooter.tsx rename to packages/email/components/weekly-summary/notification-footer.tsx index 2653fbbffd..fe4d904f7b 100644 --- a/packages/email/components/weekly-summary/NotificationFooter.tsx +++ b/packages/email/components/weekly-summary/notification-footer.tsx @@ -1,13 +1,11 @@ -import { Container, Link, Text } from "@react-email/components"; -import { Tailwind } from "@react-email/components"; +import { Container, Link, Tailwind, Text } from "@react-email/components"; import React from "react"; - import { WEBAPP_URL } from "@formbricks/lib/constants"; interface NotificatonFooterProps { environmentId: string; } -export const NotificationFooter = ({ environmentId }: NotificatonFooterProps) => { +export function NotificationFooter({ environmentId }: NotificatonFooterProps) { return ( @@ -19,8 +17,8 @@ export const NotificationFooter = ({ environmentId }: NotificatonFooterProps) => To halt Weekly Updates,{" "} + className="text-black underline" + href={`${WEBAPP_URL}/environments/${environmentId}/settings/notifications`}> please turn them off {" "} in your settings ๐Ÿ™ @@ -29,4 +27,4 @@ export const NotificationFooter = ({ environmentId }: NotificatonFooterProps) => ); -}; +} diff --git a/packages/email/components/weekly-summary/NotificationHeader.tsx b/packages/email/components/weekly-summary/notification-header.tsx similarity index 62% rename from packages/email/components/weekly-summary/NotificationHeader.tsx rename to packages/email/components/weekly-summary/notification-header.tsx index 6d6087d6f8..9a47f6f205 100644 --- a/packages/email/components/weekly-summary/NotificationHeader.tsx +++ b/packages/email/components/weekly-summary/notification-header.tsx @@ -9,32 +9,27 @@ interface NotificationHeaderProps { endYear: number; } -export const NotificationHeader = ({ +export function NotificationHeader({ productName, startDate, endDate, startYear, endYear, -}: NotificationHeaderProps) => { - const getNotificationHeaderimePeriod = ( - startDate: string, - endDate: string, - startYear: number, - endYear: number - ) => { - if (startYear == endYear) { +}: NotificationHeaderProps) { + const getNotificationHeaderimePeriod = () => { + if (startYear === endYear) { return ( {startDate} - {endDate} {endYear} ); - } else { - return ( - - {startDate} {startYear} - {endDate} {endYear} - - ); } + + return ( + + {startDate} {startYear} - {endDate} {endYear} + + ); }; return ( @@ -44,9 +39,9 @@ export const NotificationHeader = ({
    Weekly Report for {productName} - {getNotificationHeaderimePeriod(startDate, endDate, startYear, endYear)} + {getNotificationHeaderimePeriod()}
    ); -}; +} diff --git a/packages/email/components/weekly-summary/NotificationInsight.tsx b/packages/email/components/weekly-summary/notification-insight.tsx similarity index 90% rename from packages/email/components/weekly-summary/NotificationInsight.tsx rename to packages/email/components/weekly-summary/notification-insight.tsx index 64cf86f42d..da1fd22293 100644 --- a/packages/email/components/weekly-summary/NotificationInsight.tsx +++ b/packages/email/components/weekly-summary/notification-insight.tsx @@ -1,13 +1,12 @@ import { Column, Container, Row, Section, Text } from "@react-email/components"; import React from "react"; - -import { TWeeklySummaryInsights } from "@formbricks/types/weeklySummary"; +import type { TWeeklySummaryInsights } from "@formbricks/types/weeklySummary"; interface NotificationInsightProps { insights: TWeeklySummaryInsights; } -export const NotificationInsight = ({ insights }: NotificationInsightProps) => { +export function NotificationInsight({ insights }: NotificationInsightProps) { return (
    @@ -40,4 +39,4 @@ export const NotificationInsight = ({ insights }: NotificationInsightProps) => {
    ); -}; +} diff --git a/packages/email/components/weekly-summary/WeeklySummaryNotificationEmail.tsx b/packages/email/components/weekly-summary/weekly-summary-notification-email.tsx similarity index 64% rename from packages/email/components/weekly-summary/WeeklySummaryNotificationEmail.tsx rename to packages/email/components/weekly-summary/weekly-summary-notification-email.tsx index 8a2d94f88d..8fae32a78d 100644 --- a/packages/email/components/weekly-summary/WeeklySummaryNotificationEmail.tsx +++ b/packages/email/components/weekly-summary/weekly-summary-notification-email.tsx @@ -1,11 +1,9 @@ import React from "react"; - -import { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; - -import { LiveSurveyNotification } from "./LiveSurveyNotification"; -import { NotificationFooter } from "./NotificationFooter"; -import { NotificationHeader } from "./NotificationHeader"; -import { NotificationInsight } from "./NotificationInsight"; +import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; +import { LiveSurveyNotification } from "./live-survey-notification"; +import { NotificationFooter } from "./notification-footer"; +import { NotificationHeader } from "./notification-header"; +import { NotificationInsight } from "./notification-insight"; interface WeeklySummaryNotificationEmailProps { notificationData: TWeeklySummaryNotificationResponse; @@ -15,28 +13,28 @@ interface WeeklySummaryNotificationEmailProps { endYear: number; } -export const WeeklySummaryNotificationEmail = ({ +export function WeeklySummaryNotificationEmail({ notificationData, startDate, endDate, startYear, endYear, -}: WeeklySummaryNotificationEmailProps) => { +}: WeeklySummaryNotificationEmailProps) { return (
    ); -}; +} diff --git a/packages/email/index.tsx b/packages/email/index.tsx index 76ea7f1828..d7ff89c67e 100644 --- a/packages/email/index.tsx +++ b/packages/email/index.tsx @@ -1,6 +1,6 @@ import { render } from "@react-email/render"; import nodemailer from "nodemailer"; - +import type SMTPTransport from "nodemailer/lib/smtp-transport"; import { DEBUG, MAIL_FROM, @@ -13,26 +13,25 @@ import { } from "@formbricks/lib/constants"; import { createInviteToken, createToken, createTokenForLinkSurvey } from "@formbricks/lib/jwt"; import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; -import { TResponse } from "@formbricks/types/responses"; -import { TSurvey } from "@formbricks/types/surveys"; -import { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; +import type { TResponse } from "@formbricks/types/responses"; +import type { TSurvey } from "@formbricks/types/surveys"; +import type { TWeeklySummaryNotificationResponse } from "@formbricks/types/weeklySummary"; +import { ForgotPasswordEmail } from "./components/auth/forgot-password-email"; +import { PasswordResetNotifyEmail } from "./components/auth/password-reset-notify-email"; +import { VerificationEmail } from "./components/auth/verification-email"; +import { EmailTemplate } from "./components/general/email-template"; +import { InviteAcceptedEmail } from "./components/invite/invite-accepted-email"; +import { InviteEmail } from "./components/invite/invite-email"; +import { OnboardingInviteEmail } from "./components/invite/onboarding-invite-email"; +import { EmbedSurveyPreviewEmail } from "./components/survey/embed-survey-preview-email"; +import { LinkSurveyEmail } from "./components/survey/link-survey-email"; +import { ResponseFinishedEmail } from "./components/survey/response-finished-email"; +import { NoLiveSurveyNotificationEmail } from "./components/weekly-summary/no-live-survey-notification-email"; +import { WeeklySummaryNotificationEmail } from "./components/weekly-summary/weekly-summary-notification-email"; -import { ForgotPasswordEmail } from "./components/auth/ForgotPasswordEmail"; -import { PasswordResetNotifyEmail } from "./components/auth/PasswordResetNotifyEmail"; -import { VerificationEmail } from "./components/auth/VerificationEmail"; -import { EmailTemplate } from "./components/general/EmailTemplate"; -import { InviteAcceptedEmail } from "./components/invite/InviteAcceptedEmail"; -import { InviteEmail } from "./components/invite/InviteEmail"; -import { OnboardingInviteEmail } from "./components/invite/OnboardingInviteEmail"; -import { EmbedSurveyPreviewEmail } from "./components/survey/EmbedSurveyPreviewEmail"; -import { LinkSurveyEmail } from "./components/survey/LinkSurveyEmail"; -import { ResponseFinishedEmail } from "./components/survey/ResponseFinishedEmail"; -import { NoLiveSurveyNotificationEmail } from "./components/weekly-summary/NoLiveSurveyNotificationEmail"; -import { WeeklySummaryNotificationEmail } from "./components/weekly-summary/WeeklySummaryNotificationEmail"; +export const IS_SMTP_CONFIGURED = Boolean(SMTP_HOST && SMTP_PORT); -export const IS_SMTP_CONFIGURED: boolean = SMTP_HOST && SMTP_PORT ? true : false; - -interface sendEmailData { +interface SendEmailDataProps { to: string; replyTo?: string; subject: string; @@ -64,29 +63,26 @@ const getEmailSubject = (productName: string): string => { const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; -export const sendEmail = async (emailData: sendEmailData) => { - try { - if (IS_SMTP_CONFIGURED) { - let transporter = nodemailer.createTransport({ - host: SMTP_HOST, - port: SMTP_PORT, - secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports - auth: { - user: SMTP_USER, - pass: SMTP_PASSWORD, - }, - logger: DEBUG, - debug: DEBUG, - }); - const emailDefaults = { - from: `Formbricks <${MAIL_FROM || "noreply@formbricks.com"}>`, - }; - await transporter.sendMail({ ...emailDefaults, ...emailData }); - } else { - console.error(`Could not Email :: SMTP not configured :: ${emailData.subject}`); - } - } catch (error) { - throw error; +export const sendEmail = async (emailData: SendEmailDataProps) => { + if (IS_SMTP_CONFIGURED) { + const transporter = nodemailer.createTransport({ + host: SMTP_HOST, + port: SMTP_PORT, + secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports + auth: { + user: SMTP_USER, + pass: SMTP_PASSWORD, + }, + logger: DEBUG, + debug: DEBUG, + } as SMTPTransport.Options); + const emailDefaults = { + from: `Formbricks <${MAIL_FROM || "noreply@formbricks.com"}>`, + }; + await transporter.sendMail({ ...emailDefaults, ...emailData }); + } else { + // eslint-disable-next-line no-console -- necessary for logging email configuration errors + console.error(`Could not Email :: SMTP not configured :: ${emailData.subject}`); } }; @@ -202,8 +198,8 @@ export const sendEmbedSurveyPreviewEmail = async ( environmentId: string ) => { await sendEmail({ - to: to, - subject: subject, + to, + subject, html: render(EmailTemplate({ content: EmbedSurveyPreviewEmail({ html, environmentId }) })), }); }; @@ -212,7 +208,7 @@ export const sendLinkSurveyToVerifiedEmail = async (data: LinkSurveyEmailData) = const surveyId = data.surveyId; const email = data.email; const surveyData = data.surveyData; - const singleUseId = data.suId ?? null; + const singleUseId = data.suId; const token = createTokenForLinkSurvey(surveyId, email); const getSurveyLink = () => { if (singleUseId) { diff --git a/packages/email/package.json b/packages/email/package.json index 5dc313cc48..3d1d39427a 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -4,16 +4,22 @@ "description": "Email package", "main": "./index.tsx", "scripts": { - "clean": "rimraf .turbo node_modules dist" + "clean": "rimraf .turbo node_modules dist", + "lint": "eslint --ext .ts,.tsx --fix ." }, "dependencies": { + "@formbricks/config-typescript": "workspace:*", "@formbricks/lib": "workspace:*", "@formbricks/types": "workspace:*", "@formbricks/ui": "workspace:*", "@react-email/components": "^0.0.19", "@react-email/render": "^0.0.15", - "lucide-react": "^0.379.0", + "lucide-react": "^0.390.0", "nodemailer": "^6.9.13", "react-email": "^2.1.4" + }, + "devDependencies": { + "@types/nodemailer": "^6.4.15", + "@types/react": "18.3.3" } } diff --git a/packages/email/tsconfig.json b/packages/email/tsconfig.json new file mode 100644 index 0000000000..82881bf911 --- /dev/null +++ b/packages/email/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@formbricks/config-typescript/nextjs.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["/*"] + }, + "resolveJsonModule": true, + "strictNullChecks": true + }, + "include": [".", "../../packages/types/*.d.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/lib/i18n/i18n.mock.ts b/packages/lib/i18n/i18n.mock.ts index 457b1725d3..7fbff9491d 100644 --- a/packages/lib/i18n/i18n.mock.ts +++ b/packages/lib/i18n/i18n.mock.ts @@ -1,7 +1,5 @@ import { mockSegment } from "segment/tests/__mocks__/segment.mock"; - import { mockSurveyLanguages } from "survey/tests/__mock__/survey.mock"; - import { TSurvey, TSurveyCTAQuestion, @@ -13,7 +11,7 @@ import { TSurveyNPSQuestion, TSurveyOpenTextQuestion, TSurveyPictureSelectionQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveyRatingQuestion, TSurveyThankYouCard, TSurveyWelcomeCard, @@ -34,7 +32,7 @@ export const mockWelcomeCard: TSurveyWelcomeCard = { export const mockOpenTextQuestion: TSurveyOpenTextQuestion = { id: "lqht9sj5s6andjkmr9k1n54q", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What would you like to know?", }, @@ -51,7 +49,7 @@ export const mockOpenTextQuestion: TSurveyOpenTextQuestion = { export const mockSingleSelectQuestion: TSurveyMultipleChoiceQuestion = { id: "mvqx8t90np6isb6oel9eamzc", - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, choices: [ { id: "r52sul8ag19upaicit0fyqzo", @@ -104,7 +102,7 @@ export const mockMultiSelectQuestion: TSurveyMultipleChoiceQuestion = { ], shuffleOption: "none", id: "cpydxgsmjg8q9iwfa8wj4ida", - type: TSurveyQuestionType.MultipleChoiceMulti, + type: TSurveyQuestionTypeEnum.MultipleChoiceMulti, isDraft: true, }; @@ -128,7 +126,7 @@ export const mockPictureSelectQuestion: TSurveyPictureSelectionQuestion = { }, ], id: "a8monbe8hq0mivh3irfhd3i5", - type: TSurveyQuestionType.PictureSelection, + type: TSurveyQuestionTypeEnum.PictureSelection, isDraft: true, }; @@ -149,7 +147,7 @@ export const mockRatingQuestion: TSurveyRatingQuestion = { default: "Very good", }, id: "waldsboahjtgqhg5p18d1awz", - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, isDraft: true, }; @@ -165,7 +163,7 @@ export const mockNpsQuestion: TSurveyNPSQuestion = { default: "Extremely likely", }, id: "m9pemgdih2p4exvkmeeqq6jf", - type: TSurveyQuestionType.NPS, + type: TSurveyQuestionTypeEnum.NPS, isDraft: true, }; @@ -182,7 +180,7 @@ export const mockCtaQuestion: TSurveyCTAQuestion = { default: "Skip", }, id: "gwn15urom4ffnhfimwbz3vgc", - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, isDraft: true, }; @@ -195,7 +193,7 @@ export const mockConsentQuestion: TSurveyConsentQuestion = { default: "I agree to the terms and conditions", }, id: "av561aoif3i2hjlsl6krnsfm", - type: TSurveyQuestionType.Consent, + type: TSurveyQuestionTypeEnum.Consent, isDraft: true, }; @@ -206,7 +204,7 @@ export const mockDateQuestion: TSurveyDateQuestion = { }, format: "M-d-y", id: "ts2f6v2oo9jfmfli9kk6lki9", - type: TSurveyQuestionType.Date, + type: TSurveyQuestionTypeEnum.Date, isDraft: true, }; @@ -217,7 +215,7 @@ export const mockFileUploadQuestion: TSurveyFileUploadQuestion = { }, allowMultipleFiles: false, id: "ozzxo2jj1s6mj56c79q8pbef", - type: TSurveyQuestionType.FileUpload, + type: TSurveyQuestionTypeEnum.FileUpload, isDraft: true, }; @@ -231,7 +229,7 @@ export const mockCalQuestion: TSurveyCalQuestion = { }, calUserName: "rick/get-rick-rolled", id: "o3bnux6p42u9ew9d02l14r26", - type: TSurveyQuestionType.Cal, + type: TSurveyQuestionTypeEnum.Cal, isDraft: true, }; diff --git a/packages/lib/response/tests/__mocks__/data.mock.ts b/packages/lib/response/tests/__mocks__/data.mock.ts index fce5f7ec87..fb5d48a693 100644 --- a/packages/lib/response/tests/__mocks__/data.mock.ts +++ b/packages/lib/response/tests/__mocks__/data.mock.ts @@ -1,11 +1,9 @@ import { Prisma } from "@prisma/client"; import { isAfter, isBefore, isSameDay } from "date-fns"; - import { TDisplay } from "@formbricks/types/displays"; import { TResponse, TResponseFilterCriteria, TResponseUpdateInput } from "@formbricks/types/responses"; -import { TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; import { TTag } from "@formbricks/types/tags"; - import { responseNoteSelect } from "../../../responseNote/service"; import { responseSelection } from "../../service"; import { constantsForTests } from "../constants"; @@ -381,7 +379,7 @@ export const mockSurveySummaryOutput = { id: "ars2tjk8hsi8oqk1uac00mo8", inputType: "text", required: false, - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, }, responseCount: 0, samples: [], diff --git a/packages/lib/response/utils.ts b/packages/lib/response/utils.ts index 208d42e4fe..380eb2e7e4 100644 --- a/packages/lib/response/utils.ts +++ b/packages/lib/response/utils.ts @@ -1,7 +1,5 @@ import "server-only"; - import { Prisma } from "@prisma/client"; - import { TResponse, TResponseFilterCriteria, @@ -22,10 +20,9 @@ import { TSurveyQuestionSummaryOpenText, TSurveyQuestionSummaryPictureSelection, TSurveyQuestionSummaryRating, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveySummary, } from "@formbricks/types/surveys"; - import { getLocalizedValue } from "../i18n/utils"; import { processResponseData } from "../responses"; import { getTodaysDateTimeFormatted } from "../time"; @@ -701,7 +698,7 @@ export const getQuestionWiseSummary = ( survey.questions.forEach((question, idx) => { switch (question.type) { - case TSurveyQuestionType.OpenText: { + case TSurveyQuestionTypeEnum.OpenText: { let values: TSurveyQuestionSummaryOpenText["samples"] = []; responses.forEach((response) => { const answer = response.data[question.id]; @@ -726,8 +723,8 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.MultipleChoiceSingle: - case TSurveyQuestionType.MultipleChoiceMulti: { + case TSurveyQuestionTypeEnum.MultipleChoiceSingle: + case TSurveyQuestionTypeEnum.MultipleChoiceMulti: { let values: TSurveyQuestionSummaryMultipleChoice["choices"] = []; // check last choice is others or not const lastChoice = question.choices[question.choices.length - 1]; @@ -808,7 +805,7 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.PictureSelection: { + case TSurveyQuestionTypeEnum.PictureSelection: { let values: TSurveyQuestionSummaryPictureSelection["choices"] = []; const choiceCountMap: Record = {}; @@ -849,7 +846,7 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.Rating: { + case TSurveyQuestionTypeEnum.Rating: { let values: TSurveyQuestionSummaryRating["choices"] = []; const choiceCountMap: Record = {}; const range = question.range; @@ -899,7 +896,7 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.NPS: { + case TSurveyQuestionTypeEnum.NPS: { const data = { promoters: 0, passives: 0, @@ -956,7 +953,7 @@ export const getQuestionWiseSummary = ( }); break; } - case TSurveyQuestionType.CTA: { + case TSurveyQuestionTypeEnum.CTA: { const data = { clicked: 0, dismissed: 0, @@ -988,7 +985,7 @@ export const getQuestionWiseSummary = ( }); break; } - case TSurveyQuestionType.Consent: { + case TSurveyQuestionTypeEnum.Consent: { const data = { accepted: 0, dismissed: 0, @@ -1023,7 +1020,7 @@ export const getQuestionWiseSummary = ( break; } - case TSurveyQuestionType.Date: { + case TSurveyQuestionTypeEnum.Date: { let values: TSurveyQuestionSummaryDate["samples"] = []; responses.forEach((response) => { const answer = response.data[question.id]; @@ -1048,7 +1045,7 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.FileUpload: { + case TSurveyQuestionTypeEnum.FileUpload: { let values: TSurveyQuestionSummaryFileUpload["files"] = []; responses.forEach((response) => { const answer = response.data[question.id]; @@ -1073,7 +1070,7 @@ export const getQuestionWiseSummary = ( values = []; break; } - case TSurveyQuestionType.Cal: { + case TSurveyQuestionTypeEnum.Cal: { const data = { booked: 0, skipped: 0, @@ -1106,7 +1103,7 @@ export const getQuestionWiseSummary = ( break; } - case TSurveyQuestionType.Matrix: { + case TSurveyQuestionTypeEnum.Matrix: { const rows = question.rows.map((row) => getLocalizedValue(row, "default")); const columns = question.columns.map((column) => getLocalizedValue(column, "default")); let totalResponseCount = 0; @@ -1163,7 +1160,7 @@ export const getQuestionWiseSummary = ( }); break; } - case TSurveyQuestionType.Address: { + case TSurveyQuestionTypeEnum.Address: { let values: TSurveyQuestionSummaryAddress["samples"] = []; responses.forEach((response) => { const answer = response.data[question.id]; diff --git a/packages/lib/segment/service.ts b/packages/lib/segment/service.ts index 65cdb80477..e1025fc6a0 100644 --- a/packages/lib/segment/service.ts +++ b/packages/lib/segment/service.ts @@ -1,5 +1,4 @@ import { Prisma } from "@prisma/client"; - import { prisma } from "@formbricks/database"; import { ZString } from "@formbricks/types/common"; import { ZId } from "@formbricks/types/environment"; @@ -23,7 +22,6 @@ import { ZSegmentFilters, ZSegmentUpdateInput, } from "@formbricks/types/segment"; - import { getActionCountInLastMonth, getActionCountInLastQuarter, @@ -50,7 +48,7 @@ type PrismaSegment = Prisma.SegmentGetPayload<{ }; }>; -export const selectSegment: Prisma.SegmentDefaultArgs["select"] = { +export const selectSegment = { id: true, createdAt: true, updatedAt: true, @@ -62,6 +60,8 @@ export const selectSegment: Prisma.SegmentDefaultArgs["select"] = { surveys: { select: { id: true, + name: true, + status: true, }, }, }; diff --git a/packages/lib/survey/tests/__mock__/survey.mock.ts b/packages/lib/survey/tests/__mock__/survey.mock.ts index 4b979b9945..51560a5642 100644 --- a/packages/lib/survey/tests/__mock__/survey.mock.ts +++ b/packages/lib/survey/tests/__mock__/survey.mock.ts @@ -1,5 +1,4 @@ import { Prisma } from "@prisma/client"; - import { TActionClass } from "@formbricks/types/actionClasses"; import { TAttributeClass } from "@formbricks/types/attributeClasses"; import { TOrganization } from "@formbricks/types/organizations"; @@ -9,11 +8,10 @@ import { TSurveyInput, TSurveyLanguage, TSurveyQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveyWelcomeCard, } from "@formbricks/types/surveys"; import { TUser } from "@formbricks/types/user"; - import { selectPerson } from "../../../person/service"; import { selectSurvey } from "../../service"; @@ -147,7 +145,7 @@ export const mockAttributeClass: TAttributeClass = { const mockQuestion: TSurveyQuestion = { id: mockId, - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Question Text", de: "Fragetext" }, required: false, inputType: "text", diff --git a/packages/lib/templates.ts b/packages/lib/templates.ts index 23fc907458..6759ccdda8 100644 --- a/packages/lib/templates.ts +++ b/packages/lib/templates.ts @@ -1,5 +1,4 @@ import { createId } from "@paralleldrive/cuid2"; - import { TActionClass } from "@formbricks/types/actionClasses"; import { TSurveyCTAQuestion, @@ -7,7 +6,7 @@ import { TSurveyHiddenFields, TSurveyInput, TSurveyOpenTextQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, TSurveyStatus, TSurveyThankYouCard, TSurveyType, @@ -54,7 +53,7 @@ export const testTemplate: TTemplate = { questions: [ { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter some text:" }, required: true, @@ -63,7 +62,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter some text:" }, required: false, @@ -73,7 +72,7 @@ export const testTemplate: TTemplate = { { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter an email" }, required: true, @@ -82,7 +81,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter an email" }, required: false, @@ -91,7 +90,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a number" }, required: true, @@ -100,7 +99,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a number" }, required: false, @@ -109,7 +108,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a phone number" }, required: true, @@ -118,7 +117,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a phone number" }, required: false, @@ -127,7 +126,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a url" }, required: true, @@ -136,7 +135,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "This is an open text question" }, subheader: { default: "Please enter a url" }, required: false, @@ -145,7 +144,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "This ia a Multiple choice Single question" }, subheader: { default: "Please select one of the following" }, required: true, @@ -163,7 +162,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "This ia a Multiple choice Single question" }, subheader: { default: "Please select one of the following" }, required: false, @@ -181,7 +180,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceMulti, + type: TSurveyQuestionTypeEnum.MultipleChoiceMulti, headline: { default: "This ia a Multiple choice Multiple question" }, subheader: { default: "Please select some from the following" }, required: true, @@ -199,7 +198,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceMulti, + type: TSurveyQuestionTypeEnum.MultipleChoiceMulti, headline: { default: "This ia a Multiple choice Multiple question" }, subheader: { default: "Please select some from the following" }, required: false, @@ -217,7 +216,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: true, lowerLabel: { default: "Low" }, @@ -227,7 +226,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: false, lowerLabel: { default: "Low" }, @@ -237,7 +236,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: true, lowerLabel: { default: "Low" }, @@ -247,7 +246,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: false, lowerLabel: { default: "Low" }, @@ -257,7 +256,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: true, lowerLabel: { default: "Low" }, @@ -267,7 +266,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "This is a rating question" }, required: false, lowerLabel: { default: "Low" }, @@ -278,7 +277,7 @@ export const testTemplate: TTemplate = { { id: createId(), - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "This is a CTA question" }, html: { default: "This is a test CTA" }, buttonLabel: { default: "Click" }, @@ -289,7 +288,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "This is a CTA question" }, html: { default: "This is a test CTA" }, buttonLabel: { default: "Click" }, @@ -300,7 +299,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.PictureSelection, + type: TSurveyQuestionTypeEnum.PictureSelection, headline: { default: "This is a Picture select" }, allowMulti: true, required: true, @@ -317,7 +316,7 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.PictureSelection, + type: TSurveyQuestionTypeEnum.PictureSelection, headline: { default: "This is a Picture select" }, allowMulti: true, required: false, @@ -334,14 +333,14 @@ export const testTemplate: TTemplate = { }, { id: createId(), - type: TSurveyQuestionType.Consent, + type: TSurveyQuestionTypeEnum.Consent, headline: { default: "This is a Consent question" }, required: true, label: { default: "I agree to the terms and conditions" }, }, { id: createId(), - type: TSurveyQuestionType.Consent, + type: TSurveyQuestionTypeEnum.Consent, headline: { default: "This is a Consent question" }, required: false, label: { default: "I agree to the terms and conditions" }, @@ -366,7 +365,7 @@ export const templates: TTemplate[] = [ default: '

    We would love to understand your user experience better. Sharing your insight helps a lot.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "skipped", destination: "end" }], headline: { default: "You are one of our power users! Do you have 5 minutes?" }, required: false, @@ -376,7 +375,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How disappointed would you be if you could no longer use {{productName}}?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -398,7 +397,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "What is your role?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -428,21 +427,21 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What type of people do you think would most benefit from {{productName}}?" }, required: true, inputType: "text", }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What is the main benefit you receive from {{productName}}?" }, required: true, inputType: "text", }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "How can we improve {{productName}} for you?" }, subheader: { default: "Please be as specific as possible." }, required: true, @@ -462,7 +461,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "What is your role?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -492,7 +491,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "What's your company size?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -522,7 +521,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How did you hear about us first?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -564,7 +563,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { value: "Difficult to use", condition: "equals", destination: "sxwpskjgzzpmkgfxzi15inif" }, @@ -586,7 +585,7 @@ export const templates: TTemplate[] = [ }, { id: "sxwpskjgzzpmkgfxzi15inif", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What would have made {{productName}} easier to use?" }, required: true, @@ -599,7 +598,7 @@ export const templates: TTemplate[] = [ default: '

    We\'d love to keep you as a customer. Happy to offer a 30% discount for the next year.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "clicked", destination: "end" }], headline: { default: "Get 30% off for the next year!" }, required: true, @@ -610,7 +609,7 @@ export const templates: TTemplate[] = [ }, { id: "l054desub14syoie7n202vq4", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What features are you missing?" }, required: true, @@ -622,7 +621,7 @@ export const templates: TTemplate[] = [ default: '

    We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "clicked", destination: "end" }], headline: { default: "So sorry to hear ๐Ÿ˜” Talk to our CEO directly!" }, required: true, @@ -646,7 +645,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, logic: [{ value: "No", condition: "equals", destination: "duz2qp8eftix9wty1l221x1h" }], shuffleOption: "none", choices: [ @@ -658,7 +657,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "yhfew1j3ng6luy7t7qynwj79" }], headline: { default: "Great to hear! Why did you recommend us?" }, required: true, @@ -667,7 +666,7 @@ export const templates: TTemplate[] = [ }, { id: "duz2qp8eftix9wty1l221x1h", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "So sad. Why not?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -675,7 +674,7 @@ export const templates: TTemplate[] = [ }, { id: "yhfew1j3ng6luy7t7qynwj79", - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, logic: [{ value: "No", condition: "equals", destination: "end" }], shuffleOption: "none", choices: [ @@ -687,7 +686,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What made you discourage them?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -707,7 +706,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { @@ -745,7 +744,7 @@ export const templates: TTemplate[] = [ }, { id: "aew2ymg51mffnt9db7duz9t3", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "bqiyml1ym74ggx6htwdo7rlu" }], headline: { default: "Sorry to hear. What was the biggest problem using {{productName}}?" }, required: true, @@ -754,7 +753,7 @@ export const templates: TTemplate[] = [ }, { id: "rnrfydttavtsf2t2nfx1df7m", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "bqiyml1ym74ggx6htwdo7rlu" }], headline: { default: "What did you expect {{productName}} would do for you?" }, required: true, @@ -767,7 +766,7 @@ export const templates: TTemplate[] = [ default: '

    We\'re happy to offer you a 20% discount on a yearly plan.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "clicked", destination: "end" }], headline: { default: "Sorry to hear! Get 20% off the first year." }, required: true, @@ -778,7 +777,7 @@ export const templates: TTemplate[] = [ }, { id: "rbhww1pix03r6sl4xc511wqg", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "bqiyml1ym74ggx6htwdo7rlu" }], headline: { default: "Which features are you missing?" }, required: true, @@ -788,7 +787,7 @@ export const templates: TTemplate[] = [ }, { id: "bqiyml1ym74ggx6htwdo7rlu", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [ { condition: "submitted", destination: "end" }, { condition: "skipped", destination: "end" }, @@ -813,7 +812,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 3, condition: "lessEqual", destination: "tk9wpw2gxgb8fa6pbpp3qq5l" }], range: 5, scale: "star", @@ -825,7 +824,7 @@ export const templates: TTemplate[] = [ { id: createId(), html: { default: '

    This helps us a lot.

    ' }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "clicked", destination: "end" }], headline: { default: "Happy to hear ๐Ÿ™ Please write a review for us!" }, required: true, @@ -835,7 +834,7 @@ export const templates: TTemplate[] = [ }, { id: "tk9wpw2gxgb8fa6pbpp3qq5l", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Sorry to hear! What is ONE thing we can do better?" }, required: true, subheader: { default: "Help us improve your experience." }, @@ -858,7 +857,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "Do you have 15 min to talk to us? ๐Ÿ™" }, html: { default: "You're one of our power users. We would love to interview you briefly!" }, buttonLabel: { default: "Book slot" }, @@ -880,7 +879,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { @@ -918,7 +917,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What made you think {{productName}} wouldn't be useful?" }, required: true, @@ -927,7 +926,7 @@ export const templates: TTemplate[] = [ }, { id: "r0zvi3vburf4hm7qewimzjux", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What was difficult about setting up or using {{productName}}?" }, required: true, @@ -936,7 +935,7 @@ export const templates: TTemplate[] = [ }, { id: "rbwz3y6y9avzqcfj30nu0qj4", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What features or functionality were missing?" }, required: true, @@ -945,7 +944,7 @@ export const templates: TTemplate[] = [ }, { id: "gn6298zogd2ipdz7js17qy5i", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "How could we make it easier for you to get started?" }, required: true, @@ -954,7 +953,7 @@ export const templates: TTemplate[] = [ }, { id: "c0exdyri3erugrv0ezkyseh6", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [], headline: { default: "What was it? Please explain:" }, required: false, @@ -976,7 +975,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", choices: [ { id: createId(), label: { default: "Ease of use" } }, @@ -990,7 +989,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", choices: [ { id: createId(), label: { default: "Documentation" } }, @@ -1004,7 +1003,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Would you like to add something?" }, required: false, subheader: { default: "Feel free to speak your mind, we do too." }, @@ -1023,7 +1022,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How disappointed would you be if you could no longer use {{productName}}?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -1045,7 +1044,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "How can we improve {{productName}} for you?" }, subheader: { default: "Please be as specific as possible." }, required: true, @@ -1066,7 +1065,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How did you hear about us first?" }, subheader: { default: "Please select one of the following options:" }, required: true, @@ -1109,7 +1108,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How easy was it to change your plan?" }, required: true, shuffleOption: "none", @@ -1138,7 +1137,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "Is the pricing information easy to understand?" }, required: true, shuffleOption: "none", @@ -1174,7 +1173,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "What's your primary goal for using {{productName}}?" }, required: true, shuffleOption: "none", @@ -1212,7 +1211,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, range: 5, scale: "number", headline: { default: "How important is [ADD FEATURE] for you?" }, @@ -1222,7 +1221,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", choices: [ { id: createId(), label: { default: "Aspect 1" } }, @@ -1248,7 +1247,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "How important is this feature for you?" }, required: true, lowerLabel: { default: "Not important" }, @@ -1258,7 +1257,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceMulti, + type: TSurveyQuestionTypeEnum.MultipleChoiceMulti, headline: { default: "What should be definitely include building this?" }, required: false, shuffleOption: "none", @@ -1296,7 +1295,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { value: "Bug report ๐Ÿž", condition: "equals", destination: "dnbiuq4l33l7jypcf2cg6vhh" }, @@ -1312,7 +1311,7 @@ export const templates: TTemplate[] = [ }, { id: "dnbiuq4l33l7jypcf2cg6vhh", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "a6c76m5oocw6xp9agf3d2tam" }], headline: { default: "What's broken?" }, required: true, @@ -1325,7 +1324,7 @@ export const templates: TTemplate[] = [ default: '

    We will fix this as soon as possible. Do you want to be notified when we did?

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [ { condition: "clicked", destination: "end" }, { condition: "skipped", destination: "end" }, @@ -1338,7 +1337,7 @@ export const templates: TTemplate[] = [ }, { id: "en9nuuevbf7g9oa9rzcs1l50", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Lovely, tell us more!" }, required: true, subheader: { default: "What problem do you want us to solve?" }, @@ -1361,7 +1360,7 @@ export const templates: TTemplate[] = [ questions: [ { id: "s6ss6znzxdwjod1hv16fow4w", - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 4, condition: "greaterEqual", destination: "ef0qo3l8iisd517ikp078u1p" }], range: 5, scale: "number", @@ -1372,7 +1371,7 @@ export const templates: TTemplate[] = [ }, { id: "mko13ptjj6tpi5u2pl7a5drz", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Why was it hard?" }, required: false, placeholder: { default: "Type your answer here..." }, @@ -1380,7 +1379,7 @@ export const templates: TTemplate[] = [ }, { id: "ef0qo3l8iisd517ikp078u1p", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What other tools would you like to use with {{productName}}?" }, required: false, subheader: { default: "We keep building integrations, yours can be next:" }, @@ -1401,7 +1400,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "Which other tools are you using?" }, required: true, shuffleOption: "none", @@ -1443,7 +1442,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "Was this page helpful?" }, required: true, shuffleOption: "none", @@ -1460,14 +1459,14 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Please elaborate:" }, required: false, inputType: "text", }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Page URL" }, required: false, inputType: "text", @@ -1487,7 +1486,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.NPS, + type: TSurveyQuestionTypeEnum.NPS, headline: { default: "How likely are you to recommend {{productName}} to a friend or colleague?" }, required: false, lowerLabel: { default: "Not likely" }, @@ -1495,7 +1494,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What made you give that rating?" }, required: false, inputType: "text", @@ -1514,7 +1513,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 3, condition: "lessEqual", destination: "vyo4mkw4ln95ts4ya7qp2tth" }], range: 5, scale: "smiley", @@ -1525,7 +1524,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Lovely! Is there anything we can do to improve your experience?" }, required: false, @@ -1534,7 +1533,7 @@ export const templates: TTemplate[] = [ }, { id: "vyo4mkw4ln95ts4ya7qp2tth", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Ugh, sorry! Is there anything we can do to improve your experience?" }, required: false, placeholder: { default: "Type your answer here..." }, @@ -1554,7 +1553,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: "3", condition: "lessEqual", destination: "dlpa0371pe7rphmggy2sgbap" }], range: 5, scale: "star", @@ -1566,7 +1565,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "gwo0fq5kug13e83fcour4n1w" }], headline: { default: "Lovely! What did you like about it?" }, required: true, @@ -1576,7 +1575,7 @@ export const templates: TTemplate[] = [ }, { id: "dlpa0371pe7rphmggy2sgbap", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Thanks for sharing! What did you not like?" }, required: true, longAnswer: true, @@ -1585,7 +1584,7 @@ export const templates: TTemplate[] = [ }, { id: "gwo0fq5kug13e83fcour4n1w", - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, range: 5, scale: "smiley", headline: { default: "How do you rate our communication?" }, @@ -1595,7 +1594,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Anything else you'd like to share with our team?" }, required: false, longAnswer: true, @@ -1604,7 +1603,7 @@ export const templates: TTemplate[] = [ }, { id: "sjbaghd1bi59pkjun2c97kw9", - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, logic: [], choices: [ { id: createId(), label: { default: "Google" } }, @@ -1619,7 +1618,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Lastly, we'd love to respond to your feedback. Please share your email:" }, required: false, inputType: "email", @@ -1641,7 +1640,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "How many hours does your team save per week by using {{productName}}?" }, required: true, shuffleOption: "none", @@ -1680,7 +1679,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, logic: [], shuffleOption: "none", choices: [ @@ -1694,7 +1693,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, logic: [], shuffleOption: "none", choices: [ @@ -1707,7 +1706,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "How else could we improve you experience with {{productName}}?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -1728,7 +1727,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, headline: { default: "How easy was it to achieve ... ?" }, required: true, lowerLabel: { default: "Not easy" }, @@ -1738,7 +1737,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What is one thing we could do better?" }, required: false, inputType: "text", @@ -1760,7 +1759,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, headline: { default: "Do you have all the info you need to give {{productName}} a try?" }, required: true, shuffleOption: "none", @@ -1781,14 +1780,14 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Whatโ€™s missing or unclear to you about {{productName}}?" }, required: false, inputType: "text", }, { id: createId(), - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "Thanks for your answer! Get 25% off your first 6 months:" }, required: false, buttonLabel: { default: "Get discount" }, @@ -1810,7 +1809,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, range: 5, scale: "number", headline: { default: "{{productName}} makes it easy for me to [ADD GOAL]" }, @@ -1820,7 +1819,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Thanks! How could we make it easier for you to [ADD GOAL]?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -1842,7 +1841,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 4, condition: "greaterEqual", destination: "lpof3d9t9hmnqvyjlpksmxd7" }], range: 5, scale: "number", @@ -1853,7 +1852,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Sorry about that! What would have made it easier for you?" }, required: true, @@ -1862,7 +1861,7 @@ export const templates: TTemplate[] = [ }, { id: "lpof3d9t9hmnqvyjlpksmxd7", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Lovely! Is there anything we can do to improve your experience?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -1883,7 +1882,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 4, condition: "greaterEqual", destination: "adcs3d9t9hmnqvyjlpksmxd7" }], range: 5, scale: "number", @@ -1894,7 +1893,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Ugh! What makes the results irrelevant for you?" }, required: true, @@ -1903,7 +1902,7 @@ export const templates: TTemplate[] = [ }, { id: "adcs3d9t9hmnqvyjlpksmxd7", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Lovely! Is there anything we can do to improve your experience?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -1924,7 +1923,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 4, condition: "greaterEqual", destination: "adcs3d9t9hmnqvyjlpkswi38" }], range: 5, scale: "number", @@ -1935,7 +1934,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Hmpft! What were you hoping for?" }, required: true, @@ -1944,7 +1943,7 @@ export const templates: TTemplate[] = [ }, { id: "adcs3d9t9hmnqvyjlpkswi38", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Lovely! Is there anything else you would like us to cover?" }, required: true, placeholder: { default: "Topics, trends, tutorials..." }, @@ -1965,7 +1964,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { value: "Working on it, boss", condition: "equals", destination: "nq88udm0jjtylr16ax87xlyc" }, @@ -1982,7 +1981,7 @@ export const templates: TTemplate[] = [ }, { id: "rjeac33gd13h3nnbrbid1fb2", - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: 4, condition: "greaterEqual", destination: "nq88udm0jjtylr16ax87xlyc" }], range: 5, scale: "number", @@ -1993,7 +1992,7 @@ export const templates: TTemplate[] = [ }, { id: "s0999bhpaz8vgf7ps264piek", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [ { condition: "submitted", destination: "end" }, { condition: "skipped", destination: "end" }, @@ -2005,7 +2004,7 @@ export const templates: TTemplate[] = [ }, { id: "nq88udm0jjtylr16ax87xlyc", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [ { condition: "skipped", destination: "end" }, { condition: "submitted", destination: "end" }, @@ -2017,7 +2016,7 @@ export const templates: TTemplate[] = [ }, { id: "u83zhr66knyfozccoqojx7bc", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What stopped you?" }, required: true, buttonLabel: { default: "Send" }, @@ -2043,7 +2042,7 @@ export const templates: TTemplate[] = [ default: '

    You seem to be considering signing up. Answer four questions and get 10% on any plan.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, logic: [{ condition: "skipped", destination: "end" }], headline: { default: "Answer this short survey, get 10% off!" }, required: false, @@ -2053,7 +2052,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [{ value: "5", condition: "equals", destination: "end" }], range: 5, scale: "number", @@ -2064,7 +2063,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { @@ -2093,7 +2092,7 @@ export const templates: TTemplate[] = [ }, { id: "atiw0j1oykb77zr0b7q4tixu", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "k3q0vt1ko0bzbsq076p7lnys" }], headline: { default: "What do you need but {{productName}} does not offer?" }, required: true, @@ -2102,7 +2101,7 @@ export const templates: TTemplate[] = [ }, { id: "j7jkpolm5xl7u0zt3g0e4z7d", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "k3q0vt1ko0bzbsq076p7lnys" }], headline: { default: "What options are you looking at?" }, required: true, @@ -2111,7 +2110,7 @@ export const templates: TTemplate[] = [ }, { id: "t5gvag2d7kq311szz5iyiy79", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "k3q0vt1ko0bzbsq076p7lnys" }], headline: { default: "What seems complicated to you?" }, required: true, @@ -2120,7 +2119,7 @@ export const templates: TTemplate[] = [ }, { id: "or0yhhrof753sq9ug4mdavgz", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "k3q0vt1ko0bzbsq076p7lnys" }], headline: { default: "What are you concerned about regarding pricing?" }, required: true, @@ -2129,7 +2128,7 @@ export const templates: TTemplate[] = [ }, { id: "v0pq1qcnm6ohiry5ywcd91qq", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Please explain:" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -2141,7 +2140,7 @@ export const templates: TTemplate[] = [ default: '

    Thanks a lot for taking the time to share feedback ๐Ÿ™

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "Thanks! Here is your code: SIGNUPNOW10" }, required: false, buttonUrl: "https://app.formbricks.com/auth/signup", @@ -2164,7 +2163,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, range: 5, scale: "number", headline: { @@ -2176,7 +2175,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What's ONE change we could make to improve your {{productName}} experience most?", }, @@ -2199,7 +2198,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [ { value: "2", condition: "lessEqual", destination: "y19mwcmstlc7pi7s4izxk1ll" }, { value: "3", condition: "equals", destination: "zm1hs8qkeuidh3qm0hx8pnw7" }, @@ -2215,7 +2214,7 @@ export const templates: TTemplate[] = [ }, { id: "y19mwcmstlc7pi7s4izxk1ll", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [ { condition: "submitted", destination: "end" }, { condition: "skipped", destination: "end" }, @@ -2227,7 +2226,7 @@ export const templates: TTemplate[] = [ }, { id: "zm1hs8qkeuidh3qm0hx8pnw7", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What, if anything, is holding you back from making a purchase today?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -2247,7 +2246,7 @@ export const templates: TTemplate[] = [ questions: [ { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [ { value: "5", condition: "equals", destination: "l2q1chqssong8n0xwaagyl8g" }, { value: "5", condition: "lessThan", destination: "k3s6gm5ivkc5crpycdbpzkpa" }, @@ -2261,7 +2260,7 @@ export const templates: TTemplate[] = [ }, { id: "k3s6gm5ivkc5crpycdbpzkpa", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [ { condition: "submitted", destination: "end" }, { condition: "skipped", destination: "end" }, @@ -2277,7 +2276,7 @@ export const templates: TTemplate[] = [ default: '

    Who thinks like you? You\'d do us a huge favor if you\'d share this weeks episode with your brain friend!

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "Thanks! โค๏ธ Spread the love with ONE friend." }, required: false, buttonUrl: "https://formbricks.com", @@ -2304,7 +2303,7 @@ export const templates: TTemplate[] = [ default: '

    We respect your time and kept it short ๐Ÿคธ

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "We love how you use {{productName}}! We'd love to pick your brain on a feature idea. Got a minute?", @@ -2316,7 +2315,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [ { value: "3", condition: "lessEqual", destination: "ndacjg9lqf5jcpq9w8ote666" }, { value: "4", condition: "greaterEqual", destination: "jmzgbo73cfjswlvhoynn7o0q" }, @@ -2330,7 +2329,7 @@ export const templates: TTemplate[] = [ }, { id: "ndacjg9lqf5jcpq9w8ote666", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What's most difficult for you when it comes to [PROBLEM AREA]?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -2342,7 +2341,7 @@ export const templates: TTemplate[] = [ default: '


    Read the text below, then answer 2 questions:


    Insert concept brief here. Add neccessary details but keep it concise and easy to understand.

    ', }, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "We're working on an idea to help with [PROBLEM AREA]." }, required: true, buttonLabel: { default: "Next" }, @@ -2351,7 +2350,7 @@ export const templates: TTemplate[] = [ }, { id: createId(), - type: TSurveyQuestionType.Rating, + type: TSurveyQuestionTypeEnum.Rating, logic: [ { value: "3", condition: "lessEqual", destination: "mmiuun3z4e7gk4ufuwh8lq8q" }, { value: "4", condition: "greaterEqual", destination: "gvzevzw4hkqd6dmlkcly6kd1" }, @@ -2365,7 +2364,7 @@ export const templates: TTemplate[] = [ }, { id: "mmiuun3z4e7gk4ufuwh8lq8q", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "bqmnpyku9etsgbtb322luzb2" }], headline: { default: "Got it. Why wouldn't this feature be valuable to you?" }, required: true, @@ -2374,7 +2373,7 @@ export const templates: TTemplate[] = [ }, { id: "gvzevzw4hkqd6dmlkcly6kd1", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Got it. What would be most valuable to you in this feature?" }, required: true, placeholder: { default: "Type your answer here..." }, @@ -2382,7 +2381,7 @@ export const templates: TTemplate[] = [ }, { id: "bqmnpyku9etsgbtb322luzb2", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "Anything else we should keep in mind?" }, required: false, placeholder: { default: "Type your answer here..." }, @@ -2403,7 +2402,7 @@ export const templates: TTemplate[] = [ questions: [ { id: "aq9dafe9nxe0kpm67b1os2z9", - type: TSurveyQuestionType.MultipleChoiceSingle, + type: TSurveyQuestionTypeEnum.MultipleChoiceSingle, shuffleOption: "none", logic: [ { value: "Difficult to use", condition: "equals", destination: "r0zvi3vburf4hm7qewimzjux" }, @@ -2436,7 +2435,7 @@ export const templates: TTemplate[] = [ }, { id: "r0zvi3vburf4hm7qewimzjux", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "What's difficult about using {{productName}}?" }, required: true, @@ -2445,7 +2444,7 @@ export const templates: TTemplate[] = [ }, { id: "g92s5wetp51ps6afmc6y7609", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Got it. Which alternative are you using instead?" }, required: true, @@ -2454,7 +2453,7 @@ export const templates: TTemplate[] = [ }, { id: "gn6298zogd2ipdz7js17qy5i", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Got it. How could we make it easier for you to get started?" }, required: true, @@ -2463,7 +2462,7 @@ export const templates: TTemplate[] = [ }, { id: "rbwz3y6y9avzqcfj30nu0qj4", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [{ condition: "submitted", destination: "end" }], headline: { default: "Got it. What features or functionality were missing?" }, required: true, @@ -2472,7 +2471,7 @@ export const templates: TTemplate[] = [ }, { id: "c0exdyri3erugrv0ezkyseh6", - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, logic: [], headline: { default: "Please add more details:" }, required: false, @@ -2493,7 +2492,7 @@ export const customSurvey = { questions: [ { id: createId(), - type: TSurveyQuestionType.OpenText, + type: TSurveyQuestionTypeEnum.OpenText, headline: { default: "What would you like to know?" }, subheader: { default: "This is an example survey." }, placeholder: { default: "Type your answer here..." }, @@ -2510,7 +2509,7 @@ export const getExampleSurveyTemplate = (webAppUrl: string, trigger: TActionClas (question) => ({ ...question, - type: TSurveyQuestionType.CTA, + type: TSurveyQuestionTypeEnum.CTA, headline: { default: "You did it ๐ŸŽ‰" }, html: { default: "You're all set up. Create your own survey to gather exactly the feedback you need :)", diff --git a/packages/surveys/package.json b/packages/surveys/package.json index 5145731f5e..51d402f54b 100644 --- a/packages/surveys/package.json +++ b/packages/surveys/package.json @@ -31,7 +31,7 @@ "build": "tsc && vite build", "build:dev": "tsc && vite build --mode dev", "go": "vite build --watch --mode dev", - "lint": "eslint . --ext .ts,.js,.tsx,.jsx", + "lint": "eslint src --fix --ext .ts,.js,.tsx,.jsx", "preview": "vite preview", "clean": "rimraf .turbo node_modules dist" }, diff --git a/packages/surveys/src/components/general/QuestionConditional.tsx b/packages/surveys/src/components/general/QuestionConditional.tsx index 2e76a1ba64..330a3c2915 100644 --- a/packages/surveys/src/components/general/QuestionConditional.tsx +++ b/packages/surveys/src/components/general/QuestionConditional.tsx @@ -11,10 +11,9 @@ import { NPSQuestion } from "@/components/questions/NPSQuestion"; import { OpenTextQuestion } from "@/components/questions/OpenTextQuestion"; import { PictureSelectionQuestion } from "@/components/questions/PictureSelectionQuestion"; import { RatingQuestion } from "@/components/questions/RatingQuestion"; - import { TResponseData, TResponseDataValue, TResponseTtc } from "@formbricks/types/responses"; import { TUploadFileConfig } from "@formbricks/types/storage"; -import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys"; +import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys"; interface QuestionConditionalProps { question: TSurveyQuestion; @@ -61,7 +60,7 @@ export const QuestionConditional = ({ } } - return question.type === TSurveyQuestionType.OpenText ? ( + return question.type === TSurveyQuestionTypeEnum.OpenText ? ( - ) : question.type === TSurveyQuestionType.MultipleChoiceSingle ? ( + ) : question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ? ( - ) : question.type === TSurveyQuestionType.MultipleChoiceMulti ? ( + ) : question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti ? ( - ) : question.type === TSurveyQuestionType.NPS ? ( + ) : question.type === TSurveyQuestionTypeEnum.NPS ? ( - ) : question.type === TSurveyQuestionType.CTA ? ( + ) : question.type === TSurveyQuestionTypeEnum.CTA ? ( - ) : question.type === TSurveyQuestionType.Rating ? ( + ) : question.type === TSurveyQuestionTypeEnum.Rating ? ( - ) : question.type === TSurveyQuestionType.Consent ? ( + ) : question.type === TSurveyQuestionTypeEnum.Consent ? ( - ) : question.type === TSurveyQuestionType.Date ? ( + ) : question.type === TSurveyQuestionTypeEnum.Date ? ( - ) : question.type === TSurveyQuestionType.PictureSelection ? ( + ) : question.type === TSurveyQuestionTypeEnum.PictureSelection ? ( - ) : question.type === TSurveyQuestionType.FileUpload ? ( + ) : question.type === TSurveyQuestionTypeEnum.FileUpload ? ( - ) : question.type === TSurveyQuestionType.Cal ? ( + ) : question.type === TSurveyQuestionTypeEnum.Cal ? ( - ) : question.type === TSurveyQuestionType.Matrix ? ( + ) : question.type === TSurveyQuestionTypeEnum.Matrix ? ( - ) : question.type === TSurveyQuestionType.Address ? ( + ) : question.type === TSurveyQuestionTypeEnum.Address ? ( ; export const ZLegacySurveyConsentQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Consent), + type: z.literal(TSurveyQuestionTypeEnum.Consent), html: z.string().optional(), label: z.string(), placeholder: z.string().optional(), @@ -53,7 +52,7 @@ export const ZLegacySurveyChoice = z.object({ export type TLegacySurveyChoice = z.infer; export const ZLegacySurveyMultipleChoiceSingleQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.MultipleChoiceSingle), + type: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceSingle), choices: z.array(ZLegacySurveyChoice), logic: z.array(ZSurveyMultipleChoiceLogic).optional(), shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(), @@ -65,7 +64,7 @@ export type TLegacySurveyMultipleChoiceSingleQuestion = z.infer< >; export const ZLegacySurveyMultipleChoiceMultiQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.MultipleChoiceMulti), + type: z.literal(TSurveyQuestionTypeEnum.MultipleChoiceMulti), choices: z.array(ZLegacySurveyChoice), logic: z.array(ZSurveyMultipleChoiceLogic).optional(), shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(), @@ -77,7 +76,7 @@ export type TLegacySurveyMultipleChoiceMultiQuestion = z.infer< >; export const ZLegacySurveyNPSQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.NPS), + type: z.literal(TSurveyQuestionTypeEnum.NPS), lowerLabel: z.string(), upperLabel: z.string(), logic: z.array(ZSurveyNPSLogic).optional(), @@ -86,7 +85,7 @@ export const ZLegacySurveyNPSQuestion = ZLegacySurveyQuestionBase.extend({ export type TLegacySurveyNPSQuestion = z.infer; export const ZLegacySurveyCTAQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.CTA), + type: z.literal(TSurveyQuestionTypeEnum.CTA), html: z.string().optional(), buttonUrl: z.string().optional(), buttonExternal: z.boolean(), @@ -97,7 +96,7 @@ export const ZLegacySurveyCTAQuestion = ZLegacySurveyQuestionBase.extend({ export type TLegacySurveyCTAQuestion = z.infer; export const ZLegacySurveyRatingQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Rating), + type: z.literal(TSurveyQuestionTypeEnum.Rating), scale: z.enum(["number", "smiley", "star"]), range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]), lowerLabel: z.string(), @@ -106,7 +105,7 @@ export const ZLegacySurveyRatingQuestion = ZLegacySurveyQuestionBase.extend({ }); export const ZLegacySurveyDateQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Date), + type: z.literal(TSurveyQuestionTypeEnum.Date), html: z.string().optional(), format: z.enum(["M-d-y", "d-M-y", "y-M-d"]), }); @@ -116,7 +115,7 @@ export type TLegacySurveyDateQuestion = z.infer; export const ZLegacySurveyPictureSelectionQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.PictureSelection), + type: z.literal(TSurveyQuestionTypeEnum.PictureSelection), allowMulti: z.boolean().optional().default(false), choices: z.array(ZSurveyPictureChoice), logic: z.array(ZSurveyPictureSelectionLogic).optional(), @@ -125,7 +124,7 @@ export const ZLegacySurveyPictureSelectionQuestion = ZLegacySurveyQuestionBase.e export type TLegacySurveyPictureSelectionQuestion = z.infer; export const ZLegacySurveyFileUploadQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.FileUpload), + type: z.literal(TSurveyQuestionTypeEnum.FileUpload), allowMultipleFiles: z.boolean(), maxSizeInMB: z.number().optional(), allowedFileExtensions: z.array(ZAllowedFileExtension).optional(), @@ -135,7 +134,7 @@ export const ZLegacySurveyFileUploadQuestion = ZLegacySurveyQuestionBase.extend( export type TLegacySurveyFileUploadQuestion = z.infer; export const ZLegacySurveyCalQuestion = ZLegacySurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Cal), + type: z.literal(TSurveyQuestionTypeEnum.Cal), calUserName: z.string(), logic: z.array(ZSurveyCalLogic).optional(), }); diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts index 4fd5610403..6f62df18ae 100644 --- a/packages/types/surveys.ts +++ b/packages/types/surveys.ts @@ -1,5 +1,4 @@ import { z } from "zod"; - import { ZActionClass, ZNoCodeConfig } from "./actionClasses"; import { ZAttributes } from "./attributes"; import { ZAllowedFileExtension, ZColor, ZPlacement } from "./common"; @@ -24,7 +23,7 @@ export const ZSurveyThankYouCard = z.object({ videoUrl: z.string().optional(), }); -export enum TSurveyQuestionType { +export enum TSurveyQuestionTypeEnum { FileUpload = "fileUpload", OpenText = "openText", MultipleChoiceSingle = "multipleChoiceSingle", @@ -271,7 +270,7 @@ export const ZSurveyOpenTextQuestionInputType = z.enum(["text", "email", "url", export type TSurveyOpenTextQuestionInputType = z.infer; export const ZSurveyOpenTextQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.OpenText), + type: z.literal(TSurveyQuestionTypeEnum.OpenText), placeholder: ZI18nString.optional(), longAnswer: z.boolean().optional(), logic: z.array(ZSurveyOpenTextLogic).optional(), @@ -281,7 +280,7 @@ export const ZSurveyOpenTextQuestion = ZSurveyQuestionBase.extend({ export type TSurveyOpenTextQuestion = z.infer; export const ZSurveyConsentQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Consent), + type: z.literal(TSurveyQuestionTypeEnum.Consent), html: ZI18nString.optional(), label: ZI18nString, placeholder: z.string().optional(), @@ -296,8 +295,8 @@ export type TShuffleOption = z.infer; export const ZSurveyMultipleChoiceQuestion = ZSurveyQuestionBase.extend({ type: z.union([ - z.literal(TSurveyQuestionType.MultipleChoiceSingle), - z.literal(TSurveyQuestionType.MultipleChoiceMulti), + z.literal(TSurveyQuestionTypeEnum.MultipleChoiceSingle), + z.literal(TSurveyQuestionTypeEnum.MultipleChoiceMulti), ]), choices: z.array(ZSurveyChoice), logic: z.array(ZSurveyMultipleChoiceLogic).optional(), @@ -307,7 +306,7 @@ export const ZSurveyMultipleChoiceQuestion = ZSurveyQuestionBase.extend({ (question) => { const { logic, type } = question; - if (type === TSurveyQuestionType.MultipleChoiceSingle) { + if (type === TSurveyQuestionTypeEnum.MultipleChoiceSingle) { // The single choice question should not have 'includesAll' logic return !logic?.some((l) => l.condition === "includesAll"); } else { @@ -324,7 +323,7 @@ export const ZSurveyMultipleChoiceQuestion = ZSurveyQuestionBase.extend({ export type TSurveyMultipleChoiceQuestion = z.infer; export const ZSurveyNPSQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.NPS), + type: z.literal(TSurveyQuestionTypeEnum.NPS), lowerLabel: ZI18nString.optional(), upperLabel: ZI18nString.optional(), logic: z.array(ZSurveyNPSLogic).optional(), @@ -333,7 +332,7 @@ export const ZSurveyNPSQuestion = ZSurveyQuestionBase.extend({ export type TSurveyNPSQuestion = z.infer; export const ZSurveyCTAQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.CTA), + type: z.literal(TSurveyQuestionTypeEnum.CTA), html: ZI18nString.optional(), buttonUrl: z.string().optional(), buttonExternal: z.boolean(), @@ -344,7 +343,7 @@ export const ZSurveyCTAQuestion = ZSurveyQuestionBase.extend({ export type TSurveyCTAQuestion = z.infer; export const ZSurveyRatingQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Rating), + type: z.literal(TSurveyQuestionTypeEnum.Rating), scale: z.enum(["number", "smiley", "star"]), range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]), lowerLabel: ZI18nString.optional(), @@ -353,7 +352,7 @@ export const ZSurveyRatingQuestion = ZSurveyQuestionBase.extend({ }); export const ZSurveyDateQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Date), + type: z.literal(TSurveyQuestionTypeEnum.Date), html: ZI18nString.optional(), format: z.enum(["M-d-y", "d-M-y", "y-M-d"]), }); @@ -363,7 +362,7 @@ export type TSurveyDateQuestion = z.infer; export type TSurveyRatingQuestion = z.infer; export const ZSurveyPictureSelectionQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.PictureSelection), + type: z.literal(TSurveyQuestionTypeEnum.PictureSelection), allowMulti: z.boolean().optional().default(false), choices: z.array(ZSurveyPictureChoice), logic: z.array(ZSurveyPictureSelectionLogic).optional(), @@ -372,7 +371,7 @@ export const ZSurveyPictureSelectionQuestion = ZSurveyQuestionBase.extend({ export type TSurveyPictureSelectionQuestion = z.infer; export const ZSurveyFileUploadQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.FileUpload), + type: z.literal(TSurveyQuestionTypeEnum.FileUpload), allowMultipleFiles: z.boolean(), maxSizeInMB: z.number().optional(), allowedFileExtensions: z.array(ZAllowedFileExtension).optional(), @@ -382,7 +381,7 @@ export const ZSurveyFileUploadQuestion = ZSurveyQuestionBase.extend({ export type TSurveyFileUploadQuestion = z.infer; export const ZSurveyCalQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Cal), + type: z.literal(TSurveyQuestionTypeEnum.Cal), calUserName: z.string(), logic: z.array(ZSurveyCalLogic).optional(), }); @@ -390,7 +389,7 @@ export const ZSurveyCalQuestion = ZSurveyQuestionBase.extend({ export type TSurveyCalQuestion = z.infer; export const ZSurveyMatrixQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Matrix), + type: z.literal(TSurveyQuestionTypeEnum.Matrix), rows: z.array(ZI18nString), columns: z.array(ZI18nString), logic: z.array(ZSurveyMatrixLogic).optional(), @@ -399,7 +398,7 @@ export const ZSurveyMatrixQuestion = ZSurveyQuestionBase.extend({ export type TSurveyMatrixQuestion = z.infer; export const ZSurveyAddressQuestion = ZSurveyQuestionBase.extend({ - type: z.literal(TSurveyQuestionType.Address), + type: z.literal(TSurveyQuestionTypeEnum.Address), isAddressLine1Required: z.boolean().default(false), isAddressLine2Required: z.boolean().default(false), isCityRequired: z.boolean().default(false), @@ -424,6 +423,30 @@ export const ZSurveyQuestion = z.union([ ZSurveyAddressQuestion, ]); +export type TSurveyQuestion = z.infer; + +export const ZSurveyQuestions = z.array(ZSurveyQuestion); + +export type TSurveyQuestions = z.infer; + +export const ZSurveyQuestionType = z.enum([ + TSurveyQuestionTypeEnum.Address, + TSurveyQuestionTypeEnum.CTA, + TSurveyQuestionTypeEnum.Consent, + TSurveyQuestionTypeEnum.Date, + TSurveyQuestionTypeEnum.FileUpload, + TSurveyQuestionTypeEnum.Matrix, + TSurveyQuestionTypeEnum.MultipleChoiceMulti, + TSurveyQuestionTypeEnum.MultipleChoiceSingle, + TSurveyQuestionTypeEnum.NPS, + TSurveyQuestionTypeEnum.OpenText, + TSurveyQuestionTypeEnum.PictureSelection, + TSurveyQuestionTypeEnum.Rating, + TSurveyQuestionTypeEnum.Cal, +]); + +export type TSurveyQuestionType = z.infer; + export const ZSurveyLanguage = z.object({ language: ZLanguage, default: z.boolean(), @@ -432,12 +455,6 @@ export const ZSurveyLanguage = z.object({ export type TSurveyLanguage = z.infer; -export type TSurveyQuestion = z.infer; - -export const ZSurveyQuestions = z.array(ZSurveyQuestion); - -export type TSurveyQuestions = z.infer; - export const ZSurveyQuestionsObject = z.object({ questions: ZSurveyQuestions, hiddenFields: ZSurveyHiddenFields, diff --git a/packages/types/weeklySummary.ts b/packages/types/weeklySummary.ts index 9452acad07..3e6e3a9a43 100644 --- a/packages/types/weeklySummary.ts +++ b/packages/types/weeklySummary.ts @@ -1,8 +1,7 @@ import { z } from "zod"; - import { ZAttributeClass } from "./attributeClasses"; import { ZResponseData } from "./responses"; -import { ZSurveyHiddenFields, ZSurveyQuestion, ZSurveyStatus } from "./surveys"; +import { ZSurveyHiddenFields, ZSurveyQuestion, ZSurveyQuestionType, ZSurveyStatus } from "./surveys"; import { ZUserNotificationSettings } from "./user"; const ZWeeklySummaryInsights = z.object({ @@ -18,7 +17,7 @@ export type TWeeklySummaryInsights = z.infer; export const ZWeeklySummarySurveyResponseData = z.object({ headline: z.string(), responseValue: z.union([z.string(), z.array(z.string())]), - questionType: z.string(), + questionType: ZSurveyQuestionType, }); export type TWeeklySummarySurveyResponseData = z.infer; @@ -28,7 +27,7 @@ export const ZWeeklySummaryNotificationDataSurvey = z.object({ name: z.string(), responses: z.array(ZWeeklySummarySurveyResponseData), responseCount: z.number(), - status: z.string(), + status: ZSurveyStatus, }); export type TWeeklySummaryNotificationDataSurvey = z.infer; diff --git a/packages/ui/QuestionFormInput/index.tsx b/packages/ui/QuestionFormInput/index.tsx index 240a0ff9ae..c5d4a84679 100644 --- a/packages/ui/QuestionFormInput/index.tsx +++ b/packages/ui/QuestionFormInput/index.tsx @@ -27,7 +27,7 @@ import { TSurveyRecallItem, } from "@formbricks/types/surveys"; -import { LanguageIndicator } from "../../ee/multiLanguage/components/LanguageIndicator"; +import { LanguageIndicator } from "../../ee/multi-language/components/language-indicator"; import { createI18nString } from "../../lib/i18n/utils"; import { FileInput } from "../FileInput"; import { Input } from "../Input"; diff --git a/packages/ui/ShareSurveyLink/components/LanguageDropdown.tsx b/packages/ui/ShareSurveyLink/components/LanguageDropdown.tsx index c47d516b37..26622e6ecd 100644 --- a/packages/ui/ShareSurveyLink/components/LanguageDropdown.tsx +++ b/packages/ui/ShareSurveyLink/components/LanguageDropdown.tsx @@ -5,7 +5,7 @@ import { getEnabledLanguages } from "@formbricks/lib/i18n/utils"; import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside"; import { TSurvey } from "@formbricks/types/surveys"; -import { getLanguageLabel } from "../../../ee/multiLanguage/lib/isoLanguages"; +import { getLanguageLabel } from "../../../ee/multi-language/lib/iso-languages"; import { Button } from "../../Button"; interface LanguageDropdownProps { diff --git a/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx b/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx index 274ccc9386..e845e76a99 100644 --- a/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx +++ b/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx @@ -1,5 +1,4 @@ import { CheckCircle2Icon } from "lucide-react"; - import { getLanguageCode, getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { formatDateWithOrdinal } from "@formbricks/lib/utils/datetime"; import { parseRecallInfo } from "@formbricks/lib/utils/recall"; @@ -9,9 +8,8 @@ import { TSurveyMatrixQuestion, TSurveyPictureSelectionQuestion, TSurveyQuestion, - TSurveyQuestionType, + TSurveyQuestionTypeEnum, } from "@formbricks/types/surveys"; - import { AddressResponse } from "../../AddressResponse"; import { FileUploadResponse } from "../../FileUploadResponse"; import { PictureSelectionResponse } from "../../PictureSelectionResponse"; @@ -64,23 +62,23 @@ export const SingleResponseCardBody = ({ }; const renderResponse = ( - questionType: TSurveyQuestionType, + questionType: TSurveyQuestionTypeEnum, responseData: string | number | string[] | Record, question: TSurveyQuestion ) => { switch (questionType) { - case TSurveyQuestionType.Rating: + case TSurveyQuestionTypeEnum.Rating: if (typeof responseData === "number") return ; - case TSurveyQuestionType.Date: + case TSurveyQuestionTypeEnum.Date: if (typeof responseData === "string") { const formattedDateString = formatDateWithOrdinal(new Date(responseData)); return

    {formattedDateString}

    ; } - case TSurveyQuestionType.Cal: + case TSurveyQuestionTypeEnum.Cal: if (typeof responseData === "string") return

    {responseData}

    ; - case TSurveyQuestionType.PictureSelection: + case TSurveyQuestionTypeEnum.PictureSelection: if (Array.isArray(responseData)) return ( ); - case TSurveyQuestionType.FileUpload: + case TSurveyQuestionTypeEnum.FileUpload: if (Array.isArray(responseData)) return ; - case TSurveyQuestionType.Matrix: + case TSurveyQuestionTypeEnum.Matrix: if (typeof responseData === "object" && !Array.isArray(responseData)) { return (question as TSurveyMatrixQuestion).rows.map((row) => { const languagCode = getLanguageCode(survey.languages, response.language); @@ -103,7 +101,7 @@ export const SingleResponseCardBody = ({ ); }); } - case TSurveyQuestionType.Address: + case TSurveyQuestionTypeEnum.Address: if (Array.isArray(responseData)) { return ; } diff --git a/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx b/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx index 27bba72ecb..4a13ed464b 100644 --- a/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx +++ b/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx @@ -9,7 +9,7 @@ import { TResponse } from "@formbricks/types/responses"; import { TSurvey } from "@formbricks/types/surveys"; import { TUser } from "@formbricks/types/user"; -import { getLanguageLabel } from "../../../ee/multiLanguage/lib/isoLanguages"; +import { getLanguageLabel } from "../../../ee/multi-language/lib/iso-languages"; import { PersonAvatar } from "../../Avatars"; import { SurveyStatusIndicator } from "../../SurveyStatusIndicator"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../Tooltip"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2a5677871..55f8fb1799 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,9 +429,6 @@ importers: next: specifier: 15.0.0-rc.0 version: 15.0.0-rc.0(@babel/core@7.24.6)(@opentelemetry/api@1.8.0)(@playwright/test@1.44.1)(react-dom@19.0.0-rc-935180c7e0-20240524)(react@19.0.0-rc-935180c7e0-20240524) - nodemailer: - specifier: ^6.9.13 - version: 6.9.13 optional: specifier: ^0.1.4 version: 0.1.4 @@ -647,15 +644,45 @@ importers: packages/ee: dependencies: + '@formbricks/database': + specifier: workspace:* + version: link:../database '@formbricks/lib': specifier: workspace:* version: link:../lib + '@paralleldrive/cuid2': + specifier: ^2.2.2 + version: 2.2.2 + '@radix-ui/react-collapsible': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) axios: specifier: ^1.7.2 version: 1.7.2 + lucide-react: + specifier: ^0.390.0 + version: 0.390.0(react@18.3.1) + next: + specifier: ^14.2.3 + version: 14.2.3(@playwright/test@1.44.1)(react-dom@18.3.1)(react@18.3.1) + next-auth: + specifier: ^4.24.7 + version: 4.24.7(next@14.2.3)(react-dom@18.3.1)(react@18.3.1) + react-hook-form: + specifier: ^7.51.5 + version: 7.51.5(react@18.3.1) + react-hot-toast: + specifier: ^2.4.1 + version: 2.4.1(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1) + server-only: + specifier: ^0.0.1 + version: 0.0.1 stripe: specifier: ^15.8.0 version: 15.8.0 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@formbricks/config-typescript': specifier: '*' @@ -669,9 +696,18 @@ importers: '@formbricks/ui': specifier: '*' version: link:../ui + '@types/dompurify': + specifier: ^3.0.5 + version: 3.0.5 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 packages/email: dependencies: + '@formbricks/config-typescript': + specifier: workspace:* + version: link:../config-typescript '@formbricks/lib': specifier: workspace:* version: link:../lib @@ -683,19 +719,26 @@ importers: version: link:../ui '@react-email/components': specifier: ^0.0.19 - version: 0.0.19(react@18.3.1) + version: 0.0.19(@types/react@18.3.3)(react@18.3.1) '@react-email/render': specifier: ^0.0.15 version: 0.0.15 lucide-react: - specifier: ^0.379.0 - version: 0.379.0(react@18.3.1) + specifier: ^0.390.0 + version: 0.390.0(react@18.3.1) nodemailer: specifier: ^6.9.13 version: 6.9.13 react-email: specifier: ^2.1.4 version: 2.1.4(eslint@8.57.0) + devDependencies: + '@types/nodemailer': + specifier: ^6.4.15 + version: 6.4.15 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 packages/js: devDependencies: @@ -8180,7 +8223,7 @@ packages: react: 19.0.0-rc-935180c7e0-20240524 dev: false - /@react-email/components@0.0.19(react@18.3.1): + /@react-email/components@0.0.19(@types/react@18.3.3)(react@18.3.1): resolution: {integrity: sha512-yf49eIq0NDDXzO2RTZaT8fKa16eKUFMdWWMx4V5Bq+b2JdGuAMobO5s9Ea6azSVL6RDcJ8epdY1TCR2kL2PPHw==} engines: {node: '>=18.0.0'} peerDependencies: @@ -8194,7 +8237,7 @@ packages: '@react-email/container': 0.0.12(react@18.3.1) '@react-email/font': 0.0.6(react@18.3.1) '@react-email/head': 0.0.9(react@18.3.1) - '@react-email/heading': 0.0.12(react@18.3.1) + '@react-email/heading': 0.0.12(@types/react@18.3.3)(react@18.3.1) '@react-email/hr': 0.0.8(react@18.3.1) '@react-email/html': 0.0.8(react@18.3.1) '@react-email/img': 0.0.8(react@18.3.1) @@ -8294,7 +8337,7 @@ packages: react: 19.0.0-rc-935180c7e0-20240524 dev: false - /@react-email/heading@0.0.12(react@18.3.1): + /@react-email/heading@0.0.12(@types/react@18.3.3)(react@18.3.1): resolution: {integrity: sha512-eB7mpnAvDmwvQLoPuwEiPRH4fPXWe6ltz6Ptbry2BlI88F0a2k11Ghb4+sZHBqg7vVw/MKbqEgtLqr3QJ/KfCQ==} engines: {node: '>=18.0.0'} peerDependencies: @@ -11049,6 +11092,12 @@ packages: dependencies: undici-types: 5.26.5 + /@types/nodemailer@6.4.15: + resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==} + dependencies: + '@types/node': 20.12.12 + dev: true + /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -17181,6 +17230,14 @@ packages: react: 19.0.0-rc-935180c7e0-20240524 dev: false + /lucide-react@0.390.0(react@18.3.1): + resolution: {integrity: sha512-APqbfEcVuHnZbiy3E97gYWLeBdkE4e6NbY6AuVETZDZVn/bQCHYUoHyxcUHyvRopfPOHhFUEvDyyQzHwM+S9/w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.3.1 + dev: false + /lucide@0.378.0: resolution: {integrity: sha512-bwWXuZf2jZbCI0Y+MWyv5bedWIxYKtgAEzC2Yl87Nrt/KcG9qTwAQxVFcZ6IwBioML06QUQsG+qRjyQvYHdbBQ==} dev: false @@ -19836,6 +19893,15 @@ packages: react: 18.3.1 dev: false + /react-hook-form@7.51.5(react@18.3.1): + resolution: {integrity: sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.3.1 + dev: false + /react-hook-form@7.51.5(react@19.0.0-rc-935180c7e0-20240524): resolution: {integrity: sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==} engines: {node: '>=12.22.0'}