From cb41e2d34457e058f95abef534d0fd7f171dfebe Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:44:37 +0530 Subject: [PATCH] fix: sets apps/web TS strict check to true (#7451) --- .../environments/[environmentId]/layout.tsx | 5 +- .../xm-templates/lib/utils.test.ts | 5 +- .../xm-templates/lib/xm-templates.test.ts | 6 +- .../(app)/(onboarding)/lib/onboarding.test.ts | 4 +- .../[organizationId]/landing/layout.tsx | 5 +- .../[organizationId]/landing/page.tsx | 2 +- .../organizations/[organizationId]/layout.tsx | 5 +- .../workspaces/new/layout.tsx | 5 +- .../components/OnboardingOptionsContainer.tsx | 2 +- .../environments/[environmentId]/layout.tsx | 5 +- .../app/(app)/billing-confirmation/page.tsx | 8 +- .../environments/[environmentId]/actions.ts | 105 ++-- .../environments/[environmentId]/page.tsx | 2 +- .../settings/(account)/layout.tsx | 5 +- .../(account)/notifications/actions.ts | 31 +- .../settings/(account)/notifications/page.tsx | 12 +- .../settings/(account)/profile/actions.ts | 66 +-- .../(organization)/enterprise/page.tsx | 2 +- .../(organization)/general/actions.ts | 97 ++-- .../general/components/DeleteOrganization.tsx | 2 +- .../components/EditOrganizationNameForm.tsx | 2 +- .../settings/(organization)/layout.tsx | 2 +- .../settings/components/SettingsTitle.tsx | 2 +- .../[environmentId]/settings/page.tsx | 2 +- .../surveys/[surveyId]/(analysis)/layout.tsx | 2 +- .../responses/components/ResponseTable.tsx | 4 +- .../components/ResponseTableColumns.tsx | 6 +- .../(analysis)/responses/lib/utils.test.ts | 3 +- .../(analysis)/responses/lib/utils.ts | 23 +- .../[surveyId]/(analysis)/responses/page.tsx | 2 +- .../[surveyId]/(analysis)/summary/actions.ts | 75 ++- .../summary/components/NPSSummary.tsx | 6 +- .../summary/components/SummaryMetadata.tsx | 2 +- .../summary/components/SummaryPage.tsx | 2 +- .../summary/components/share-survey-modal.tsx | 12 +- .../shareEmbedModal/qr-code-tab.tsx | 4 +- .../shareEmbedModal/success-view.tsx | 10 +- .../(analysis)/summary/lib/survey.test.ts | 4 +- .../summary/lib/surveySummary.test.ts | 439 +++++++++++--- .../(analysis)/summary/lib/surveySummary.ts | 41 +- .../surveys/[surveyId]/actions.ts | 81 ++- .../[surveyId]/components/CustomFilter.tsx | 4 +- .../components/ElementsComboBox.tsx | 4 +- .../[surveyId]/components/ResponseFilter.tsx | 2 +- .../surveys/[surveyId]/page.tsx | 2 +- .../workspace/integrations/actions.ts | 96 ++-- .../components/AddIntegrationModal.tsx | 4 +- .../workspace/integrations/airtable/page.tsx | 2 +- .../components/AddIntegrationModal.tsx | 4 +- .../integrations/google-sheets/page.tsx | 2 +- .../notion/components/AddIntegrationModal.tsx | 4 +- .../notion/components/MappingRow.tsx | 6 +- .../notion/components/NotionWrapper.tsx | 2 +- .../workspace/integrations/notion/page.tsx | 2 +- .../workspace/integrations/page.tsx | 2 +- .../components/AddChannelMappingModal.tsx | 4 +- .../slack/components/SlackWrapper.tsx | 2 +- .../workspace/integrations/slack/page.tsx | 2 +- apps/web/app/(app)/layout.tsx | 2 +- apps/web/app/(auth)/layout.tsx | 2 +- .../lib/__mocks__/survey-follow-up.mock.ts | 3 + .../pipeline/lib/handleIntegrations.ts | 12 +- apps/web/app/api/auth/[...nextauth]/route.ts | 18 +- .../displays/lib/display.test.ts | 4 +- .../client/[environmentId]/displays/route.ts | 11 +- .../environment/lib/environmentState.test.ts | 1 - .../[environmentId]/environment/route.ts | 16 +- .../responses/[responseId]/route.ts | 48 +- .../responses/lib/contact.test.ts | 16 +- .../[environmentId]/responses/lib/contact.ts | 4 +- .../responses/lib/response.test.ts | 12 +- .../client/[environmentId]/responses/route.ts | 17 +- .../client/[environmentId]/storage/route.ts | 11 +- .../integrations/airtable/callback/route.ts | 19 +- .../app/api/v1/integrations/airtable/route.ts | 15 +- .../v1/integrations/airtable/tables/route.ts | 15 +- .../v1/integrations/notion/callback/route.ts | 15 +- .../app/api/v1/integrations/notion/route.ts | 15 +- .../v1/integrations/slack/callback/route.ts | 17 +- .../app/api/v1/integrations/slack/route.ts | 16 +- .../action-classes/[actionClassId]/route.ts | 51 +- .../action-classes/lib/action-classes.test.ts | 12 +- .../api/v1/management/action-classes/route.ts | 29 +- apps/web/app/api/v1/management/me/route.ts | 4 +- .../responses/[responseId]/route.ts | 48 +- .../management/responses/lib/contact.test.ts | 7 +- .../v1/management/responses/lib/contact.ts | 4 +- .../management/responses/lib/response.test.ts | 25 +- .../app/api/v1/management/responses/route.ts | 39 +- .../api/v1/management/storage/lib/utils.ts | 2 +- .../app/api/v1/management/storage/route.ts | 5 +- .../v1/management/surveys/[surveyId]/route.ts | 56 +- .../surveys/[surveyId]/singleUseIds/route.ts | 13 +- .../v1/management/surveys/lib/surveys.test.ts | 16 +- .../v1/management/surveys/lib/utils.test.ts | 1 + .../app/api/v1/management/surveys/route.ts | 35 +- .../webhooks/[webhookId]/lib/webhook.test.ts | 4 + .../app/api/v1/webhooks/[webhookId]/route.ts | 38 +- apps/web/app/api/v1/webhooks/route.ts | 29 +- .../displays/lib/contact.test.ts | 11 +- .../displays/lib/display.test.ts | 4 +- .../responses/lib/contact.test.ts | 6 +- .../[environmentId]/responses/lib/contact.ts | 4 +- .../responses/lib/response.test.ts | 25 +- .../responses/lib/utils.test.ts | 4 + .../[environmentId]/responses/lib/utils.ts | 2 +- .../client/[environmentId]/responses/route.ts | 10 +- apps/web/app/lib/api/validator.test.ts | 5 +- apps/web/app/lib/api/with-api-logging.ts | 34 +- apps/web/app/lib/survey-block-builder.test.ts | 8 +- apps/web/app/lib/survey-builder.test.ts | 7 +- .../app/setup/organization/create/actions.ts | 51 +- .../[accessType]/[fileName]/route.ts | 8 +- apps/web/lib/account/service.ts | 4 +- apps/web/lib/account/utils.ts | 11 - apps/web/lib/actionClass/service.test.ts | 16 +- apps/web/lib/actionClass/service.ts | 6 +- apps/web/lib/auth.test.ts | 10 - apps/web/lib/cache/index.ts | 5 +- apps/web/lib/crypto.test.ts | 12 +- .../lib/display/tests/__mocks__/data.mock.ts | 1 - apps/web/lib/environment/service.test.ts | 4 +- apps/web/lib/googleSheet/service.ts | 35 +- apps/web/lib/i18n/utils.ts | 11 +- apps/web/lib/integration/service.ts | 2 +- apps/web/lib/language/tests/language.test.ts | 2 +- apps/web/lib/project/service.test.ts | 25 +- .../lib/response/tests/__mocks__/data.mock.ts | 48 +- apps/web/lib/response/tests/response.test.ts | 2 +- apps/web/lib/response/utils.ts | 8 +- apps/web/lib/responses.test.ts | 20 +- apps/web/lib/slack/service.ts | 2 +- apps/web/lib/survey/service.test.ts | 8 +- apps/web/lib/survey/utils.ts | 2 +- apps/web/lib/tag/service.ts | 2 +- .../action-client/action-client-middleware.ts | 8 +- apps/web/lib/utils/colors.test.ts | 2 +- apps/web/lib/utils/datetime.test.ts | 2 +- apps/web/lib/utils/helper.ts | 16 +- apps/web/lib/utils/services.test.ts | 56 +- .../components/DeleteAccountModal/actions.ts | 29 +- .../components/SingleResponseCard/actions.ts | 267 ++++----- apps/web/modules/api/v2/auth/api-wrapper.ts | 7 +- .../api/v2/auth/authenticated-api-client.ts | 2 +- .../modules/api/v2/lib/tests/element.test.ts | 9 +- .../lib/contact-attribute-key.ts | 21 +- .../lib/tests/contact-attribute-key.test.ts | 46 +- .../lib/contact-attribute-key.ts | 14 +- .../lib/tests/contact-attribute-key.test.ts | 3 +- .../modules/api/v2/management/lib/services.ts | 11 +- .../v2/management/lib/tests/services.test.ts | 25 +- .../api/v2/management/lib/utils.test.ts | 16 +- .../responses/[responseId]/lib/display.ts | 4 +- .../responses/[responseId]/lib/response.ts | 16 +- .../responses/[responseId]/lib/survey.ts | 7 +- .../lib/tests/__mocks__/display.mock.ts | 2 - .../lib/tests/__mocks__/response.mock.ts | 3 +- .../responses/[responseId]/lib/utils.ts | 6 +- .../v2/management/responses/lib/contact.ts | 8 +- .../management/responses/lib/organization.ts | 16 +- .../v2/management/responses/lib/response.ts | 14 +- .../responses/lib/tests/organization.test.ts | 48 +- .../contacts/[contactId]/lib/contacts.test.ts | 4 +- .../contacts/[contactId]/lib/contacts.ts | 7 +- .../contacts/[contactId]/lib/response.test.ts | 4 +- .../contacts/[contactId]/lib/response.ts | 7 +- .../contacts/[contactId]/lib/surveys.test.ts | 4 +- .../contacts/[contactId]/lib/surveys.ts | 7 +- .../[segmentId]/lib/contact-attribute-key.ts | 7 +- .../segments/[segmentId]/lib/segment.ts | 7 +- .../segments/[segmentId]/lib/surveys.ts | 7 +- .../lib/tests/contact-attribute-key.test.ts | 4 +- .../[segmentId]/lib/tests/contact.test.ts | 9 +- .../[segmentId]/lib/tests/segment.test.ts | 4 +- .../[segmentId]/lib/tests/surveys.test.ts | 4 +- .../lib/tests/mocks/webhook.mock.ts | 1 + .../[webhookId]/lib/tests/webhook.test.ts | 24 +- .../webhooks/[webhookId]/lib/webhook.ts | 12 +- .../webhooks/lib/tests/webhook.test.ts | 26 +- .../api/v2/management/webhooks/lib/webhook.ts | 8 +- .../project-teams/lib/project-teams.ts | 28 +- .../project-teams/lib/utils.ts | 7 +- .../teams/[teamId]/lib/teams.ts | 6 +- .../[organizationId]/teams/lib/teams.ts | 10 +- .../[organizationId]/users/lib/users.ts | 16 +- .../auth/forgot-password/reset/actions.ts | 35 +- apps/web/modules/auth/hooks/use-sign-out.ts | 5 +- .../modules/auth/invite/lib/invite.test.ts | 4 +- apps/web/modules/auth/lib/authOptions.ts | 24 +- apps/web/modules/auth/lib/user.test.ts | 8 +- .../auth/login/components/login-form.tsx | 2 +- .../page.tsx | 6 +- apps/web/modules/auth/signup/actions.ts | 65 +-- .../modules/auth/signup/lib/invite.test.ts | 5 +- apps/web/modules/auth/signup/page.tsx | 6 +- .../verification-requested/actions.test.ts | 2 +- .../auth/verification-requested/actions.ts | 31 +- .../auth/verification-requested/page.tsx | 6 +- .../auth/verify-email-change/actions.ts | 39 +- .../modules/auth/verify-email-change/page.tsx | 6 +- .../auth/verify/components/sign-in.tsx | 2 +- apps/web/modules/auth/verify/page.tsx | 2 +- .../core/rate-limit/rate-limit-load.test.ts | 2 +- .../modules/ee/audit-logs/lib/handler.test.ts | 2 +- apps/web/modules/ee/audit-logs/lib/handler.ts | 18 +- .../ee/auth/saml/lib/preload-connection.ts | 4 +- apps/web/modules/ee/billing/actions.ts | 67 +-- apps/web/modules/ee/billing/page.tsx | 2 +- .../components/delete-contact-button.tsx | 2 +- apps/web/modules/ee/contacts/actions.ts | 223 ++++---- .../[environmentId]/user/lib/contact.test.ts | 2 +- .../v1/client/[environmentId]/user/route.ts | 12 +- .../lib/contact-attribute-key.test.ts | 1 + .../[contactAttributeKeyId]/route.ts | 53 +- .../lib/contact-attribute-keys.test.ts | 3 + .../contact-attribute-keys/route.ts | 30 +- .../v1/management/contact-attributes/route.ts | 8 +- .../management/contacts/[contactId]/route.ts | 32 +- .../api/v1/management/contacts/route.ts | 8 +- .../contacts/bulk/lib/tests/contact.test.ts | 18 +- .../api/v2/management/contacts/lib/contact.ts | 4 +- .../modules/ee/contacts/attributes/actions.ts | 231 ++++---- .../components/attributes-table.tsx | 4 +- .../components/contact-table-column.tsx | 2 +- .../ee/contacts/components/contacts-table.tsx | 4 +- .../components/upload-contacts-attribute.tsx | 2 +- .../components/upload-contacts-button.tsx | 9 +- apps/web/modules/ee/contacts/layout.tsx | 5 +- .../ee/contacts/lib/attributes.test.ts | 11 + .../lib/contact-attribute-keys.test.ts | 41 +- .../ee/contacts/lib/contact-attributes.ts | 4 +- .../ee/contacts/lib/contact-survey-link.ts | 5 +- .../modules/ee/contacts/lib/contacts.test.ts | 18 +- .../modules/ee/contacts/lib/surveys.test.ts | 8 +- .../lib/update-contact-attributes.test.ts | 9 + .../web/modules/ee/contacts/lib/utils.test.ts | 15 +- apps/web/modules/ee/contacts/lib/utils.ts | 17 +- .../modules/ee/contacts/segments/actions.ts | 207 +++---- .../components/create-segment-modal.tsx | 6 +- .../segments/components/segment-settings.tsx | 4 +- .../segments/lib/filter/prisma-query.test.ts | 6 +- .../ee/contacts/segments/lib/helper.test.ts | 4 +- .../ee/contacts/segments/lib/segments.test.ts | 8 +- .../ee/contacts/segments/lib/segments.ts | 2 +- .../ee/contacts/segments/lib/utils.test.ts | 5 +- .../ee/license-check/lib/license.test.ts | 2 +- apps/web/modules/ee/quotas/actions.ts | 176 +++--- .../components/quota-condition-builder.tsx | 8 +- .../ee/quotas/lib/evaluation-service.test.ts | 17 +- apps/web/modules/ee/quotas/lib/utils.test.ts | 41 +- .../web/modules/ee/role-management/actions.ts | 187 +++--- .../components/add-member-role.tsx | 4 +- .../ee/role-management/lib/membership.test.ts | 8 +- apps/web/modules/ee/sso/lib/providers.ts | 4 +- apps/web/modules/ee/sso/lib/team.ts | 5 +- .../ee/sso/lib/tests/sso-handlers.test.ts | 10 +- .../web/modules/ee/sso/lib/tests/team.test.ts | 2 +- .../ee/teams/project-teams/lib/team.test.ts | 8 +- .../web/modules/ee/teams/team-list/actions.ts | 143 +++-- .../components/create-team-modal.tsx | 2 +- .../ee/teams/team-list/lib/project.test.ts | 2 +- .../web/modules/ee/two-factor-auth/actions.ts | 79 +-- .../two-factor-auth/components/enter-code.tsx | 2 +- .../whitelabel/email-customization/actions.ts | 75 +-- .../favicon-customization/actions.ts | 79 +-- .../favicon-customization-settings.tsx | 8 +- .../ee/whitelabel/remove-branding/actions.ts | 89 ++- .../remove-branding/lib/project.test.ts | 2 +- .../modules/integrations/webhooks/actions.ts | 159 +++--- .../webhooks/components/add-webhook-modal.tsx | 13 +- .../webhooks/components/webhook-row-data.tsx | 2 +- .../components/webhook-settings-tab.tsx | 22 +- .../webhooks/components/webhook-table.tsx | 2 +- .../integrations/webhooks/lib/webhook.ts | 6 +- .../modules/integrations/webhooks/page.tsx | 2 +- apps/web/modules/organization/actions.ts | 78 ++- .../modules/organization/lib/utils.test.ts | 2 +- .../organization/settings/api-keys/actions.ts | 117 ++-- .../api-keys/components/edit-api-keys.tsx | 6 +- .../settings/api-keys/lib/api-key.ts | 2 +- .../settings/api-keys/lib/projects.test.ts | 4 +- .../organization/settings/api-keys/page.tsx | 2 +- .../organization/settings/teams/actions.ts | 535 ++++++++---------- .../edit-memberships/member-actions.tsx | 4 +- .../edit-memberships/organization-actions.tsx | 2 +- .../invite-member/bulk-invite-tab.tsx | 2 +- .../invite-member/individual-invite-tab.tsx | 2 +- .../invite-member/invite-member-modal.tsx | 3 +- .../settings/teams/lib/invite.test.ts | 7 +- .../settings/teams/lib/membership.test.ts | 17 +- .../(setup)/app-connection/actions.ts | 119 ++-- .../(setup)/components/ActionSettingsTab.tsx | 2 +- .../(setup)/components/AddActionModal.tsx | 4 +- apps/web/modules/projects/settings/actions.ts | 95 ++-- .../projects/settings/general/actions.ts | 51 +- .../components/edit-waiting-time-form.tsx | 2 +- apps/web/modules/projects/settings/layout.tsx | 5 +- apps/web/modules/projects/settings/lib/tag.ts | 6 +- .../settings/look/components/edit-logo.tsx | 8 +- .../settings/look/lib/project.test.ts | 3 +- apps/web/modules/projects/settings/page.tsx | 2 +- .../modules/projects/settings/tags/actions.ts | 189 +++---- .../modules/projects/settings/tags/page.tsx | 2 +- .../[organizationId]/invite/actions.ts | 73 +-- apps/web/modules/storage/file-upload.test.ts | 10 +- apps/web/modules/storage/utils.test.ts | 24 - apps/web/modules/storage/utils.ts | 19 +- .../components/element-form-input/index.tsx | 17 +- .../components/template-list/actions.ts | 65 +-- .../template-list/lib/utils.test.ts | 8 +- apps/web/modules/survey/editor/actions.ts | 253 ++++----- .../add-element-to-block-button.tsx | 3 +- .../components/address-element-form.tsx | 4 +- .../editor/components/animated-survey-bg.tsx | 2 +- .../editor/components/cal-element-form.tsx | 4 +- .../components/consent-element-form.tsx | 4 +- .../components/contact-info-element-form.tsx | 4 +- .../editor/components/cta-element-form.tsx | 4 +- .../editor/components/date-element-form.tsx | 8 +- .../editor/components/edit-ending-card.tsx | 2 +- .../editor/components/edit-welcome-card.tsx | 19 +- .../components/element-option-choice.tsx | 9 +- .../editor/components/elements-view.tsx | 2 +- .../editor/components/end-screen-form.tsx | 6 +- .../components/file-upload-element-form.tsx | 4 +- .../editor/components/image-survey-bg.tsx | 4 +- .../components/logic-editor-actions.tsx | 20 +- .../components/logic-editor-conditions.tsx | 13 +- .../editor/components/logo-settings-card.tsx | 4 +- .../editor/components/matrix-element-form.tsx | 4 +- .../multiple-choice-element-form.tsx | 11 +- .../editor/components/nps-element-form.tsx | 4 +- .../editor/components/open-element-form.tsx | 3 +- .../components/picture-selection-form.tsx | 7 +- .../components/ranking-element-form.tsx | 11 +- .../editor/components/rating-element-form.tsx | 4 +- .../components/rating-type-dropdown.tsx | 2 +- .../components/recontact-options-card.tsx | 4 +- .../components/response-options-card.tsx | 19 +- .../editor/components/settings-view.tsx | 3 +- .../survey/editor/components/styling-view.tsx | 4 +- .../editor/components/survey-editor.tsx | 19 +- .../editor/components/survey-menu-bar.tsx | 4 +- .../components/survey-variables-card.tsx | 3 +- .../editor/components/unsplash-images.tsx | 4 +- .../editor/components/when-to-send-card.tsx | 2 +- .../survey/editor/lib/action-builder.test.ts | 2 +- .../modules/survey/editor/lib/action-class.ts | 3 +- .../survey/editor/lib/action-utils.test.ts | 34 +- .../modules/survey/editor/lib/blocks.test.ts | 2 + .../check-external-urls-permission.test.ts | 27 +- .../editor/lib/logic-rule-engine.test.ts | 35 +- .../modules/survey/editor/lib/project.test.ts | 6 +- .../lib/shared-conditions-factory.test.ts | 2 +- .../editor/lib/shared-conditions-factory.ts | 4 +- .../modules/survey/editor/lib/user.test.ts | 8 +- apps/web/modules/survey/editor/lib/utils.tsx | 6 +- .../lib/validation-rules-helpers.test.ts | 8 +- .../modules/survey/editor/lib/validation.ts | 16 +- apps/web/modules/survey/editor/page.tsx | 7 +- apps/web/modules/survey/lib/project.test.ts | 2 +- apps/web/modules/survey/lib/utils.ts | 2 +- apps/web/modules/survey/link/layout.tsx | 2 +- .../survey/link/lib/metadata-utils.test.ts | 12 +- apps/web/modules/survey/link/metadata.test.ts | 1 + apps/web/modules/survey/list/actions.ts | 191 +++---- .../list/components/survey-dropdown-menu.tsx | 2 +- .../list/components/survey-type-indicator.tsx | 4 +- .../survey/list/lib/environment.test.ts | 12 +- .../components/localized-editor.tsx | 8 +- .../multi-language-surveys/lib/actions.ts | 193 +++---- .../modules/ui/components/alert/stories.tsx | 2 +- .../ui/components/code-block/prismjs.d.ts | 11 + .../ui/components/conditions-editor/index.tsx | 8 +- .../data-table-settings-modal-item.tsx | 2 +- .../components/data-table-toolbar.tsx | 2 +- .../components/selected-row-settings.tsx | 4 +- .../ui/components/delete-dialog/stories.tsx | 2 +- .../modules/ui/components/dialog/stories.tsx | 2 +- .../editor/components/recall-plugin.tsx | 9 +- .../components/element-toggle-table/index.tsx | 7 +- .../components/page-url-selector.tsx | 2 +- .../modules/ui/components/tab-nav/stories.tsx | 4 +- .../modules/ui/components/tooltip/stories.tsx | 2 +- .../api/management/contacts.spec.ts | 3 +- .../api/management/response.spec.ts | 33 +- .../playwright/api/management/survey.spec.ts | 4 +- .../playwright/api/management/webhook.spec.ts | 3 +- .../api/organization/project-team.spec.ts | 22 +- .../playwright/api/organization/team.spec.ts | 17 +- .../playwright/api/organization/user.spec.ts | 20 +- apps/web/playwright/api/role.spec.ts | 2 +- apps/web/playwright/lib/utils.ts | 2 +- packages/config-typescript/nextjs.json | 2 +- 394 files changed, 4368 insertions(+), 4110 deletions(-) delete mode 100644 apps/web/lib/account/utils.ts create mode 100644 apps/web/modules/ui/components/code-block/prismjs.d.ts diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/layout.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/layout.tsx index fe30ddd901..5a800502e1 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/layout.tsx +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/layout.tsx @@ -4,7 +4,10 @@ import { AuthorizationError } from "@formbricks/types/errors"; import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; import { authOptions } from "@/modules/auth/lib/authOptions"; -const OnboardingLayout = async (props) => { +const OnboardingLayout = async (props: { + params: Promise<{ environmentId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts index 0201e02a60..b5aef49c3a 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/utils.test.ts @@ -2,6 +2,7 @@ import "@testing-library/jest-dom/vitest"; import { cleanup } from "@testing-library/react"; import { afterEach, describe, expect, test } from "vitest"; import { TProject } from "@formbricks/types/project"; +import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/constants"; import { TXMTemplate } from "@formbricks/types/templates"; import { replacePresetPlaceholders } from "./utils"; @@ -39,13 +40,13 @@ const mockTemplate: TXMTemplate = { elements: [ { id: "q1", - type: "openText" as const, + type: "openText" as TSurveyElementTypeEnum.OpenText, inputType: "text" as const, headline: { default: "$[projectName] Question" }, subheader: { default: "" }, required: false, placeholder: { default: "" }, - charLimit: 1000, + charLimit: { enabled: true, max: 1000 }, }, ], }, diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/xm-templates.test.ts b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/xm-templates.test.ts index f78da5277e..31923c458a 100644 --- a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/xm-templates.test.ts +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/lib/xm-templates.test.ts @@ -14,7 +14,7 @@ describe("xm-templates", () => { }); test("getXMSurveyDefault returns default survey template", () => { - const tMock = vi.fn((key) => key) as TFunction; + const tMock = vi.fn((key: string) => key) as unknown as TFunction; const result = getXMSurveyDefault(tMock); expect(result).toEqual({ @@ -29,7 +29,7 @@ describe("xm-templates", () => { }); test("getXMTemplates returns all templates", () => { - const tMock = vi.fn((key) => key) as TFunction; + const tMock = vi.fn((key: string) => key) as unknown as TFunction; const result = getXMTemplates(tMock); expect(result).toHaveLength(6); @@ -44,7 +44,7 @@ describe("xm-templates", () => { test("getXMTemplates handles errors gracefully", async () => { const tMock = vi.fn(() => { throw new Error("Test error"); - }) as TFunction; + }) as unknown as TFunction; const result = getXMTemplates(tMock); diff --git a/apps/web/app/(app)/(onboarding)/lib/onboarding.test.ts b/apps/web/app/(app)/(onboarding)/lib/onboarding.test.ts index 6980cdf046..8a7904f386 100644 --- a/apps/web/app/(app)/(onboarding)/lib/onboarding.test.ts +++ b/apps/web/app/(app)/(onboarding)/lib/onboarding.test.ts @@ -19,8 +19,8 @@ describe("getTeamsByOrganizationId", () => { test("returns mapped teams", async () => { const mockTeams = [ - { id: "t1", name: "Team 1" }, - { id: "t2", name: "Team 2" }, + { id: "t1", name: "Team 1", createdAt: new Date(), updatedAt: new Date(), organizationId: "org1" }, + { id: "t2", name: "Team 2", createdAt: new Date(), updatedAt: new Date(), organizationId: "org1" }, ]; vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockTeams); const result = await getTeamsByOrganizationId("org1"); diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/layout.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/layout.tsx index e635d06efe..d3617a1711 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/layout.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/layout.tsx @@ -5,7 +5,10 @@ import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service"; import { getUserProjects } from "@/lib/project/service"; import { authOptions } from "@/modules/auth/lib/authOptions"; -const LandingLayout = async (props) => { +const LandingLayout = async (props: { + params: Promise<{ organizationId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx index c56ec26ae7..e5d2946522 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/landing/page.tsx @@ -10,7 +10,7 @@ import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils"; import { getOrganizationAuth } from "@/modules/organization/lib/utils"; import { Header } from "@/modules/ui/components/header"; -const Page = async (props) => { +const Page = async (props: { params: Promise<{ organizationId: string }> }) => { const params = await props.params; const t = await getTranslate(); diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/layout.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/layout.tsx index 261388c920..2f9e1b853c 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/layout.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/layout.tsx @@ -8,7 +8,10 @@ import { getTranslate } from "@/lingodotdev/server"; import { authOptions } from "@/modules/auth/lib/authOptions"; import { ToasterClient } from "@/modules/ui/components/toaster-client"; -const ProjectOnboardingLayout = async (props) => { +const ProjectOnboardingLayout = async (props: { + params: Promise<{ organizationId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/layout.tsx b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/layout.tsx index dece0bf1eb..c4d73b5c54 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/layout.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/[organizationId]/workspaces/new/layout.tsx @@ -8,7 +8,10 @@ import { getTranslate } from "@/lingodotdev/server"; import { authOptions } from "@/modules/auth/lib/authOptions"; import { getOrganizationProjectsLimit } from "@/modules/ee/license-check/lib/utils"; -const OnboardingLayout = async (props) => { +const OnboardingLayout = async (props: { + params: Promise<{ organizationId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer.tsx b/apps/web/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer.tsx index eac172c388..4cceeca055 100644 --- a/apps/web/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer.tsx +++ b/apps/web/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer.tsx @@ -16,7 +16,7 @@ interface OnboardingOptionsContainerProps { } export const OnboardingOptionsContainer = ({ options }: OnboardingOptionsContainerProps) => { - const getOptionCard = (option) => { + const getOptionCard = (option: OnboardingOptionsContainerProps["options"][number]) => { const Icon = option.icon; return ( { +const SurveyEditorEnvironmentLayout = async (props: { + params: Promise<{ environmentId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/billing-confirmation/page.tsx b/apps/web/app/(app)/billing-confirmation/page.tsx index 9b3add45e2..b5c0459914 100644 --- a/apps/web/app/(app)/billing-confirmation/page.tsx +++ b/apps/web/app/(app)/billing-confirmation/page.tsx @@ -3,13 +3,17 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper export const dynamic = "force-dynamic"; -const Page = async (props) => { +const Page = async (props: { searchParams: Promise<{ environmentId: string }> }) => { const searchParams = await props.searchParams; const { environmentId } = searchParams; + if (!environmentId) { + return null; + } + return ( - + ); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts index 68f4f3c22a..03ca87604e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts @@ -10,7 +10,6 @@ import { getOrganizationProjectsCount } from "@/lib/project/service"; import { updateUser } from "@/lib/user/service"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; import { getAccessControlPermission, @@ -26,66 +25,62 @@ const ZCreateProjectAction = z.object({ }); export const createProjectAction = authenticatedActionClient.inputSchema(ZCreateProjectAction).action( - withAuditLogging( - "created", - "project", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { - const { user } = ctx; + withAuditLogging("created", "project", async ({ ctx, parsedInput }) => { + const { user } = ctx; - const organizationId = parsedInput.organizationId; + const organizationId = parsedInput.organizationId; - await checkAuthorizationUpdated({ - userId: user.id, - organizationId: parsedInput.organizationId, - access: [ - { - data: parsedInput.data, - schema: ZProjectUpdateInput, - type: "organization", - roles: ["owner", "manager"], - }, - ], - }); - - const organization = await getOrganization(organizationId); - - if (!organization) { - throw new Error("Organization not found"); - } - - const organizationProjectsLimit = await getOrganizationProjectsLimit(organization.id); - const organizationProjectsCount = await getOrganizationProjectsCount(organization.id); - - if (organizationProjectsCount >= organizationProjectsLimit) { - throw new OperationNotAllowedError("Organization workspace limit reached"); - } - - if (parsedInput.data.teamIds && parsedInput.data.teamIds.length > 0) { - const isAccessControlAllowed = await getAccessControlPermission(organization.id); - - if (!isAccessControlAllowed) { - throw new OperationNotAllowedError("You do not have permission to manage roles"); - } - } - - const project = await createProject(parsedInput.organizationId, parsedInput.data); - const updatedNotificationSettings = { - ...user.notificationSettings, - alert: { - ...user.notificationSettings?.alert, + await checkAuthorizationUpdated({ + userId: user.id, + organizationId: parsedInput.organizationId, + access: [ + { + data: parsedInput.data, + schema: ZProjectUpdateInput, + type: "organization", + roles: ["owner", "manager"], }, - }; + ], + }); - await updateUser(user.id, { - notificationSettings: updatedNotificationSettings, - }); + const organization = await getOrganization(organizationId); - ctx.auditLoggingCtx.organizationId = organizationId; - ctx.auditLoggingCtx.projectId = project.id; - ctx.auditLoggingCtx.newObject = project; - return project; + if (!organization) { + throw new Error("Organization not found"); } - ) + + const organizationProjectsLimit = await getOrganizationProjectsLimit(organization.id); + const organizationProjectsCount = await getOrganizationProjectsCount(organization.id); + + if (organizationProjectsCount >= organizationProjectsLimit) { + throw new OperationNotAllowedError("Organization workspace limit reached"); + } + + if (parsedInput.data.teamIds && parsedInput.data.teamIds.length > 0) { + const isAccessControlAllowed = await getAccessControlPermission(organization.id); + + if (!isAccessControlAllowed) { + throw new OperationNotAllowedError("You do not have permission to manage roles"); + } + } + + const project = await createProject(parsedInput.organizationId, parsedInput.data); + const updatedNotificationSettings = { + ...user.notificationSettings, + alert: { + ...user.notificationSettings?.alert, + }, + }; + + await updateUser(user.id, { + notificationSettings: updatedNotificationSettings, + }); + + ctx.auditLoggingCtx.organizationId = organizationId; + ctx.auditLoggingCtx.projectId = project.id; + ctx.auditLoggingCtx.newObject = project; + return project; + }) ); const ZGetOrganizationsForSwitcherAction = z.object({ diff --git a/apps/web/app/(app)/environments/[environmentId]/page.tsx b/apps/web/app/(app)/environments/[environmentId]/page.tsx index 1ef791e033..8e72c09b61 100644 --- a/apps/web/app/(app)/environments/[environmentId]/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/page.tsx @@ -4,7 +4,7 @@ import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service"; import { getAccessFlags } from "@/lib/membership/utils"; import { getEnvironmentAuth } from "@/modules/environments/lib/utils"; -const EnvironmentPage = async (props) => { +const EnvironmentPage = async (props: { params: Promise<{ environmentId: string }> }) => { const params = await props.params; const { session, organization } = await getEnvironmentAuth(params.environmentId); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx index cfe33c687b..d9d8fe99da 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx @@ -4,7 +4,10 @@ import { getProjectByEnvironmentId } from "@/lib/project/service"; import { getTranslate } from "@/lingodotdev/server"; import { authOptions } from "@/modules/auth/lib/authOptions"; -const AccountSettingsLayout = async (props) => { +const AccountSettingsLayout = async (props: { + params: Promise<{ environmentId: string }>; + children: React.ReactNode; +}) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/actions.ts index 8b7334fcd5..e50127da45 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/actions.ts @@ -4,7 +4,6 @@ import { z } from "zod"; import { ZUserNotificationSettings } from "@formbricks/types/user"; import { getUser, updateUser } from "@/lib/user/service"; import { authenticatedActionClient } from "@/lib/utils/action-client"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; const ZUpdateNotificationSettingsAction = z.object({ @@ -14,24 +13,14 @@ const ZUpdateNotificationSettingsAction = z.object({ export const updateNotificationSettingsAction = authenticatedActionClient .inputSchema(ZUpdateNotificationSettingsAction) .action( - withAuditLogging( - "updated", - "user", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: Record; - }) => { - const oldObject = await getUser(ctx.user.id); - const result = await updateUser(ctx.user.id, { - notificationSettings: parsedInput.notificationSettings, - }); - ctx.auditLoggingCtx.userId = ctx.user.id; - ctx.auditLoggingCtx.oldObject = oldObject; - ctx.auditLoggingCtx.newObject = result; - return result; - } - ) + withAuditLogging("updated", "user", async ({ ctx, parsedInput }) => { + const oldObject = await getUser(ctx.user.id); + const result = await updateUser(ctx.user.id, { + notificationSettings: parsedInput.notificationSettings, + }); + ctx.auditLoggingCtx.userId = ctx.user.id; + ctx.auditLoggingCtx.oldObject = oldObject; + ctx.auditLoggingCtx.newObject = result; + return result; + }) ); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx index 5f79c304b4..9964f16b7d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/page.tsx @@ -16,8 +16,8 @@ const setCompleteNotificationSettings = ( notificationSettings: TUserNotificationSettings, memberships: Membership[] ): TUserNotificationSettings => { - const newNotificationSettings = { - alert: {}, + const newNotificationSettings: TUserNotificationSettings = { + alert: {} as Record, unsubscribedOrganizationIds: notificationSettings.unsubscribedOrganizationIds || [], }; for (const membership of memberships) { @@ -26,7 +26,8 @@ const setCompleteNotificationSettings = ( for (const environment of project.environments) { for (const survey of environment.surveys) { newNotificationSettings.alert[survey.id] = - notificationSettings[survey.id]?.responseFinished || + (notificationSettings as unknown as Record>)[survey.id] + ?.responseFinished || (notificationSettings.alert && notificationSettings.alert[survey.id]) || false; // check for legacy notification settings w/o "alerts" key } @@ -136,7 +137,10 @@ const getMemberships = async (userId: string): Promise => { return memberships; }; -const Page = async (props) => { +const Page = async (props: { + params: Promise<{ environmentId: string }>; + searchParams: Promise>; +}) => { const searchParams = await props.searchParams; const params = await props.params; const t = await getTranslate(); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts index b2d5548012..94486242bb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts @@ -20,7 +20,7 @@ import { rateLimitConfigs } from "@/modules/core/rate-limit/rate-limit-configs"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; import { sendForgotPasswordEmail, sendVerificationNewEmail } from "@/modules/email"; -function buildUserUpdatePayload(parsedInput: any): TUserUpdateInput { +function buildUserUpdatePayload(parsedInput: TUserPersonalInfoUpdateInput): TUserUpdateInput { return { ...(parsedInput.name && { name: parsedInput.name }), ...(parsedInput.locale && { locale: parsedInput.locale }), @@ -64,49 +64,35 @@ async function handleEmailUpdate({ } export const updateUserAction = authenticatedActionClient.inputSchema(ZUserPersonalInfoUpdateInput).action( - withAuditLogging( - "updated", - "user", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: TUserPersonalInfoUpdateInput; - }) => { - const oldObject = await getUser(ctx.user.id); - let payload = buildUserUpdatePayload(parsedInput); - payload = await handleEmailUpdate({ ctx, parsedInput, payload }); + withAuditLogging("updated", "user", async ({ ctx, parsedInput }) => { + const oldObject = await getUser(ctx.user.id); + let payload = buildUserUpdatePayload(parsedInput); + payload = await handleEmailUpdate({ ctx, parsedInput, payload }); - // Only proceed with updateUser if we have actual changes to make - let newObject = oldObject; - if (Object.keys(payload).length > 0) { - newObject = await updateUser(ctx.user.id, payload); - } - - ctx.auditLoggingCtx.userId = ctx.user.id; - ctx.auditLoggingCtx.oldObject = oldObject; - ctx.auditLoggingCtx.newObject = newObject; - - return true; + // Only proceed with updateUser if we have actual changes to make + let newObject = oldObject; + if (Object.keys(payload).length > 0) { + newObject = await updateUser(ctx.user.id, payload); } - ) + + ctx.auditLoggingCtx.userId = ctx.user.id; + ctx.auditLoggingCtx.oldObject = oldObject; + ctx.auditLoggingCtx.newObject = newObject; + + return true; + }) ); export const resetPasswordAction = authenticatedActionClient.action( - withAuditLogging( - "passwordReset", - "user", - async ({ ctx }: { ctx: AuthenticatedActionClientCtx; parsedInput: undefined }) => { - if (ctx.user.identityProvider !== "email") { - throw new OperationNotAllowedError("Password reset is not allowed for this user."); - } - - await sendForgotPasswordEmail(ctx.user); - - ctx.auditLoggingCtx.userId = ctx.user.id; - - return { success: true }; + withAuditLogging("passwordReset", "user", async ({ ctx }) => { + if (ctx.user.identityProvider !== "email") { + throw new OperationNotAllowedError("Password reset is not allowed for this user."); } - ) + + await sendForgotPasswordEmail(ctx.user); + + ctx.auditLoggingCtx.userId = ctx.user.id; + + return { success: true }; + }) ); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx index 9252c58e3b..738248445b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx @@ -11,7 +11,7 @@ import { Button } from "@/modules/ui/components/button"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; -const Page = async (props) => { +const Page = async (props: { params: Promise<{ environmentId: string }> }) => { const params = await props.params; const t = await getTranslate(); if (IS_FORMBRICKS_CLOUD) { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/actions.ts index 942a279f05..730b45fa0a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/actions.ts @@ -7,7 +7,6 @@ import { ZOrganizationUpdateInput } from "@formbricks/types/organizations"; import { deleteOrganization, getOrganization, updateOrganization } from "@/lib/organization/service"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils"; @@ -19,36 +18,26 @@ const ZUpdateOrganizationNameAction = z.object({ export const updateOrganizationNameAction = authenticatedActionClient .inputSchema(ZUpdateOrganizationNameAction) .action( - withAuditLogging( - "updated", - "organization", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: Record; - }) => { - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: parsedInput.organizationId, - access: [ - { - type: "organization", - schema: ZOrganizationUpdateInput.pick({ name: true }), - data: parsedInput.data, - roles: ["owner"], - }, - ], - }); - ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; - const oldObject = await getOrganization(parsedInput.organizationId); - const result = await updateOrganization(parsedInput.organizationId, parsedInput.data); - ctx.auditLoggingCtx.oldObject = oldObject; - ctx.auditLoggingCtx.newObject = result; - return result; - } - ) + withAuditLogging("updated", "organization", async ({ ctx, parsedInput }) => { + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: parsedInput.organizationId, + access: [ + { + type: "organization", + schema: ZOrganizationUpdateInput.pick({ name: true }), + data: parsedInput.data, + roles: ["owner"], + }, + ], + }); + ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; + const oldObject = await getOrganization(parsedInput.organizationId); + const result = await updateOrganization(parsedInput.organizationId, parsedInput.data); + ctx.auditLoggingCtx.oldObject = oldObject; + ctx.auditLoggingCtx.newObject = result; + return result; + }) ); const ZDeleteOrganizationAction = z.object({ @@ -58,33 +47,23 @@ const ZDeleteOrganizationAction = z.object({ export const deleteOrganizationAction = authenticatedActionClient .inputSchema(ZDeleteOrganizationAction) .action( - withAuditLogging( - "deleted", - "organization", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: Record; - }) => { - const isMultiOrgEnabled = await getIsMultiOrgEnabled(); - if (!isMultiOrgEnabled) throw new OperationNotAllowedError("Organization deletion disabled"); + withAuditLogging("deleted", "organization", async ({ ctx, parsedInput }) => { + const isMultiOrgEnabled = await getIsMultiOrgEnabled(); + if (!isMultiOrgEnabled) throw new OperationNotAllowedError("Organization deletion disabled"); - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: parsedInput.organizationId, - access: [ - { - type: "organization", - roles: ["owner"], - }, - ], - }); - ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; - const oldObject = await getOrganization(parsedInput.organizationId); - ctx.auditLoggingCtx.oldObject = oldObject; - return await deleteOrganization(parsedInput.organizationId); - } - ) + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: parsedInput.organizationId, + access: [ + { + type: "organization", + roles: ["owner"], + }, + ], + }); + ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; + const oldObject = await getOrganization(parsedInput.organizationId); + ctx.auditLoggingCtx.oldObject = oldObject; + return await deleteOrganization(parsedInput.organizationId); + }) ); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx index 14df58d4a9..8ee14807ee 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/DeleteOrganization.tsx @@ -107,7 +107,7 @@ const DeleteOrganizationModal = ({ }: DeleteOrganizationModalProps) => { const [inputValue, setInputValue] = useState(""); const { t } = useTranslation(); - const handleInputChange = (e) => { + const handleInputChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditOrganizationNameForm.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditOrganizationNameForm.tsx index aa974bb1e7..f2d94d86ce 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditOrganizationNameForm.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditOrganizationNameForm.tsx @@ -61,7 +61,7 @@ export const EditOrganizationNameForm = ({ organization, membershipRole }: EditO toast.error(errorMessage); } } catch (err) { - toast.error(`Error: ${err.message}`); + toast.error(`Error: ${err instanceof Error ? err.message : "Unknown error occurred"}`); } }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/layout.tsx index e271e4e4b9..a339563ee5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/layout.tsx @@ -4,7 +4,7 @@ import { getProjectByEnvironmentId } from "@/lib/project/service"; import { getTranslate } from "@/lingodotdev/server"; import { authOptions } from "@/modules/auth/lib/authOptions"; -const Layout = async (props) => { +const Layout = async (props: { params: Promise<{ environmentId: string }>; children: React.ReactNode }) => { const params = await props.params; const { children } = props; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsTitle.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsTitle.tsx index 31f61ee3da..f63fd2f8a4 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsTitle.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsTitle.tsx @@ -1,3 +1,3 @@ -export const SettingsTitle = ({ title }) => { +export const SettingsTitle = ({ title }: { title: string }) => { return

{title}

; }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/page.tsx index aa7d0fde9c..5fa7e3a7e6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/page.tsx @@ -1,6 +1,6 @@ import { redirect } from "next/navigation"; -const Page = async (props) => { +const Page = async (props: { params: Promise<{ environmentId: string }> }) => { const params = await props.params; return redirect(`/environments/${params.environmentId}/settings/profile`); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx index 0a07ac31fa..c01ddfcd97 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx @@ -27,7 +27,7 @@ export const generateMetadata = async (props: Props): Promise => { }; }; -const SurveyLayout = async ({ children }) => { +const SurveyLayout = async ({ children }: { children: React.ReactNode }) => { return {children}; }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx index eac94e8375..e774fcf912 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx @@ -205,11 +205,11 @@ export const ResponseTable = ({ }; // Handle downloading selected responses - const downloadSelectedRows = async (responseIds: string[], format: "csv" | "xlsx") => { + const downloadSelectedRows = async (responseIds: string[], format: "xlsx" | "csv") => { try { const downloadResponse = await getResponsesDownloadUrlAction({ surveyId: survey.id, - format: format, + format, filterCriteria: { responseIds }, }); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx index 9d22a6088b..b51b4ccb56 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTableColumns.tsx @@ -5,7 +5,7 @@ import { TFunction } from "i18next"; import { CircleHelpIcon, EyeOffIcon, MailIcon, TagIcon } from "lucide-react"; import Link from "next/link"; import { TResponseTableData } from "@formbricks/types/responses"; -import { TSurveyElement } from "@formbricks/types/surveys/elements"; +import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { getTextContent } from "@formbricks/types/surveys/validation"; import { getLocalizedValue } from "@/lib/i18n/utils"; @@ -41,7 +41,7 @@ const getElementColumnsData = ( const contactInfoFields = ["firstName", "lastName", "email", "phone", "company"]; // Helper function to create consistent column headers - const createElementHeader = (elementType: string, headline: string, suffix?: string) => { + const createElementHeader = (elementType: TSurveyElementTypeEnum, headline: string, suffix?: string) => { const title = suffix ? `${headline} - ${suffix}` : headline; const ElementHeader = () => (
@@ -232,7 +232,7 @@ const getMetadataColumnsData = (t: TFunction): ColumnDef[] = const metadataColumns: ColumnDef[] = []; METADATA_FIELDS.forEach((label) => { - const IconComponent = COLUMNS_ICON_MAP[label]; + const IconComponent = COLUMNS_ICON_MAP[label as keyof typeof COLUMNS_ICON_MAP]; metadataColumns.push({ accessorKey: "METADATA_" + label, diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.test.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.test.ts index 400631f65e..8151147a63 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.test.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.test.ts @@ -1,4 +1,5 @@ import "@testing-library/jest-dom/vitest"; +import { TFunction } from "i18next"; import { AirplayIcon, ArrowUpFromDotIcon, @@ -38,7 +39,7 @@ describe("utils", () => { "environments.surveys.responses.source": "Source", }; return translations[key] || key; - }); + }) as unknown as TFunction; describe("getAddressFieldLabel", () => { test("returns correct label for addressLine1", () => { diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.ts index a288418c3d..3a2040afca 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/lib/utils.ts @@ -80,9 +80,24 @@ export const COLUMNS_ICON_MAP = { const userAgentFields = ["browser", "os", "device"]; export const METADATA_FIELDS = ["action", "country", ...userAgentFields, "source", "url"]; -export const getMetadataValue = (meta: TResponseMeta, label: string) => { - if (userAgentFields.includes(label)) { - return meta.userAgent?.[label]; +export const getMetadataValue = ( + meta: TResponseMeta, + label: (typeof METADATA_FIELDS)[number] +): string | undefined => { + switch (label) { + case "browser": + return meta.userAgent?.browser; + case "os": + return meta.userAgent?.os; + case "device": + return meta.userAgent?.device; + case "action": + return meta.action; + case "country": + return meta.country; + case "source": + return meta.source; + case "url": + return meta.url; } - return meta[label]; }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx index 4fb55b833e..80fe12366c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx @@ -17,7 +17,7 @@ import { getOrganizationBilling } from "@/modules/survey/lib/survey"; import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper"; import { PageHeader } from "@/modules/ui/components/page-header"; -const Page = async (props) => { +const Page = async (props: { params: Promise<{ environmentId: string; surveyId: string }> }) => { const params = await props.params; const t = await getTranslate(); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts index 72045b69f4..fb255b3fc3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts @@ -7,7 +7,6 @@ import { getEmailTemplateHtml } from "@/app/(app)/environments/[environmentId]/s import { getSurvey, updateSurvey } from "@/lib/survey/service"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { convertToCsv } from "@/lib/utils/file-conversion"; import { getOrganizationIdFromSurveyId, getProjectIdFromSurveyId } from "@/lib/utils/helper"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; @@ -70,52 +69,42 @@ const ZResetSurveyAction = z.object({ }); export const resetSurveyAction = authenticatedActionClient.inputSchema(ZResetSurveyAction).action( - withAuditLogging( - "updated", - "survey", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: z.infer; - }) => { - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: parsedInput.organizationId, - access: [ - { - type: "organization", - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - minPermission: "readWrite", - projectId: parsedInput.projectId, - }, - ], - }); + withAuditLogging("updated", "survey", async ({ ctx, parsedInput }) => { + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: parsedInput.organizationId, + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + minPermission: "readWrite", + projectId: parsedInput.projectId, + }, + ], + }); - ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; - ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; - ctx.auditLoggingCtx.oldObject = null; + ctx.auditLoggingCtx.organizationId = parsedInput.organizationId; + ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; + ctx.auditLoggingCtx.oldObject = null; - const { deletedResponsesCount, deletedDisplaysCount } = await deleteResponsesAndDisplaysForSurvey( - parsedInput.surveyId - ); + const { deletedResponsesCount, deletedDisplaysCount } = await deleteResponsesAndDisplaysForSurvey( + parsedInput.surveyId + ); - ctx.auditLoggingCtx.newObject = { - deletedResponsesCount: deletedResponsesCount, - deletedDisplaysCount: deletedDisplaysCount, - }; + ctx.auditLoggingCtx.newObject = { + deletedResponsesCount: deletedResponsesCount, + deletedDisplaysCount: deletedDisplaysCount, + }; - return { - success: true, - deletedResponsesCount: deletedResponsesCount, - deletedDisplaysCount: deletedDisplaysCount, - }; - } - ) + return { + success: true, + deletedResponsesCount: deletedResponsesCount, + deletedDisplaysCount: deletedDisplaysCount, + }; + }) ); const ZGetEmailHtmlAction = z.object({ diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx index 029361c014..9df7723f07 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx @@ -60,7 +60,9 @@ export const NPSSummary = ({ elementSummary, survey, setFilter }: NPSSummaryProp }, }; - const filter = filters[group]; + const filter = (filters as Record)[ + group + ]; if (filter) { setFilter( @@ -104,7 +106,7 @@ export const NPSSummary = ({ elementSummary, survey, setFilter }: NPSSummaryProp
- {["promoters", "passives", "detractors", "dismissed"].map((group) => ( + {(["promoters", "passives", "detractors", "dismissed"] as const).map((group) => (
diff --git a/apps/web/modules/survey/editor/components/edit-ending-card.tsx b/apps/web/modules/survey/editor/components/edit-ending-card.tsx index 60e6be2b34..6c849704a6 100644 --- a/apps/web/modules/survey/editor/components/edit-ending-card.tsx +++ b/apps/web/modules/survey/editor/components/edit-ending-card.tsx @@ -87,7 +87,7 @@ export const EditEndingCard = ({ let open = activeElementId === endingCard.id; - const setOpen = (e) => { + const setOpen = (e: boolean) => { if (e) { setActiveElementId(endingCard.id); } else { diff --git a/apps/web/modules/survey/editor/components/edit-welcome-card.tsx b/apps/web/modules/survey/editor/components/edit-welcome-card.tsx index bdc4fcaadb..a89e5c1db0 100644 --- a/apps/web/modules/survey/editor/components/edit-welcome-card.tsx +++ b/apps/web/modules/survey/editor/components/edit-welcome-card.tsx @@ -4,7 +4,12 @@ import * as Collapsible from "@radix-ui/react-collapsible"; import { Hand } from "lucide-react"; import { usePathname } from "next/navigation"; import { useTranslation } from "react-i18next"; -import { TSurvey, TSurveyWelcomeCard } from "@formbricks/types/surveys/types"; +import { + TSurvey, + TSurveyEndScreenCard, + TSurveyRedirectUrlCard, + TSurveyWelcomeCard, +} from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { cn } from "@/lib/cn"; import { ElementFormInput } from "@/modules/survey/components/element-form-input"; @@ -44,7 +49,7 @@ export const EditWelcomeCard = ({ let open = activeElementId == "start"; - const setOpen = (e) => { + const setOpen = (e: boolean) => { if (e) { setActiveElementId("start"); } else { @@ -52,7 +57,9 @@ export const EditWelcomeCard = ({ } }; - const updateSurvey = (data: Partial) => { + const updateSurvey = ( + data: Partial | Partial | Partial + ) => { setLocalSurvey({ ...localSurvey, welcomeCard: { @@ -121,8 +128,10 @@ export const EditWelcomeCard = ({ id="welcome-card-image" allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]} environmentId={environmentId} - onFileUpload={(url: string[]) => { - updateSurvey({ fileUrl: url[0] }); + onFileUpload={(url: string[] | undefined, _fileType: "image" | "video") => { + if (url?.[0]) { + updateSurvey({ fileUrl: url[0] }); + } }} fileUrl={localSurvey?.welcomeCard?.fileUrl} isStorageConfigured={isStorageConfigured} diff --git a/apps/web/modules/survey/editor/components/element-option-choice.tsx b/apps/web/modules/survey/editor/components/element-option-choice.tsx index 37e11ac83d..fcc332e279 100644 --- a/apps/web/modules/survey/editor/components/element-option-choice.tsx +++ b/apps/web/modules/survey/editor/components/element-option-choice.tsx @@ -4,8 +4,8 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { GripVerticalIcon, PlusIcon, TrashIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; -import { TI18nString } from "@formbricks/types/i18n"; import { + TSurveyElement, TSurveyElementChoice, TSurveyMultipleChoiceElement, TSurveyRankingElement, @@ -23,7 +23,7 @@ interface ChoiceProps { choice: TSurveyElementChoice; choiceIdx: number; elementIdx: number; - updateChoice: (choiceIdx: number, updatedAttributes: { label: TI18nString }) => void; + updateChoice: (choiceIdx: number, updatedAttributes: Partial) => void; deleteChoice: (choiceIdx: number) => void; addChoice: (choiceIdx: number) => void; isInvalid: boolean; @@ -32,10 +32,7 @@ interface ChoiceProps { setSelectedLanguageCode: (language: string) => void; surveyLanguages: TSurveyLanguage[]; element: TSurveyMultipleChoiceElement | TSurveyRankingElement; - updateElement: ( - elementIdx: number, - updatedAttributes: Partial | Partial - ) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; surveyLanguageCodes: string[]; locale: TUserLocale; isStorageConfigured: boolean; diff --git a/apps/web/modules/survey/editor/components/elements-view.tsx b/apps/web/modules/survey/editor/components/elements-view.tsx index 58083cfe8e..c71b8352d3 100644 --- a/apps/web/modules/survey/editor/components/elements-view.tsx +++ b/apps/web/modules/survey/editor/components/elements-view.tsx @@ -112,7 +112,7 @@ export const ElementsView = ({ const elements = useMemo(() => getElementsFromBlocks(localSurvey.blocks), [localSurvey.blocks]); const internalElementIdMap = useMemo(() => { - return elements.reduce((acc, element) => { + return elements.reduce>((acc, element) => { acc[element.id] = createId(); return acc; }, {}); diff --git a/apps/web/modules/survey/editor/components/end-screen-form.tsx b/apps/web/modules/survey/editor/components/end-screen-form.tsx index d9cb2daf5c..4bde577532 100644 --- a/apps/web/modules/survey/editor/components/end-screen-form.tsx +++ b/apps/web/modules/survey/editor/components/end-screen-form.tsx @@ -3,7 +3,7 @@ import { PlusIcon } from "lucide-react"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { TSurvey, TSurveyEndScreenCard } from "@formbricks/types/surveys/types"; +import { TSurvey, TSurveyEndScreenCard, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes, getLocalizedValue } from "@/lib/i18n/utils"; import { headlineToRecall, recallToHeadline } from "@/lib/utils/recall"; @@ -21,7 +21,9 @@ interface EndScreenFormProps { isInvalid: boolean; selectedLanguageCode: string; setSelectedLanguageCode: (languageCode: string) => void; - updateSurvey: (input: Partial) => void; + updateSurvey: ( + input: Partial | Partial + ) => void; endingCard: TSurveyEndScreenCard; locale: TUserLocale; isStorageConfigured: boolean; diff --git a/apps/web/modules/survey/editor/components/file-upload-element-form.tsx b/apps/web/modules/survey/editor/components/file-upload-element-form.tsx index 3145261798..eca45382a5 100644 --- a/apps/web/modules/survey/editor/components/file-upload-element-form.tsx +++ b/apps/web/modules/survey/editor/components/file-upload-element-form.tsx @@ -7,7 +7,7 @@ import Link from "next/link"; import { type JSX, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; -import type { TSurveyFileUploadElement } from "@formbricks/types/surveys/elements"; +import type { TSurveyElement, TSurveyFileUploadElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils"; @@ -23,7 +23,7 @@ interface FileUploadFormProps { project: Project; element: TSurveyFileUploadElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (languageCode: string) => void; isInvalid: boolean; diff --git a/apps/web/modules/survey/editor/components/image-survey-bg.tsx b/apps/web/modules/survey/editor/components/image-survey-bg.tsx index 570d029c1e..787c6bbcc9 100644 --- a/apps/web/modules/survey/editor/components/image-survey-bg.tsx +++ b/apps/web/modules/survey/editor/components/image-survey-bg.tsx @@ -20,8 +20,8 @@ export const UploadImageSurveyBg = ({ id="survey-bg-file-input" allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]} environmentId={environmentId} - onFileUpload={(url: string[]) => { - if (url.length > 0) { + onFileUpload={(url: string[] | undefined, _fileType: "image" | "video") => { + if (url && url.length > 0) { handleBgChange(url[0], "upload"); } else { handleBgChange("", "upload"); diff --git a/apps/web/modules/survey/editor/components/logic-editor-actions.tsx b/apps/web/modules/survey/editor/components/logic-editor-actions.tsx index 997d5f11e9..b0a9a7258c 100644 --- a/apps/web/modules/survey/editor/components/logic-editor-actions.tsx +++ b/apps/web/modules/survey/editor/components/logic-editor-actions.tsx @@ -136,8 +136,8 @@ export function LogicEditorActions({ : filteredObjectiveOptions } value={action.objective} - onChangeValue={(val: TSurveyBlockLogicActionObjective) => { - handleObjectiveChange(idx, val); + onChangeValue={(val: string | number | string[]) => { + handleObjectiveChange(idx, val as TSurveyBlockLogicActionObjective); }} comboboxClasses="grow" /> @@ -151,9 +151,9 @@ export function LogicEditorActions({ showSearch={false} options={getActionTargetOptions(action, localSurvey, blockIdx, t)} value={action.target} - onChangeValue={(val: string) => { + onChangeValue={(val: string | number | string[]) => { handleValuesChange(idx, { - target: val, + target: String(val), }); }} comboboxClasses="grow" @@ -170,9 +170,9 @@ export function LogicEditorActions({ showSearch={false} options={getActionVariableOptions(localSurvey)} value={action.variableId} - onChangeValue={(val: string) => { + onChangeValue={(val: string | number | string[]) => { handleValuesChange(idx, { - variableId: val, + variableId: String(val), value: { type: "static", value: "", @@ -194,11 +194,11 @@ export function LogicEditorActions({ localSurvey.variables.find((v) => v.id === action.variableId)?.type )} value={action.operator} - onChangeValue={( - val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator - ) => { + onChangeValue={(val: string | number | string[]) => { handleValuesChange(idx, { - operator: val, + operator: val as + | TActionTextVariableCalculateOperator + | TActionNumberVariableCalculateOperator, }); }} comboboxClasses="grow" diff --git a/apps/web/modules/survey/editor/components/logic-editor-conditions.tsx b/apps/web/modules/survey/editor/components/logic-editor-conditions.tsx index 514727ac1a..c1bbae487a 100644 --- a/apps/web/modules/survey/editor/components/logic-editor-conditions.tsx +++ b/apps/web/modules/survey/editor/components/logic-editor-conditions.tsx @@ -7,6 +7,10 @@ import { TSurvey } from "@formbricks/types/surveys/types"; import { createSharedConditionsFactory } from "@/modules/survey/editor/lib/shared-conditions-factory"; import { getDefaultOperatorForElement } from "@/modules/survey/editor/lib/utils"; import { ConditionsEditor } from "@/modules/ui/components/conditions-editor"; +import type { + TConditionsEditorCallbacks, + TConditionsEditorConfig, +} from "@/modules/ui/components/conditions-editor/types"; interface LogicEditorConditionsProps { conditions: TConditionGroup; @@ -56,5 +60,12 @@ export function LogicEditorConditions({ } ); - return ; + return ( + + ); } diff --git a/apps/web/modules/survey/editor/components/logo-settings-card.tsx b/apps/web/modules/survey/editor/components/logo-settings-card.tsx index 19fdc31f6e..1e113a0c6c 100644 --- a/apps/web/modules/survey/editor/components/logo-settings-card.tsx +++ b/apps/web/modules/survey/editor/components/logo-settings-card.tsx @@ -62,8 +62,8 @@ export const LogoSettingsCard = ({ }); }; - const handleFileInputChange = async (files: string[]) => { - if (files.length > 0) { + const handleFileInputChange = async (files: string[] | undefined, _fileType: "image" | "video") => { + if (files && files.length > 0) { setLogoUrl(files[0]); } }; diff --git a/apps/web/modules/survey/editor/components/matrix-element-form.tsx b/apps/web/modules/survey/editor/components/matrix-element-form.tsx index 4a8781f40c..cbbc0f10b6 100644 --- a/apps/web/modules/survey/editor/components/matrix-element-form.tsx +++ b/apps/web/modules/survey/editor/components/matrix-element-form.tsx @@ -9,7 +9,7 @@ import { type JSX, useCallback } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { TI18nString } from "@formbricks/types/i18n"; -import type { TSurveyMatrixElement } from "@formbricks/types/surveys/elements"; +import type { TSurveyElement, TSurveyMatrixElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils"; @@ -26,7 +26,7 @@ interface MatrixElementFormProps { localSurvey: TSurvey; element: TSurveyMatrixElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; isInvalid: boolean; diff --git a/apps/web/modules/survey/editor/components/multiple-choice-element-form.tsx b/apps/web/modules/survey/editor/components/multiple-choice-element-form.tsx index ad6b2ddbec..22745b47fd 100644 --- a/apps/web/modules/survey/editor/components/multiple-choice-element-form.tsx +++ b/apps/web/modules/survey/editor/components/multiple-choice-element-form.tsx @@ -9,9 +9,10 @@ import { type JSX, useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils"; -import { TI18nString } from "@formbricks/types/i18n"; import { TMultipleChoiceOptionDisplayType, + TSurveyElement, + TSurveyElementChoice, TSurveyElementTypeEnum, TSurveyMultipleChoiceElement, } from "@formbricks/types/surveys/elements"; @@ -32,7 +33,7 @@ interface MultipleChoiceElementFormProps { localSurvey: TSurvey; element: TSurveyMultipleChoiceElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; isInvalid: boolean; @@ -85,7 +86,7 @@ export const MultipleChoiceElementForm = ({ { value: "dropdown", label: t("environments.surveys.edit.dropdown") }, ]; - const updateChoice = (choiceIdx: number, updatedAttributes: { label: TI18nString }) => { + const updateChoice = (choiceIdx: number, updatedAttributes: Partial) => { let newChoices: any[] = []; if (element.choices) { newChoices = element.choices.map((choice, idx) => { @@ -399,8 +400,8 @@ export const MultipleChoiceElementForm = ({ - updateElement(elementIdx, { displayType: value }) + handleOptionChange={(value: string) => + updateElement(elementIdx, { displayType: value as TMultipleChoiceOptionDisplayType }) } />
diff --git a/apps/web/modules/survey/editor/components/nps-element-form.tsx b/apps/web/modules/survey/editor/components/nps-element-form.tsx index 1a9c2ed641..3a72e32f0e 100644 --- a/apps/web/modules/survey/editor/components/nps-element-form.tsx +++ b/apps/web/modules/survey/editor/components/nps-element-form.tsx @@ -4,7 +4,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { PlusIcon } from "lucide-react"; import { type JSX } from "react"; import { useTranslation } from "react-i18next"; -import { TSurveyNPSElement } from "@formbricks/types/surveys/elements"; +import { type TSurveyElement, TSurveyNPSElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils"; @@ -16,7 +16,7 @@ interface NPSElementFormProps { localSurvey: TSurvey; element: TSurveyNPSElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (languageCode: string) => void; isInvalid: boolean; diff --git a/apps/web/modules/survey/editor/components/open-element-form.tsx b/apps/web/modules/survey/editor/components/open-element-form.tsx index 292733bd60..de43dac859 100644 --- a/apps/web/modules/survey/editor/components/open-element-form.tsx +++ b/apps/web/modules/survey/editor/components/open-element-form.tsx @@ -5,6 +5,7 @@ import { PlusIcon } from "lucide-react"; import { JSX } from "react"; import { useTranslation } from "react-i18next"; import type { + TSurveyElement, TSurveyOpenTextElement, TSurveyOpenTextElementInputType, } from "@formbricks/types/surveys/elements"; @@ -20,7 +21,7 @@ interface OpenElementFormProps { localSurvey: TSurvey; element: TSurveyOpenTextElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; lastElement: boolean; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; diff --git a/apps/web/modules/survey/editor/components/picture-selection-form.tsx b/apps/web/modules/survey/editor/components/picture-selection-form.tsx index 47dbe5160f..3b675c0b80 100644 --- a/apps/web/modules/survey/editor/components/picture-selection-form.tsx +++ b/apps/web/modules/survey/editor/components/picture-selection-form.tsx @@ -5,7 +5,7 @@ import { createId } from "@paralleldrive/cuid2"; import { PlusIcon } from "lucide-react"; import { type JSX } from "react"; import { useTranslation } from "react-i18next"; -import type { TSurveyPictureSelectionElement } from "@formbricks/types/surveys/elements"; +import type { TSurveyElement, TSurveyPictureSelectionElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { cn } from "@/lib/cn"; @@ -21,7 +21,7 @@ interface PictureSelectionFormProps { localSurvey: TSurvey; element: TSurveyPictureSelectionElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; isInvalid: boolean; @@ -55,7 +55,8 @@ export const PictureSelectionForm = ({ }); }; - const handleFileInputChanges = (urls: string[]) => { + const handleFileInputChanges = (urls: string[] | undefined, _fileType: "image" | "video") => { + if (!urls) return; // Handle choice deletion if (urls.length < element.choices.length) { const deletedChoice = element.choices.find((choice) => !urls.includes(choice.imageUrl)); diff --git a/apps/web/modules/survey/editor/components/ranking-element-form.tsx b/apps/web/modules/survey/editor/components/ranking-element-form.tsx index 864e374b02..40273c488b 100644 --- a/apps/web/modules/survey/editor/components/ranking-element-form.tsx +++ b/apps/web/modules/survey/editor/components/ranking-element-form.tsx @@ -7,8 +7,11 @@ import { createId } from "@paralleldrive/cuid2"; import { PlusIcon } from "lucide-react"; import { type JSX, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { TI18nString } from "@formbricks/types/i18n"; -import type { TSurveyRankingElement } from "@formbricks/types/surveys/elements"; +import type { + TSurveyElement, + TSurveyElementChoice, + TSurveyRankingElement, +} from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils"; @@ -23,7 +26,7 @@ interface RankingElementFormProps { localSurvey: TSurvey; element: TSurveyRankingElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; isInvalid: boolean; @@ -51,7 +54,7 @@ export const RankingElementForm = ({ const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages); const surveyLanguages = localSurvey.languages ?? []; - const updateChoice = (choiceIdx: number, updatedAttributes: { label: TI18nString }) => { + const updateChoice = (choiceIdx: number, updatedAttributes: Partial) => { if (element.choices) { const newChoices = element.choices.map((choice, idx) => { if (idx !== choiceIdx) return choice; diff --git a/apps/web/modules/survey/editor/components/rating-element-form.tsx b/apps/web/modules/survey/editor/components/rating-element-form.tsx index acbc6821ab..87d85bf766 100644 --- a/apps/web/modules/survey/editor/components/rating-element-form.tsx +++ b/apps/web/modules/survey/editor/components/rating-element-form.tsx @@ -3,7 +3,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; -import { TSurveyRatingElement } from "@formbricks/types/surveys/elements"; +import { type TSurveyElement, TSurveyRatingElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils"; @@ -17,7 +17,7 @@ interface RatingElementFormProps { localSurvey: TSurvey; element: TSurveyRatingElement; elementIdx: number; - updateElement: (elementIdx: number, updatedAttributes: Partial) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; lastElement: boolean; selectedLanguageCode: string; setSelectedLanguageCode: (language: string) => void; diff --git a/apps/web/modules/survey/editor/components/rating-type-dropdown.tsx b/apps/web/modules/survey/editor/components/rating-type-dropdown.tsx index 4562335b42..679af44a40 100644 --- a/apps/web/modules/survey/editor/components/rating-type-dropdown.tsx +++ b/apps/web/modules/survey/editor/components/rating-type-dropdown.tsx @@ -25,7 +25,7 @@ export const Dropdown = ({ options, defaultValue, onSelect, disabled = false }: setSelectedOption(options.filter((option) => option.value === defaultValue)[0] || options[0]); }, [defaultValue, options]); - const handleSelect = (option) => { + const handleSelect = (option: Option) => { setSelectedOption(option); onSelect(option); }; diff --git a/apps/web/modules/survey/editor/components/recontact-options-card.tsx b/apps/web/modules/survey/editor/components/recontact-options-card.tsx index daef09c4d1..9050205b06 100644 --- a/apps/web/modules/survey/editor/components/recontact-options-card.tsx +++ b/apps/web/modules/survey/editor/components/recontact-options-card.tsx @@ -113,7 +113,7 @@ export const RecontactOptionsCard = ({ localSurvey, setLocalSurvey }: RecontactO } }; - const handleOverwriteDaysChange = (event) => { + const handleOverwriteDaysChange = (event: React.ChangeEvent) => { let value = Number(event.target.value); if (Number.isNaN(value) || value < 1) { value = 1; @@ -126,7 +126,7 @@ export const RecontactOptionsCard = ({ localSurvey, setLocalSurvey }: RecontactO setLocalSurvey(updatedSurvey); }; - const handleDisplayLimitChange = (event) => { + const handleDisplayLimitChange = (event: React.ChangeEvent) => { let value = Number(event.target.value); if (Number.isNaN(value) || value < 1) { value = 1; diff --git a/apps/web/modules/survey/editor/components/response-options-card.tsx b/apps/web/modules/survey/editor/components/response-options-card.tsx index 63a4504a3b..8f3794663b 100644 --- a/apps/web/modules/survey/editor/components/response-options-card.tsx +++ b/apps/web/modules/survey/editor/components/response-options-card.tsx @@ -16,7 +16,7 @@ import { Slider } from "@/modules/ui/components/slider"; interface ResponseOptionsCardProps { localSurvey: TSurvey; - setLocalSurvey: (survey: TSurvey | ((TSurvey) => TSurvey)) => void; + setLocalSurvey: (survey: TSurvey | ((prev: TSurvey) => TSurvey)) => void; responseCount: number; isSpamProtectionAllowed: boolean; } @@ -143,7 +143,7 @@ export const ResponseOptionsCard = ({ } }; - const handleInputResponse = (e) => { + const handleInputResponse = (e: React.ChangeEvent) => { let value = parseInt(e.target.value); if (Number.isNaN(value) || value < 1) { value = 1; @@ -153,7 +153,7 @@ export const ResponseOptionsCard = ({ setLocalSurvey(updatedSurvey); }; - const handleInputResponseBlur = (e) => { + const handleInputResponseBlur = (e: React.FocusEvent) => { if (parseInt(e.target.value) === 0) { toast.error(t("environments.surveys.edit.response_limit_can_t_be_set_to_0")); return; @@ -189,10 +189,15 @@ export const ResponseOptionsCard = ({ const handleThresholdChange = (value: number) => { setRecaptchaThreshold(value); - setLocalSurvey((prevSurvey) => ({ - ...prevSurvey, - recaptcha: { ...prevSurvey.recaptcha, threshold: value }, - })); + setLocalSurvey( + (prevSurvey: TSurvey): TSurvey => ({ + ...prevSurvey, + recaptcha: { + enabled: prevSurvey.recaptcha?.enabled ?? false, + threshold: value, + }, + }) + ); }; return ( diff --git a/apps/web/modules/survey/editor/components/settings-view.tsx b/apps/web/modules/survey/editor/components/settings-view.tsx index ce501699fa..b32d4180da 100644 --- a/apps/web/modules/survey/editor/components/settings-view.tsx +++ b/apps/web/modules/survey/editor/components/settings-view.tsx @@ -1,4 +1,5 @@ import { ActionClass, Environment, OrganizationRole } from "@prisma/client"; +import { type Dispatch, type SetStateAction } from "react"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { TSurveyQuota } from "@formbricks/types/quota"; import { TSegment } from "@formbricks/types/segment"; @@ -16,7 +17,7 @@ import { WhenToSendCard } from "@/modules/survey/editor/components/when-to-send- interface SettingsViewProps { environment: Pick; localSurvey: TSurvey; - setLocalSurvey: (survey: TSurvey) => void; + setLocalSurvey: Dispatch>; actionClasses: ActionClass[]; contactAttributeKeys: TContactAttributeKey[]; segments: TSegment[]; diff --git a/apps/web/modules/survey/editor/components/styling-view.tsx b/apps/web/modules/survey/editor/components/styling-view.tsx index 8e83b79294..8385c2a036 100644 --- a/apps/web/modules/survey/editor/components/styling-view.tsx +++ b/apps/web/modules/survey/editor/components/styling-view.tsx @@ -136,12 +136,12 @@ export const StylingView = ({ }, [overwriteThemeStyling]); useEffect(() => { - const subscription = form.watch((data: TSurveyStyling) => { + const subscription = form.watch((data) => { setLocalSurvey((prev) => ({ ...prev, styling: { ...prev.styling, - ...data, + ...(data as TSurveyStyling), }, })); }); diff --git a/apps/web/modules/survey/editor/components/survey-editor.tsx b/apps/web/modules/survey/editor/components/survey-editor.tsx index ef032bbaed..8b41e55595 100644 --- a/apps/web/modules/survey/editor/components/survey-editor.tsx +++ b/apps/web/modules/survey/editor/components/survey-editor.tsx @@ -1,7 +1,7 @@ "use client"; import { ActionClass, Environment, Language, OrganizationRole, Project } from "@prisma/client"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from "react"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { TSurveyQuota } from "@formbricks/types/quota"; import { TSegment } from "@formbricks/types/segment"; @@ -83,13 +83,13 @@ export const SurveyEditor = ({ const [activeView, setActiveView] = useState("elements"); const [activeElementId, setActiveElementId] = useState(null); const [localSurvey, setLocalSurvey] = useState(() => structuredClone(survey)); - const [invalidElements, setInvalidElements] = useState(null); + const [invalidElements, setInvalidElements] = useState([]); const [selectedLanguageCode, setSelectedLanguageCode] = useState("default"); const surveyEditorRef = useRef(null); const [localProject, setLocalProject] = useState(project); - const [styling, setStyling] = useState(localSurvey?.styling); + const [styling, setStyling] = useState(localSurvey?.styling ?? null); const [localStylingChanges, setLocalStylingChanges] = useState(null); const fetchLatestProject = useCallback(async () => { @@ -159,10 +159,13 @@ export const SurveyEditor = ({ return ; } + // After the null guard, we can safely narrow the setter type for child components + const setLocalSurveyNonNull = setLocalSurvey as Dispatch>; + return (
>; - setInvalidElements: React.Dispatch>; + setInvalidElements: React.Dispatch>; project: Project; responseCount: number; selectedLanguageCode: string; @@ -196,7 +196,7 @@ export const SurveyMenuBar = ({ for (const issue of issues) { if (issue.path[0] === "blocks") { - const blockIdx = issue.path[1]; + const blockIdx = issue.path[1] as number; if (issue.path[2] === "elements" && typeof issue.path[3] === "number") { const elementIdx = issue.path[3]; diff --git a/apps/web/modules/survey/editor/components/survey-variables-card.tsx b/apps/web/modules/survey/editor/components/survey-variables-card.tsx index 697867e5b5..2f9025a489 100644 --- a/apps/web/modules/survey/editor/components/survey-variables-card.tsx +++ b/apps/web/modules/survey/editor/components/survey-variables-card.tsx @@ -3,6 +3,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import * as Collapsible from "@radix-ui/react-collapsible"; import { FileDigitIcon } from "lucide-react"; +import { type Dispatch, type SetStateAction } from "react"; import { useTranslation } from "react-i18next"; import { TSurveyQuota } from "@formbricks/types/quota"; import { TSurvey } from "@formbricks/types/surveys/types"; @@ -12,7 +13,7 @@ import { SurveyVariablesCardItem } from "@/modules/survey/editor/components/surv interface SurveyVariablesCardProps { localSurvey: TSurvey; - setLocalSurvey: (survey: TSurvey) => void; + setLocalSurvey: Dispatch>; activeElementId: string | null; setActiveElementId: (id: string | null) => void; quotas: TSurveyQuota[]; diff --git a/apps/web/modules/survey/editor/components/unsplash-images.tsx b/apps/web/modules/survey/editor/components/unsplash-images.tsx index ac379fbf41..b56c2ef6d2 100644 --- a/apps/web/modules/survey/editor/components/unsplash-images.tsx +++ b/apps/web/modules/survey/editor/components/unsplash-images.tsx @@ -147,7 +147,7 @@ export const ImageFromUnsplashSurveyBg = ({ handleBgChange }: ImageFromUnsplashS } setImages((prevImages) => [...prevImages, ...imagesFromUnsplash]); } catch (error) { - toast.error(error.message); + toast.error(error instanceof Error ? error.message : "Unknown error occurred"); } finally { setIsLoading(false); } @@ -181,7 +181,7 @@ export const ImageFromUnsplashSurveyBg = ({ handleBgChange }: ImageFromUnsplashS await triggerDownloadUnsplashImageAction({ downloadUrl: downloadImageUrl }); } } catch (error) { - toast.error(error.message); + toast.error(error instanceof Error ? error.message : "Unknown error occurred"); } }; diff --git a/apps/web/modules/survey/editor/components/when-to-send-card.tsx b/apps/web/modules/survey/editor/components/when-to-send-card.tsx index e38a77865b..78703ed7f7 100644 --- a/apps/web/modules/survey/editor/components/when-to-send-card.tsx +++ b/apps/web/modules/survey/editor/components/when-to-send-card.tsx @@ -107,7 +107,7 @@ export const WhenToSendCard = ({ setLocalSurvey(updatedSurvey); }; - const handleRandomizerInput = (e) => { + const handleRandomizerInput = (e: React.ChangeEvent) => { let value = parseFloat(e.target.value); if (Number.isNaN(value)) { diff --git a/apps/web/modules/survey/editor/lib/action-builder.test.ts b/apps/web/modules/survey/editor/lib/action-builder.test.ts index 8bf24d3517..6480f04f39 100644 --- a/apps/web/modules/survey/editor/lib/action-builder.test.ts +++ b/apps/web/modules/survey/editor/lib/action-builder.test.ts @@ -1,4 +1,4 @@ -import { TFunction } from "react-i18next"; +import { TFunction } from "i18next"; import { describe, expect, test, vi } from "vitest"; import { TActionClassInput } from "@formbricks/types/action-classes"; import { buildActionObject, buildCodeAction, buildNoCodeAction } from "./action-builder"; diff --git a/apps/web/modules/survey/editor/lib/action-class.ts b/apps/web/modules/survey/editor/lib/action-class.ts index 0041d38b91..fadccd674a 100644 --- a/apps/web/modules/survey/editor/lib/action-class.ts +++ b/apps/web/modules/survey/editor/lib/action-class.ts @@ -31,8 +31,9 @@ export const createActionClass = async ( error instanceof Prisma.PrismaClientKnownRequestError && error.code === PrismaErrorType.UniqueConstraintViolation ) { + const targetField = (error.meta?.target as string[] | undefined)?.[0]; throw new DatabaseError( - `Action with ${error.meta?.target?.[0]} ${actionClass[error.meta?.target?.[0]]} already exists` + `Action with ${targetField} ${targetField ? (actionClass as Record)[targetField] : ""} already exists` ); } diff --git a/apps/web/modules/survey/editor/lib/action-utils.test.ts b/apps/web/modules/survey/editor/lib/action-utils.test.ts index 32286ab554..1faf964657 100644 --- a/apps/web/modules/survey/editor/lib/action-utils.test.ts +++ b/apps/web/modules/survey/editor/lib/action-utils.test.ts @@ -5,7 +5,7 @@ import "@testing-library/jest-dom/vitest"; import { renderHook } from "@testing-library/react"; import { beforeEach, describe, expect, test, vi } from "vitest"; import { z } from "zod"; -import { TActionClass } from "@formbricks/types/action-classes"; +import { TActionClass, TActionClassInput } from "@formbricks/types/action-classes"; import { createActionClassZodResolver, useActionClassKeys, @@ -165,7 +165,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { name: "existingAction" }; - validateActionNameUniqueness(data, ["existingAction"], ctx, mockT); + validateActionNameUniqueness(data as TActionClassInput, ["existingAction"], ctx, mockT); expect(ctx.addIssue).toHaveBeenCalledWith({ code: "custom", @@ -178,7 +178,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { name: "uniqueAction" }; - validateActionNameUniqueness(data, ["existingAction"], ctx, mockT); + validateActionNameUniqueness(data as TActionClassInput, ["existingAction"], ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -187,7 +187,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { name: undefined }; - validateActionNameUniqueness(data, ["existingAction"], ctx, mockT); + validateActionNameUniqueness(data as unknown as TActionClassInput, ["existingAction"], ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -198,7 +198,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "code", key: "existingKey" }; - validateActionKeyUniqueness(data, ["existingKey"], ctx, mockT); + validateActionKeyUniqueness(data as TActionClassInput, ["existingKey"], ctx, mockT); expect(ctx.addIssue).toHaveBeenCalledWith({ code: "custom", @@ -211,7 +211,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "code", key: "uniqueKey" }; - validateActionKeyUniqueness(data, ["existingKey"], ctx, mockT); + validateActionKeyUniqueness(data as TActionClassInput, ["existingKey"], ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -220,7 +220,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "noCode", key: "existingKey" }; - validateActionKeyUniqueness(data, ["existingKey"], ctx, mockT); + validateActionKeyUniqueness(data as TActionClassInput, ["existingKey"], ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -229,7 +229,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "code", key: undefined }; - validateActionKeyUniqueness(data, ["existingKey"], ctx, mockT); + validateActionKeyUniqueness(data as unknown as TActionClassInput, ["existingKey"], ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -248,7 +248,7 @@ describe("action-utils", () => { vi.mocked(isValidCssSelector).mockReturnValue(false); - validateCssSelector(data, ctx, mockT); + validateCssSelector(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).toHaveBeenCalledWith({ code: "custom", @@ -269,7 +269,7 @@ describe("action-utils", () => { vi.mocked(isValidCssSelector).mockReturnValue(true); - validateCssSelector(data, ctx, mockT); + validateCssSelector(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -281,7 +281,7 @@ describe("action-utils", () => { noCodeConfig: { type: "pageView" }, }; - validateCssSelector(data, ctx, mockT); + validateCssSelector(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -290,7 +290,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "code" }; - validateCssSelector(data, ctx, mockT); + validateCssSelector(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -306,7 +306,7 @@ describe("action-utils", () => { }, }; - validateUrlFilterRegex(data, ctx, mockT); + validateUrlFilterRegex(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).toHaveBeenCalledWith({ code: "custom", @@ -324,7 +324,7 @@ describe("action-utils", () => { }, }; - validateUrlFilterRegex(data, ctx, mockT); + validateUrlFilterRegex(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -338,7 +338,7 @@ describe("action-utils", () => { }, }; - validateUrlFilterRegex(data, ctx, mockT); + validateUrlFilterRegex(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -347,7 +347,7 @@ describe("action-utils", () => { const ctx = createMockContext(); const data = { type: "code" }; - validateUrlFilterRegex(data, ctx, mockT); + validateUrlFilterRegex(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).not.toHaveBeenCalled(); }); @@ -364,7 +364,7 @@ describe("action-utils", () => { }, }; - validateUrlFilterRegex(data, ctx, mockT); + validateUrlFilterRegex(data as TActionClassInput, ctx, mockT); expect(ctx.addIssue).toHaveBeenCalledTimes(1); expect(ctx.addIssue).toHaveBeenCalledWith({ diff --git a/apps/web/modules/survey/editor/lib/blocks.test.ts b/apps/web/modules/survey/editor/lib/blocks.test.ts index 3185b51dd7..e4ab8fe7b9 100644 --- a/apps/web/modules/survey/editor/lib/blocks.test.ts +++ b/apps/web/modules/survey/editor/lib/blocks.test.ts @@ -82,6 +82,8 @@ const createMockSurvey = (blocks: TSurveyBlock[] = []): TSurvey => ({ recaptcha: null, isBackButtonHidden: false, metadata: {}, + isCaptureIpEnabled: false, + slug: null, }); describe("renumberBlocks", () => { diff --git a/apps/web/modules/survey/editor/lib/check-external-urls-permission.test.ts b/apps/web/modules/survey/editor/lib/check-external-urls-permission.test.ts index 6026c0b1dc..e09d3e48a7 100644 --- a/apps/web/modules/survey/editor/lib/check-external-urls-permission.test.ts +++ b/apps/web/modules/survey/editor/lib/check-external-urls-permission.test.ts @@ -214,12 +214,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://example.com", required: false, @@ -246,12 +247,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://example.com", required: false, @@ -266,12 +268,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here now", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://example.com", required: false, @@ -295,12 +298,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: false, buttonUrl: "", required: false, @@ -315,12 +319,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://example.com", required: false, @@ -344,12 +349,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://example.com", required: false, @@ -364,12 +370,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: true, buttonUrl: "https://different-url.com", required: false, @@ -393,12 +400,13 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: false, buttonUrl: "", required: false, @@ -422,6 +430,7 @@ describe("checkExternalUrlsPermission", () => { blocks: [ { id: "block1", + name: "Block 1", elements: [ { id: "q1", @@ -435,7 +444,7 @@ describe("checkExternalUrlsPermission", () => { id: "q2", type: TSurveyElementTypeEnum.CTA, headline: createI18nString("Click here", ["en"]), - buttonLabel: createI18nString("Visit", ["en"]), + ctaButtonLabel: createI18nString("Visit", ["en"]), buttonExternal: false, buttonUrl: "", required: false, diff --git a/apps/web/modules/survey/editor/lib/logic-rule-engine.test.ts b/apps/web/modules/survey/editor/lib/logic-rule-engine.test.ts index c6c7dec228..18ed164438 100644 --- a/apps/web/modules/survey/editor/lib/logic-rule-engine.test.ts +++ b/apps/web/modules/survey/editor/lib/logic-rule-engine.test.ts @@ -7,6 +7,7 @@ import { TLogicRuleOption, getLogicRules } from "./logic-rule-engine"; // Mock the translation function const mockT = vi.fn((key: string) => `mockTranslate(${key})`); const logicRules = getLogicRules(mockT as unknown as TFunction); +const elementRules = logicRules.element as Record; describe("getLogicRules", () => { test("should return correct structure for question rules", () => { @@ -35,7 +36,7 @@ describe("getLogicRules", () => { describe("Question Specific Rules", () => { test("OpenText.text", () => { - const openTextTextRules = logicRules.element[TSurveyQuestionTypeEnum.OpenText + ".text"]; + const openTextTextRules = elementRules[TSurveyQuestionTypeEnum.OpenText + ".text"]; expect(openTextTextRules).toBeDefined(); expect(openTextTextRules.options).toEqual([ { @@ -82,7 +83,7 @@ describe("getLogicRules", () => { }); test("OpenText.number", () => { - const openTextNumberRules = logicRules.element[TSurveyQuestionTypeEnum.OpenText + ".number"]; + const openTextNumberRules = elementRules[TSurveyQuestionTypeEnum.OpenText + ".number"]; expect(openTextNumberRules).toBeDefined(); expect(openTextNumberRules.options).toEqual([ { label: "=", value: ZSurveyLogicConditionsOperator.enum.equals }, @@ -103,7 +104,7 @@ describe("getLogicRules", () => { }); test("MultipleChoiceSingle", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.MultipleChoiceSingle]; + const rules = elementRules[TSurveyQuestionTypeEnum.MultipleChoiceSingle]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -130,7 +131,7 @@ describe("getLogicRules", () => { }); test("MultipleChoiceMulti", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.MultipleChoiceMulti]; + const rules = elementRules[TSurveyQuestionTypeEnum.MultipleChoiceMulti]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -169,7 +170,7 @@ describe("getLogicRules", () => { }); test("PictureSelection", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.PictureSelection]; + const rules = elementRules[TSurveyQuestionTypeEnum.PictureSelection]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -208,7 +209,7 @@ describe("getLogicRules", () => { }); test("Rating", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Rating]; + const rules = elementRules[TSurveyQuestionTypeEnum.Rating]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { label: "=", value: ZSurveyLogicConditionsOperator.enum.equals }, @@ -229,7 +230,7 @@ describe("getLogicRules", () => { }); test("NPS", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.NPS]; + const rules = elementRules[TSurveyQuestionTypeEnum.NPS]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { label: "=", value: ZSurveyLogicConditionsOperator.enum.equals }, @@ -250,7 +251,7 @@ describe("getLogicRules", () => { }); test("CTA", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.CTA]; + const rules = elementRules[TSurveyQuestionTypeEnum.CTA]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -265,7 +266,7 @@ describe("getLogicRules", () => { }); test("Consent", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Consent]; + const rules = elementRules[TSurveyQuestionTypeEnum.Consent]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -280,7 +281,7 @@ describe("getLogicRules", () => { }); test("Date", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Date]; + const rules = elementRules[TSurveyQuestionTypeEnum.Date]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -311,7 +312,7 @@ describe("getLogicRules", () => { }); test("FileUpload", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.FileUpload]; + const rules = elementRules[TSurveyQuestionTypeEnum.FileUpload]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -326,7 +327,7 @@ describe("getLogicRules", () => { }); test("Ranking", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Ranking]; + const rules = elementRules[TSurveyQuestionTypeEnum.Ranking]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -341,7 +342,7 @@ describe("getLogicRules", () => { }); test("Cal", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Cal]; + const rules = elementRules[TSurveyQuestionTypeEnum.Cal]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -356,7 +357,7 @@ describe("getLogicRules", () => { }); test("Matrix", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Matrix]; + const rules = elementRules[TSurveyQuestionTypeEnum.Matrix]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -375,7 +376,7 @@ describe("getLogicRules", () => { }); test("Matrix.row", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Matrix + ".row"]; + const rules = elementRules[TSurveyQuestionTypeEnum.Matrix + ".row"]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -402,7 +403,7 @@ describe("getLogicRules", () => { }); test("Address", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.Address]; + const rules = elementRules[TSurveyQuestionTypeEnum.Address]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { @@ -417,7 +418,7 @@ describe("getLogicRules", () => { }); test("ContactInfo", () => { - const rules = logicRules.element[TSurveyQuestionTypeEnum.ContactInfo]; + const rules = elementRules[TSurveyQuestionTypeEnum.ContactInfo]; expect(rules).toBeDefined(); expect(rules.options).toEqual([ { diff --git a/apps/web/modules/survey/editor/lib/project.test.ts b/apps/web/modules/survey/editor/lib/project.test.ts index cbd686b329..97380597fc 100644 --- a/apps/web/modules/survey/editor/lib/project.test.ts +++ b/apps/web/modules/survey/editor/lib/project.test.ts @@ -52,7 +52,9 @@ const mockProject = { describe("getProject", () => { test("should return project when found", async () => { - vi.mocked(prisma.project.findUnique).mockResolvedValue(mockProject); + vi.mocked(prisma.project.findUnique).mockResolvedValue( + mockProject as unknown as Awaited> + ); const project = await getProject("testProjectId"); expect(project).toEqual(mockProject); expect(prisma.project.findUnique).toHaveBeenCalledWith({ @@ -86,7 +88,7 @@ describe("getProjectLanguages", () => { test("should return project languages when project found", async () => { vi.mocked(prisma.project.findUnique).mockResolvedValue({ languages: mockProject.languages, - }); + } as unknown as Awaited>); const languages = await getProjectLanguages("testProjectId"); expect(languages).toEqual(mockProject.languages); expect(prisma.project.findUnique).toHaveBeenCalledWith({ diff --git a/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts b/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts index e86cc090c1..eb60bdc394 100644 --- a/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts +++ b/apps/web/modules/survey/editor/lib/shared-conditions-factory.test.ts @@ -567,7 +567,7 @@ describe("shared-conditions-factory", () => { conditions: [ { id: "condition1", - leftOperand: { value: "question1", type: "question" }, + leftOperand: { value: "question1", type: "element" }, operator: "equals", }, ], diff --git a/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts b/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts index 254597d288..655b1f195c 100644 --- a/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts +++ b/apps/web/modules/survey/editor/lib/shared-conditions-factory.ts @@ -222,6 +222,6 @@ export const genericConditionsToQuota = (genericConditions: TQuotaConditionGroup return { connector: genericConditions.connector, - conditions: genericConditions.conditions.map(convertCondition), - }; + conditions: genericConditions.conditions.map((c) => ("conditions" in c ? c : convertCondition(c))), + } as TSurveyQuotaLogic; }; diff --git a/apps/web/modules/survey/editor/lib/user.test.ts b/apps/web/modules/survey/editor/lib/user.test.ts index e6d5eefb91..fc475aac37 100644 --- a/apps/web/modules/survey/editor/lib/user.test.ts +++ b/apps/web/modules/survey/editor/lib/user.test.ts @@ -21,7 +21,9 @@ describe("getUserEmail", () => { test("should return user email if user is found", async () => { const mockUser = { id: "test-user-id", email: "test@example.com" }; - vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser); + vi.mocked(prisma.user.findUnique).mockResolvedValue( + mockUser as Awaited> + ); const email = await getUserEmail("test-user-id"); expect(email).toBe("test@example.com"); @@ -64,7 +66,9 @@ describe("getUserLocale", () => { test("should return user locale if user is found", async () => { const mockUser = { id: "test-user-id", locale: "en" as TUserLocale }; - vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser); + vi.mocked(prisma.user.findUnique).mockResolvedValue( + mockUser as Awaited> + ); const locale = await getUserLocale("test-user-id"); expect(locale).toBe("en"); diff --git a/apps/web/modules/survey/editor/lib/utils.tsx b/apps/web/modules/survey/editor/lib/utils.tsx index 6c206ecab3..bcbb00cc4b 100644 --- a/apps/web/modules/survey/editor/lib/utils.tsx +++ b/apps/web/modules/survey/editor/lib/utils.tsx @@ -24,7 +24,7 @@ import { isConditionGroup } from "@/lib/surveyLogic/utils"; import { recallToHeadline } from "@/lib/utils/recall"; import { findElementLocation } from "@/modules/survey/editor/lib/blocks"; import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils"; -import { getElementTypes, getTSurveyElementTypeEnumName } from "@/modules/survey/lib/elements"; +import { type TElement, getElementTypes, getTSurveyElementTypeEnumName } from "@/modules/survey/lib/elements"; import { TComboboxGroupedOption, TComboboxOption } from "@/modules/ui/components/input-combo-box"; import { TLogicRuleOption, getLogicRules } from "./logic-rule-engine"; @@ -95,8 +95,8 @@ export const formatTextWithSlashes = ( }); }; -const getElementIconMapping = (t: TFunction) => - getElementTypes(t).reduce( +const getElementIconMapping = (t: TFunction): Record => + getElementTypes(t).reduce>( (prev, curr) => ({ ...prev, [curr.id]: curr.icon, diff --git a/apps/web/modules/survey/editor/lib/validation-rules-helpers.test.ts b/apps/web/modules/survey/editor/lib/validation-rules-helpers.test.ts index 8b62eae748..8bdc1cec00 100644 --- a/apps/web/modules/survey/editor/lib/validation-rules-helpers.test.ts +++ b/apps/web/modules/survey/editor/lib/validation-rules-helpers.test.ts @@ -107,7 +107,7 @@ describe("getDefaultRuleValue", () => { { id: "opt2", label: { default: "Option 2" } }, { id: "other", label: { default: "Other" } }, ], - } as TSurveyMultipleChoiceElement; + } as unknown as TSurveyMultipleChoiceElement; const config = RULE_TYPE_CONFIG.equals; const value = getDefaultRuleValue(config, element); @@ -124,7 +124,7 @@ describe("getDefaultRuleValue", () => { { id: "none", label: { default: "None" } }, { id: "opt1", label: { default: "Option 1" } }, ], - } as TSurveyMultipleChoiceElement; + } as unknown as TSurveyMultipleChoiceElement; const config = RULE_TYPE_CONFIG.equals; const value = getDefaultRuleValue(config, element); @@ -140,7 +140,7 @@ describe("getDefaultRuleValue", () => { { id: "other", label: { default: "Other" } }, { id: "none", label: { default: "None" } }, ], - } as TSurveyMultipleChoiceElement; + } as unknown as TSurveyMultipleChoiceElement; const config = RULE_TYPE_CONFIG.equals; const value = getDefaultRuleValue(config, element); @@ -161,7 +161,7 @@ describe("getDefaultRuleValue", () => { { id: "opt1", label: { default: "Option 1" } }, { id: "opt2", label: { default: "Option 2" } }, ], - } as TSurveyRankingElement; + } as unknown as TSurveyRankingElement; const config = RULE_TYPE_CONFIG.minRanked; const value = getDefaultRuleValue(config, element); diff --git a/apps/web/modules/survey/editor/lib/validation.ts b/apps/web/modules/survey/editor/lib/validation.ts index aea639314c..f49b2fe3f0 100644 --- a/apps/web/modules/survey/editor/lib/validation.ts +++ b/apps/web/modules/survey/editor/lib/validation.ts @@ -150,12 +150,13 @@ export const validationRules = { let fieldsToValidate = ["upperLabel", "lowerLabel"]; for (const field of fieldsToValidate) { + const fieldValue = (element as unknown as Record | undefined>)[field]; if ( - element[field] && - typeof element[field][defaultLanguageCode] !== "undefined" && - element[field][defaultLanguageCode].trim() !== "" + fieldValue && + typeof fieldValue[defaultLanguageCode] !== "undefined" && + fieldValue[defaultLanguageCode].trim() !== "" ) { - isValid = isValid && isLabelValidForAllLanguages(element[field], languages); + isValid = isValid && isLabelValidForAllLanguages(fieldValue, languages); } } @@ -165,7 +166,12 @@ export const validationRules = { // Main validation function export const validateElement = (element: TSurveyElement, surveyLanguages: TSurveyLanguage[]): boolean => { - const specificValidation = validationRules[element.type]; + const specificValidation = ( + validationRules as Record< + string, + ((element: TSurveyElement, languages: TSurveyLanguage[]) => boolean) | undefined + > + )[element.type]; const defaultValidation = validationRules.defaultValidation; const specificValidationResult = specificValidation ? specificValidation(element, surveyLanguages) : true; diff --git a/apps/web/modules/survey/editor/page.tsx b/apps/web/modules/survey/editor/page.tsx index 25a7e5355b..5d02e92c74 100644 --- a/apps/web/modules/survey/editor/page.tsx +++ b/apps/web/modules/survey/editor/page.tsx @@ -30,7 +30,7 @@ import { ErrorComponent } from "@/modules/ui/components/error-component"; import { SurveyEditor } from "./components/survey-editor"; import { getUserLocale } from "./lib/user"; -export const generateMetadata = async (props) => { +export const generateMetadata = async (props: { params: Promise<{ surveyId: string }> }) => { const params = await props.params; const survey = await getSurvey(params.surveyId); return { @@ -38,7 +38,10 @@ export const generateMetadata = async (props) => { }; }; -export const SurveyEditorPage = async (props) => { +export const SurveyEditorPage = async (props: { + params: Promise<{ environmentId: string; surveyId: string }>; + searchParams: Promise<{ mode?: string }>; +}) => { const searchParams = await props.searchParams; const params = await props.params; diff --git a/apps/web/modules/survey/lib/project.test.ts b/apps/web/modules/survey/lib/project.test.ts index 7b7980c53e..d5944fab9a 100644 --- a/apps/web/modules/survey/lib/project.test.ts +++ b/apps/web/modules/survey/lib/project.test.ts @@ -58,7 +58,7 @@ const mockProjectPrisma = { organizationId: "clq6167un000008l56jd8s3f9", config: { channel: "app", industry: "eCommerce" }, logo: null, -} as Project; +} as unknown as Project; const mockProjectWithTeam: Project & { teamIds: string[] } = { ...mockProjectPrisma, diff --git a/apps/web/modules/survey/lib/utils.ts b/apps/web/modules/survey/lib/utils.ts index 9d0ba081f2..62e8e527b0 100644 --- a/apps/web/modules/survey/lib/utils.ts +++ b/apps/web/modules/survey/lib/utils.ts @@ -12,7 +12,7 @@ export const transformPrismaSurvey = survey.id), + surveys: surveyPrisma.segment.surveys.map((survey: { id: string }) => survey.id), }; } diff --git a/apps/web/modules/survey/link/layout.tsx b/apps/web/modules/survey/link/layout.tsx index 06bf60fae3..da7ffe56de 100644 --- a/apps/web/modules/survey/link/layout.tsx +++ b/apps/web/modules/survey/link/layout.tsx @@ -8,6 +8,6 @@ export const viewport: Viewport = { viewportFit: "contain", }; -export const LinkSurveyLayout = ({ children }) => { +export const LinkSurveyLayout = ({ children }: { children: React.ReactNode }) => { return
{children}
; }; diff --git a/apps/web/modules/survey/link/lib/metadata-utils.test.ts b/apps/web/modules/survey/link/lib/metadata-utils.test.ts index 4983a4c768..d30abf5c6a 100644 --- a/apps/web/modules/survey/link/lib/metadata-utils.test.ts +++ b/apps/web/modules/survey/link/lib/metadata-utils.test.ts @@ -110,7 +110,7 @@ describe("Metadata Utils", () => { default: "Welcome Description", }, } as TSurveyWelcomeCard, - } as TSurvey; + } as unknown as TSurvey; vi.mocked(getSurvey).mockResolvedValue(mockSurvey); vi.mocked(getProjectByEnvironmentId).mockResolvedValue({ name: "Test Project" } as any); @@ -135,7 +135,7 @@ describe("Metadata Utils", () => { welcomeCard: { enabled: false, } as TSurveyWelcomeCard, - } as TSurvey; + } as unknown as TSurvey; vi.mocked(getSurvey).mockResolvedValue(mockSurvey); @@ -168,7 +168,7 @@ describe("Metadata Utils", () => { welcomeCard: { enabled: false, } as TSurveyWelcomeCard, - } as TSurvey; + } as unknown as TSurvey; vi.mocked(getSurvey).mockResolvedValue(mockSurvey); @@ -203,7 +203,7 @@ describe("Metadata Utils", () => { default: "Welcome Description", }, } as TSurveyWelcomeCard, - } as TSurvey; + } as unknown as TSurvey; vi.mocked(getSurvey).mockResolvedValue(mockSurvey); vi.mocked(getTextContent).mockReturnValue("Welcome Headline"); @@ -234,7 +234,7 @@ describe("Metadata Utils", () => { default: "Welcome Description", }, } as TSurveyWelcomeCard, - } as TSurvey; + } as unknown as TSurvey; vi.mocked(getSurvey).mockResolvedValue(mockSurvey); vi.mocked(recallToHeadline).mockReturnValue({ @@ -287,7 +287,7 @@ describe("Metadata Utils", () => { const surveyName = "Test Survey With Spaces"; const result = getSurveyOpenGraphMetadata(surveyId, surveyName); - expect(result.openGraph?.images?.[0]).toContain("name=Test%20Survey%20With%20Spaces"); + expect((result.openGraph?.images as string[])?.[0]).toContain("name=Test%20Survey%20With%20Spaces"); }); }); }); diff --git a/apps/web/modules/survey/link/metadata.test.ts b/apps/web/modules/survey/link/metadata.test.ts index f13969c0ca..8989481e0e 100644 --- a/apps/web/modules/survey/link/metadata.test.ts +++ b/apps/web/modules/survey/link/metadata.test.ts @@ -56,6 +56,7 @@ describe("getMetadataForLinkSurvey", () => { styling: null, logo: null, linkSurveyBranding: true, + customHeadScripts: null, }, organizationId: "org-123", organizationBilling: { diff --git a/apps/web/modules/survey/list/actions.ts b/apps/web/modules/survey/list/actions.ts index 5f52e84726..6854b5df2d 100644 --- a/apps/web/modules/survey/list/actions.ts +++ b/apps/web/modules/survey/list/actions.ts @@ -5,7 +5,6 @@ import { OperationNotAllowedError, ResourceNotFoundError } from "@formbricks/typ import { ZSurveyFilterCriteria } from "@formbricks/types/surveys/types"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { getEnvironmentIdFromSurveyId, getOrganizationIdFromEnvironmentId, @@ -58,86 +57,74 @@ const ZCopySurveyToOtherEnvironmentAction = z.object({ export const copySurveyToOtherEnvironmentAction = authenticatedActionClient .inputSchema(ZCopySurveyToOtherEnvironmentAction) .action( - withAuditLogging( - "copiedToOtherEnvironment", - "survey", - async ({ - ctx, - parsedInput, - }: { - ctx: AuthenticatedActionClientCtx; - parsedInput: z.infer; - }) => { - const sourceEnvironmentId = await getEnvironmentIdFromSurveyId(parsedInput.surveyId); - const sourceEnvironmentProjectId = await getProjectIdIfEnvironmentExists(sourceEnvironmentId); - const targetEnvironmentProjectId = await getProjectIdIfEnvironmentExists( - parsedInput.targetEnvironmentId + withAuditLogging("copiedToOtherEnvironment", "survey", async ({ ctx, parsedInput }) => { + const sourceEnvironmentId = await getEnvironmentIdFromSurveyId(parsedInput.surveyId); + const sourceEnvironmentProjectId = await getProjectIdIfEnvironmentExists(sourceEnvironmentId); + const targetEnvironmentProjectId = await getProjectIdIfEnvironmentExists( + parsedInput.targetEnvironmentId + ); + + if (!sourceEnvironmentProjectId || !targetEnvironmentProjectId) { + throw new ResourceNotFoundError( + "Environment", + sourceEnvironmentProjectId ? parsedInput.targetEnvironmentId : sourceEnvironmentId ); - - if (!sourceEnvironmentProjectId || !targetEnvironmentProjectId) { - throw new ResourceNotFoundError( - "Environment", - sourceEnvironmentProjectId ? parsedInput.targetEnvironmentId : sourceEnvironmentId - ); - } - - const sourceEnvironmentOrganizationId = await getOrganizationIdFromEnvironmentId(sourceEnvironmentId); - const targetEnvironmentOrganizationId = await getOrganizationIdFromEnvironmentId( - parsedInput.targetEnvironmentId - ); - - if (sourceEnvironmentOrganizationId !== targetEnvironmentOrganizationId) { - throw new OperationNotAllowedError( - "Source and target environments must be in the same organization" - ); - } - - // authorization check for source environment - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: sourceEnvironmentOrganizationId, - access: [ - { - type: "organization", - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - minPermission: "readWrite", - projectId: sourceEnvironmentProjectId, - }, - ], - }); - - // authorization check for target environment - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: targetEnvironmentOrganizationId, - access: [ - { - type: "organization", - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - minPermission: "readWrite", - projectId: targetEnvironmentProjectId, - }, - ], - }); - - ctx.auditLoggingCtx.organizationId = sourceEnvironmentOrganizationId; - ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; - const result = await copySurveyToOtherEnvironment( - sourceEnvironmentId, - parsedInput.surveyId, - parsedInput.targetEnvironmentId, - ctx.user.id - ); - ctx.auditLoggingCtx.newObject = result; - return result; } - ) + + const sourceEnvironmentOrganizationId = await getOrganizationIdFromEnvironmentId(sourceEnvironmentId); + const targetEnvironmentOrganizationId = await getOrganizationIdFromEnvironmentId( + parsedInput.targetEnvironmentId + ); + + if (sourceEnvironmentOrganizationId !== targetEnvironmentOrganizationId) { + throw new OperationNotAllowedError("Source and target environments must be in the same organization"); + } + + // authorization check for source environment + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: sourceEnvironmentOrganizationId, + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + minPermission: "readWrite", + projectId: sourceEnvironmentProjectId, + }, + ], + }); + + // authorization check for target environment + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: targetEnvironmentOrganizationId, + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + minPermission: "readWrite", + projectId: targetEnvironmentProjectId, + }, + ], + }); + + ctx.auditLoggingCtx.organizationId = sourceEnvironmentOrganizationId; + ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; + const result = await copySurveyToOtherEnvironment( + sourceEnvironmentId, + parsedInput.surveyId, + parsedInput.targetEnvironmentId, + ctx.user.id + ); + ctx.auditLoggingCtx.newObject = result; + return result; + }) ); const ZGetProjectsByEnvironmentIdAction = z.object({ @@ -172,32 +159,28 @@ const ZDeleteSurveyAction = z.object({ }); export const deleteSurveyAction = authenticatedActionClient.inputSchema(ZDeleteSurveyAction).action( - withAuditLogging( - "deleted", - "survey", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId: await getOrganizationIdFromSurveyId(parsedInput.surveyId), - access: [ - { - type: "organization", - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - projectId: await getProjectIdFromSurveyId(parsedInput.surveyId), - minPermission: "readWrite", - }, - ], - }); + withAuditLogging("deleted", "survey", async ({ ctx, parsedInput }) => { + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId: await getOrganizationIdFromSurveyId(parsedInput.surveyId), + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + projectId: await getProjectIdFromSurveyId(parsedInput.surveyId), + minPermission: "readWrite", + }, + ], + }); - ctx.auditLoggingCtx.organizationId = await getOrganizationIdFromSurveyId(parsedInput.surveyId); - ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; - ctx.auditLoggingCtx.oldObject = await getSurvey(parsedInput.surveyId); - return await deleteSurvey(parsedInput.surveyId); - } - ) + ctx.auditLoggingCtx.organizationId = await getOrganizationIdFromSurveyId(parsedInput.surveyId); + ctx.auditLoggingCtx.surveyId = parsedInput.surveyId; + ctx.auditLoggingCtx.oldObject = await getSurvey(parsedInput.surveyId); + return await deleteSurvey(parsedInput.surveyId); + }) ); const ZGenerateSingleUseIdAction = z.object({ diff --git a/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx b/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx index 2d8fef2ecb..ee59f9b9f7 100644 --- a/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx +++ b/apps/web/modules/survey/list/components/survey-dropdown-menu.tsx @@ -123,7 +123,7 @@ export const SurveyDropDownMenu = ({ setLoading(false); }; - const handleEditforActiveSurvey = (e) => { + const handleEditforActiveSurvey = (e: React.MouseEvent) => { e.preventDefault(); setIsDropDownOpen(false); setIsCautionDialogOpen(true); diff --git a/apps/web/modules/survey/list/components/survey-type-indicator.tsx b/apps/web/modules/survey/list/components/survey-type-indicator.tsx index 5f106633fe..1419c3bb0a 100644 --- a/apps/web/modules/survey/list/components/survey-type-indicator.tsx +++ b/apps/web/modules/survey/list/components/survey-type-indicator.tsx @@ -13,7 +13,9 @@ export const SurveyTypeIndicator = ({ type }: SurveyTypeIndicatorProps) => { app: { icon: Code, label: t("common.app") }, link: { icon: Link2Icon, label: t("common.link") }, }; - const { icon: Icon, label } = surveyTypeMapping[type] || { icon: HelpCircle, label: "Unknown" }; + const { icon: Icon, label } = (surveyTypeMapping as Record)[ + type + ] || { icon: HelpCircle, label: "Unknown" }; return (
diff --git a/apps/web/modules/survey/list/lib/environment.test.ts b/apps/web/modules/survey/list/lib/environment.test.ts index 920b08b6d9..1394a90c6b 100644 --- a/apps/web/modules/survey/list/lib/environment.test.ts +++ b/apps/web/modules/survey/list/lib/environment.test.ts @@ -41,7 +41,9 @@ describe("doesEnvironmentExist", () => { }); test("should return environmentId if environment exists", async () => { - vi.mocked(prisma.environment.findUnique).mockResolvedValue({ id: mockEnvironmentId }); + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ id: mockEnvironmentId } as Awaited< + ReturnType + >); const result = await doesEnvironmentExist(mockEnvironmentId); @@ -69,7 +71,9 @@ describe("getProjectIdIfEnvironmentExists", () => { }); test("should return projectId if environment exists", async () => { - vi.mocked(prisma.environment.findUnique).mockResolvedValue({ projectId: mockProjectId }); // Ensure correct mock value + vi.mocked(prisma.environment.findUnique).mockResolvedValue({ projectId: mockProjectId } as Awaited< + ReturnType + >); // Ensure correct mock value const result = await getProjectIdIfEnvironmentExists(mockEnvironmentId); @@ -98,7 +102,9 @@ describe("getEnvironment", () => { test("should return environment if it exists", async () => { const mockEnvData = { id: mockEnvironmentId, type: "production" as const }; - vi.mocked(prisma.environment.findUnique).mockResolvedValue(mockEnvData); + vi.mocked(prisma.environment.findUnique).mockResolvedValue( + mockEnvData as Awaited> + ); const result = await getEnvironment(mockEnvironmentId); diff --git a/apps/web/modules/survey/multi-language-surveys/components/localized-editor.tsx b/apps/web/modules/survey/multi-language-surveys/components/localized-editor.tsx index 58a4ab8fe4..8a04687d51 100644 --- a/apps/web/modules/survey/multi-language-surveys/components/localized-editor.tsx +++ b/apps/web/modules/survey/multi-language-surveys/components/localized-editor.tsx @@ -123,7 +123,7 @@ export function LocalizedEditor({ if (isEndingCard) { const ending = localSurvey.endings.find((ending) => ending.id === elementId); // If the field doesn't exist on the ending card, don't create it - if (ending?.[id] === undefined) { + if ((ending as Record)?.[id] === undefined) { return; } } @@ -142,7 +142,11 @@ export function LocalizedEditor({ } // Check if the field exists on the element (not just if it's not undefined) - if (currentElement && id in currentElement && currentElement[id] !== undefined) { + if ( + currentElement && + id in currentElement && + (currentElement as Record)[id] !== undefined + ) { const translatedContent = { ...value, [selectedLanguageCode]: sanitizedContent, diff --git a/apps/web/modules/survey/multi-language-surveys/lib/actions.ts b/apps/web/modules/survey/multi-language-surveys/lib/actions.ts index b08688e887..dcabe32a55 100644 --- a/apps/web/modules/survey/multi-language-surveys/lib/actions.ts +++ b/apps/web/modules/survey/multi-language-surveys/lib/actions.ts @@ -12,7 +12,6 @@ import { } from "@/lib/language/service"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; -import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; import { getOrganizationIdFromLanguageId, getOrganizationIdFromProjectId, @@ -26,37 +25,33 @@ const ZCreateLanguageAction = z.object({ }); export const createLanguageAction = authenticatedActionClient.inputSchema(ZCreateLanguageAction).action( - withAuditLogging( - "created", - "language", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { - const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); + withAuditLogging("created", "language", async ({ ctx, parsedInput }) => { + const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId, - access: [ - { - type: "organization", - schema: ZLanguageInput, - data: parsedInput.languageInput, - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - projectId: parsedInput.projectId, - minPermission: "manage", - }, - ], - }); + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId, + access: [ + { + type: "organization", + schema: ZLanguageInput, + data: parsedInput.languageInput, + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + projectId: parsedInput.projectId, + minPermission: "manage", + }, + ], + }); - const result = await createLanguage(parsedInput.projectId, parsedInput.languageInput); - ctx.auditLoggingCtx.organizationId = organizationId; - ctx.auditLoggingCtx.languageId = result.id; - ctx.auditLoggingCtx.newObject = result; - return result; - } - ) + const result = await createLanguage(parsedInput.projectId, parsedInput.languageInput); + ctx.auditLoggingCtx.organizationId = organizationId; + ctx.auditLoggingCtx.languageId = result.id; + ctx.auditLoggingCtx.newObject = result; + return result; + }) ); const ZDeleteLanguageAction = z.object({ @@ -65,41 +60,37 @@ const ZDeleteLanguageAction = z.object({ }); export const deleteLanguageAction = authenticatedActionClient.inputSchema(ZDeleteLanguageAction).action( - withAuditLogging( - "deleted", - "language", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { - const languageProjectId = await getProjectIdFromLanguageId(parsedInput.languageId); + withAuditLogging("deleted", "language", async ({ ctx, parsedInput }) => { + const languageProjectId = await getProjectIdFromLanguageId(parsedInput.languageId); - if (languageProjectId !== parsedInput.projectId) { - throw new Error("Invalid language id"); - } - - const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); - - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId, - access: [ - { - type: "organization", - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - projectId: parsedInput.projectId, - minPermission: "manage", - }, - ], - }); - - ctx.auditLoggingCtx.organizationId = organizationId; - ctx.auditLoggingCtx.languageId = parsedInput.languageId; - const result = await deleteLanguage(parsedInput.languageId, parsedInput.projectId); - ctx.auditLoggingCtx.oldObject = result; - return result; + if (languageProjectId !== parsedInput.projectId) { + throw new Error("Invalid language id"); } - ) + + const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); + + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId, + access: [ + { + type: "organization", + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + projectId: parsedInput.projectId, + minPermission: "manage", + }, + ], + }); + + ctx.auditLoggingCtx.organizationId = organizationId; + ctx.auditLoggingCtx.languageId = parsedInput.languageId; + const result = await deleteLanguage(parsedInput.languageId, parsedInput.projectId); + ctx.auditLoggingCtx.oldObject = result; + return result; + }) ); const ZGetSurveysUsingGivenLanguageAction = z.object({ @@ -137,46 +128,42 @@ const ZUpdateLanguageAction = z.object({ }); export const updateLanguageAction = authenticatedActionClient.inputSchema(ZUpdateLanguageAction).action( - withAuditLogging( - "updated", - "language", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { - const languageProductId = await getProjectIdFromLanguageId(parsedInput.languageId); + withAuditLogging("updated", "language", async ({ ctx, parsedInput }) => { + const languageProductId = await getProjectIdFromLanguageId(parsedInput.languageId); - if (languageProductId !== parsedInput.projectId) { - throw new Error("Invalid language id"); - } - - const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); - - await checkAuthorizationUpdated({ - userId: ctx.user.id, - organizationId, - access: [ - { - type: "organization", - schema: ZLanguageInput, - data: parsedInput.languageInput, - roles: ["owner", "manager"], - }, - { - type: "projectTeam", - projectId: parsedInput.projectId, - minPermission: "manage", - }, - ], - }); - - ctx.auditLoggingCtx.organizationId = organizationId; - ctx.auditLoggingCtx.languageId = parsedInput.languageId; - ctx.auditLoggingCtx.oldObject = await getLanguage(parsedInput.languageId); - const result = await updateLanguage( - parsedInput.projectId, - parsedInput.languageId, - parsedInput.languageInput - ); - ctx.auditLoggingCtx.newObject = result; - return result; + if (languageProductId !== parsedInput.projectId) { + throw new Error("Invalid language id"); } - ) + + const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId); + + await checkAuthorizationUpdated({ + userId: ctx.user.id, + organizationId, + access: [ + { + type: "organization", + schema: ZLanguageInput, + data: parsedInput.languageInput, + roles: ["owner", "manager"], + }, + { + type: "projectTeam", + projectId: parsedInput.projectId, + minPermission: "manage", + }, + ], + }); + + ctx.auditLoggingCtx.organizationId = organizationId; + ctx.auditLoggingCtx.languageId = parsedInput.languageId; + ctx.auditLoggingCtx.oldObject = await getLanguage(parsedInput.languageId); + const result = await updateLanguage( + parsedInput.projectId, + parsedInput.languageId, + parsedInput.languageInput + ); + ctx.auditLoggingCtx.newObject = result; + return result; + }) ); diff --git a/apps/web/modules/ui/components/alert/stories.tsx b/apps/web/modules/ui/components/alert/stories.tsx index de065a1f6f..24be78e02d 100644 --- a/apps/web/modules/ui/components/alert/stories.tsx +++ b/apps/web/modules/ui/components/alert/stories.tsx @@ -103,7 +103,7 @@ const meta: Meta = { export default meta; // Our story type just specifies Alert props plus our story options -type Story = StoryObj & { args: StoryOptions }; +type Story = StoryObj; // Create a common render function to reduce duplication const renderAlert = (args: StoryProps) => { diff --git a/apps/web/modules/ui/components/code-block/prismjs.d.ts b/apps/web/modules/ui/components/code-block/prismjs.d.ts new file mode 100644 index 0000000000..abbe18f060 --- /dev/null +++ b/apps/web/modules/ui/components/code-block/prismjs.d.ts @@ -0,0 +1,11 @@ +declare module "prismjs" { + const Prism: { + highlightAll: () => void; + highlightElement: (element: Element) => void; + highlight: (text: string, grammar: unknown, language: string) => string; + languages: Record; + }; + export default Prism; +} + +declare module "prismjs/themes/prism.css"; diff --git a/apps/web/modules/ui/components/conditions-editor/index.tsx b/apps/web/modules/ui/components/conditions-editor/index.tsx index f5bfffccb3..30a919f22e 100644 --- a/apps/web/modules/ui/components/conditions-editor/index.tsx +++ b/apps/web/modules/ui/components/conditions-editor/index.tsx @@ -179,8 +179,8 @@ export function ConditionsEditor({ showSearch groupedOptions={leftOperandOptions} value={config.formatLeftOperandValue(condition)} - onChangeValue={(val: string, option) => { - handleLeftOperandChange(condition, val, option); + onChangeValue={(val: string | number | string[], option) => { + handleLeftOperandChange(condition, String(val), option); }} />
@@ -191,8 +191,8 @@ export function ConditionsEditor({ showSearch={false} options={operatorOptions} value={condition.operator} - onChangeValue={(val: string) => { - handleOperatorChange(condition, val); + onChangeValue={(val: string | number | string[]) => { + handleOperatorChange(condition, String(val)); }} />
diff --git a/apps/web/modules/ui/components/data-table/components/data-table-settings-modal-item.tsx b/apps/web/modules/ui/components/data-table/components/data-table-settings-modal-item.tsx index 9ea2b31e52..533c67ec0b 100644 --- a/apps/web/modules/ui/components/data-table/components/data-table-settings-modal-item.tsx +++ b/apps/web/modules/ui/components/data-table/components/data-table-settings-modal-item.tsx @@ -40,7 +40,7 @@ export const DataTableSettingsModalItem = ({ column, table }: DataTableSetti - {flexRender(column.columnDef.header, header?.getContext() ?? { column })} + {header ? flexRender(column.columnDef.header, header.getContext()) : column.id}
{ updateRowList: (rowIds: string[]) => void; type: "response" | "contact" | "attribute"; deleteAction: (id: string, params?: Record) => Promise; - downloadRowsAction?: (rowIds: string[], format: string) => Promise; + downloadRowsAction?: (rowIds: string[], format: "xlsx" | "csv") => Promise; isQuotasAllowed: boolean; leftContent?: React.ReactNode; onRefresh?: () => Promise; diff --git a/apps/web/modules/ui/components/data-table/components/selected-row-settings.tsx b/apps/web/modules/ui/components/data-table/components/selected-row-settings.tsx index 7908e51b15..8e6e7d03c6 100644 --- a/apps/web/modules/ui/components/data-table/components/selected-row-settings.tsx +++ b/apps/web/modules/ui/components/data-table/components/selected-row-settings.tsx @@ -22,7 +22,7 @@ interface SelectedRowSettingsProps { updateRowList: (rowId: string[]) => void; type: "response" | "contact" | "attribute"; deleteAction: (id: string, params?: Record) => Promise; - downloadRowsAction?: (rowIds: string[], format: string) => Promise; + downloadRowsAction?: (rowIds: string[], format: "xlsx" | "csv") => Promise; isQuotasAllowed: boolean; } @@ -98,7 +98,7 @@ export const SelectedRowSettings = ({ }; // Handle download selected rows - const handleDownloadSelectedRows = async (format: string) => { + const handleDownloadSelectedRows = async (format: "xlsx" | "csv") => { setIsDownloading(true); const rowsToDownload = table.getFilteredSelectedRowModel().rows.map((row) => row.id); if (downloadRowsAction && rowsToDownload.length > 0) { diff --git a/apps/web/modules/ui/components/delete-dialog/stories.tsx b/apps/web/modules/ui/components/delete-dialog/stories.tsx index 16f1cfc38d..8adb862a9d 100644 --- a/apps/web/modules/ui/components/delete-dialog/stories.tsx +++ b/apps/web/modules/ui/components/delete-dialog/stories.tsx @@ -161,7 +161,7 @@ const meta: Meta = { export default meta; -type Story = StoryObj & { args: StoryOptions }; +type Story = StoryObj; // Create a render function for interactive dialogs const RenderDeleteDialog = (args: StoryProps) => { diff --git a/apps/web/modules/ui/components/dialog/stories.tsx b/apps/web/modules/ui/components/dialog/stories.tsx index eea32ee9f8..bd74860d44 100644 --- a/apps/web/modules/ui/components/dialog/stories.tsx +++ b/apps/web/modules/ui/components/dialog/stories.tsx @@ -208,7 +208,7 @@ const meta: Meta = { export default meta; -type Story = StoryObj & { args: StoryOptions }; +type Story = StoryObj; // Create a common render function to reduce duplication const renderModal = (args: StoryProps) => { diff --git a/apps/web/modules/ui/components/editor/components/recall-plugin.tsx b/apps/web/modules/ui/components/editor/components/recall-plugin.tsx index 7fe4b58140..76fb3ecf0d 100644 --- a/apps/web/modules/ui/components/editor/components/recall-plugin.tsx +++ b/apps/web/modules/ui/components/editor/components/recall-plugin.tsx @@ -68,7 +68,7 @@ export const RecallPlugin = ({ } } } catch (error) { - logger.error("Error traversing node:", error); + logger.error(error instanceof Error ? error : new Error(String(error)), "Error traversing node"); } }; @@ -124,7 +124,7 @@ export const RecallPlugin = ({ } node.remove(); } catch (error) { - logger.error("Error replacing text node:", error); + logger.error(error instanceof Error ? error : new Error(String(error)), "Error replacing text node"); } }, []); @@ -145,7 +145,10 @@ export const RecallPlugin = ({ recallNodes.push(...childRecallNodes); } } catch (error) { - logger.error("Error getting children from node:", error); + logger.error( + error instanceof Error ? error : new Error(String(error)), + "Error getting children from node" + ); } } diff --git a/apps/web/modules/ui/components/element-toggle-table/index.tsx b/apps/web/modules/ui/components/element-toggle-table/index.tsx index 54047f03d8..25fd256ab8 100644 --- a/apps/web/modules/ui/components/element-toggle-table/index.tsx +++ b/apps/web/modules/ui/components/element-toggle-table/index.tsx @@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next"; import { TI18nString } from "@formbricks/types/i18n"; -import { TSurveyAddressElement, TSurveyContactInfoElement } from "@formbricks/types/surveys/elements"; +import { TSurveyElement } from "@formbricks/types/surveys/elements"; import { TSurvey } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import { ElementFormInput } from "@/modules/survey/components/element-form-input"; @@ -20,10 +20,7 @@ interface ElementToggleTableProps { localSurvey: TSurvey; elementIdx: number; isInvalid: boolean; - updateElement: ( - elementIdx: number, - updatedAttributes: Partial - ) => void; + updateElement: (elementIdx: number, updatedAttributes: Partial) => void; selectedLanguageCode: string; setSelectedLanguageCode: (languageCode: string) => void; locale: TUserLocale; diff --git a/apps/web/modules/ui/components/no-code-action-form/components/page-url-selector.tsx b/apps/web/modules/ui/components/no-code-action-form/components/page-url-selector.tsx index 3bceb12def..8d13b39a22 100644 --- a/apps/web/modules/ui/components/no-code-action-form/components/page-url-selector.tsx +++ b/apps/web/modules/ui/components/no-code-action-form/components/page-url-selector.tsx @@ -92,7 +92,7 @@ export const PageUrlSelector = ({ form, isReadOnly }: PageUrlSelectorProps) => { if (match) toast.success(t("environments.actions.your_survey_would_be_shown_on_this_url")); if (!match) toast.error(t("environments.actions.your_survey_would_not_be_shown")); } catch (error) { - toast.error(error.message); + toast.error(error instanceof Error ? error.message : "Unknown error occurred"); } }; diff --git a/apps/web/modules/ui/components/tab-nav/stories.tsx b/apps/web/modules/ui/components/tab-nav/stories.tsx index 8d522439f1..8235e79d95 100644 --- a/apps/web/modules/ui/components/tab-nav/stories.tsx +++ b/apps/web/modules/ui/components/tab-nav/stories.tsx @@ -73,7 +73,7 @@ The **TabNav** component provides a navigation interface with tabs. It displays export default meta; -type Story = StoryObj & { args: StoryOptions }; +type Story = StoryObj; // Create a render function to handle dynamic tab generation const renderTabNav = (args: StoryProps) => { @@ -103,14 +103,12 @@ const renderTabNav = (args: StoryProps) => { const [activeId, setActiveId] = useState(tabs[0]?.id || "tab-1"); return ( - //
- //
); }; diff --git a/apps/web/modules/ui/components/tooltip/stories.tsx b/apps/web/modules/ui/components/tooltip/stories.tsx index 41537a0b39..a6598a4fa9 100644 --- a/apps/web/modules/ui/components/tooltip/stories.tsx +++ b/apps/web/modules/ui/components/tooltip/stories.tsx @@ -197,7 +197,7 @@ export const LongContent: Story = { }, }; -export const CustomStyling: StoryObj = { +export const CustomStyling: Story = { render: renderTooltip, args: { tooltipText: "This tooltip has custom styling", diff --git a/apps/web/playwright/api/management/contacts.spec.ts b/apps/web/playwright/api/management/contacts.spec.ts index a0673d4a95..5e08e857e8 100644 --- a/apps/web/playwright/api/management/contacts.spec.ts +++ b/apps/web/playwright/api/management/contacts.spec.ts @@ -4,7 +4,8 @@ import { loginAndGetApiKey } from "../../lib/utils"; test.describe("API Tests for Single Contact Creation", () => { test("Create and Test Contact Creation via API", async ({ page, users, request }) => { - let environmentId, apiKey; + let environmentId: string; + let apiKey: string; try { ({ environmentId, apiKey } = await loginAndGetApiKey(page, users)); diff --git a/apps/web/playwright/api/management/response.spec.ts b/apps/web/playwright/api/management/response.spec.ts index 0831110fa6..ff9faa0eb7 100644 --- a/apps/web/playwright/api/management/response.spec.ts +++ b/apps/web/playwright/api/management/response.spec.ts @@ -6,7 +6,8 @@ import { RESPONSES_API_URL, SURVEYS_API_URL } from "../constants"; test.describe("API Tests for Responses", () => { test("Create, Retrieve, Update, and Delete Responses via API", async ({ page, users, request }) => { - let environmentId, apiKey; + let environmentId: string; + let apiKey: string; try { ({ environmentId, apiKey } = await loginAndGetApiKey(page, users)); @@ -15,7 +16,9 @@ test.describe("API Tests for Responses", () => { throw error; } - let createdResponseId1, createdResponseId2, surveyId: string; + let createdResponseId1: string; + let createdResponseId2: string; + let surveyId: string; await test.step("Create Survey via API", async () => { const surveyBody = { @@ -53,7 +56,7 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string; id: string } }; expect(responseBody.data.name).toEqual("My new Survey from API"); surveyId = responseBody.data.id; }); @@ -98,7 +101,7 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseJson = await response.json(); + const responseJson = (await response.json()) as { data: { id: string } }; expect(responseJson.data).toHaveProperty("id"); createdResponseId1 = responseJson.data.id; }); @@ -143,7 +146,7 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseJson = await response.json(); + const responseJson = (await response.json()) as { data: { id: string } }; expect(responseJson.data).toHaveProperty("id"); createdResponseId2 = responseJson.data.id; }); @@ -164,7 +167,7 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string; [key: string]: unknown }[] }; expect(Array.isArray(responseBody.data)).toBe(true); expect(responseBody.data.length).toBeGreaterThan(0); @@ -253,14 +256,10 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string }[] }; expect(Array.isArray(responseBody.data)).toBe(true); expect(responseBody.data.length).toBeGreaterThan(0); - const createdResponse1 = responseBody.data.find((resp) => resp.id === createdResponseId1); - - const createdResponse2 = responseBody.data.find((resp) => resp.id === createdResponseId2); - // Check if the responses are sorted correctly expect(responseBody.data[0].id).toBe(createdResponseId1); expect(responseBody.data[1].id).toBe(createdResponseId2); @@ -282,12 +281,10 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string }[] }; expect(Array.isArray(responseBody.data)).toBe(true); expect(responseBody.data.length).toBe(1); - const createdResponse1 = responseBody.data.find((resp) => resp.id === createdResponseId1); - expect(responseBody.data[0].id).toBe(createdResponseId1); }); @@ -307,13 +304,11 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string }[] }; expect(Array.isArray(responseBody.data)).toBe(true); expect(responseBody.data.length).toBe(1); - const createdResponse2 = responseBody.data.find((resp) => resp.id === createdResponseId2); - - expect(responseBody.data[0].id).toBe(createdResponse2.id); + expect(responseBody.data[0].id).toBe(createdResponseId2); }); await test.step("Update Response by ID via API", async () => { @@ -371,7 +366,7 @@ test.describe("API Tests for Responses", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string; [key: string]: unknown } }; expect(responseBody.data.id).toEqual(createdResponseId1); expect(responseBody.data).toMatchObject({ createdAt: "2021-01-01T00:00:00.000Z", diff --git a/apps/web/playwright/api/management/survey.spec.ts b/apps/web/playwright/api/management/survey.spec.ts index eb446c87a6..8753f98957 100644 --- a/apps/web/playwright/api/management/survey.spec.ts +++ b/apps/web/playwright/api/management/survey.spec.ts @@ -5,7 +5,9 @@ import { loginAndGetApiKey } from "../../lib/utils"; import { SURVEYS_API_URL } from "../constants"; test.describe("API Tests", () => { - let surveyId, environmentId, apiKey; + let surveyId: string; + let environmentId: string; + let apiKey: string; test("API Tests", async ({ page, users, request }) => { try { diff --git a/apps/web/playwright/api/management/webhook.spec.ts b/apps/web/playwright/api/management/webhook.spec.ts index 2dc59d3925..a8304fc92d 100644 --- a/apps/web/playwright/api/management/webhook.spec.ts +++ b/apps/web/playwright/api/management/webhook.spec.ts @@ -6,7 +6,8 @@ import { loginAndGetApiKey } from "../../lib/utils"; test.describe("API Tests for Webhooks", () => { test("Create, Retrieve, Update, and Delete Webhooks via API", async ({ page, users, request }) => { - let environmentId, apiKey; + let environmentId: string; + let apiKey: string; try { ({ environmentId, apiKey } = await loginAndGetApiKey(page, users)); diff --git a/apps/web/playwright/api/organization/project-team.spec.ts b/apps/web/playwright/api/organization/project-team.spec.ts index fa69847c1b..b0c837b9c7 100644 --- a/apps/web/playwright/api/organization/project-team.spec.ts +++ b/apps/web/playwright/api/organization/project-team.spec.ts @@ -6,7 +6,7 @@ import { loginAndGetApiKey } from "../../lib/utils"; test.describe("API Tests for ProjectTeams", () => { test("Create, Retrieve, Update, and Delete ProjectTeams via API", async ({ page, users, request }) => { - let apiKey; + let apiKey: string; try { ({ apiKey } = await loginAndGetApiKey(page, users)); } catch (error) { @@ -14,7 +14,9 @@ test.describe("API Tests for ProjectTeams", () => { throw error; } - let organizationId, projectId, teamId: string; + let organizationId: string; + let projectId: string; + let teamId: string; // Get organization ID using the me endpoint await test.step("Get Organization ID", async () => { @@ -24,7 +26,9 @@ test.describe("API Tests for ProjectTeams", () => { }, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { + data: { organizationId: string; environmentPermissions: { projectId: string }[] }; + }; expect(responseBody.data).toBeTruthy(); expect(responseBody.data.organizationId).toBeTruthy(); @@ -49,7 +53,7 @@ test.describe("API Tests for ProjectTeams", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string; id: string } }; expect(responseBody.data.name).toEqual("New Team from API"); teamId = responseBody.data.id; }); @@ -80,11 +84,11 @@ test.describe("API Tests for ProjectTeams", () => { params: queryParams, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { + data: { teamId: string; projectId: string }[]; + }; expect(Array.isArray(responseBody.data)).toBe(true); - expect( - responseBody.data.find((pt: any) => pt.teamId === teamId && pt.projectId === projectId) - ).toBeTruthy(); + expect(responseBody.data.find((pt) => pt.teamId === teamId && pt.projectId === projectId)).toBeTruthy(); }); await test.step("Update ProjectTeam by ID via API", async () => { @@ -102,7 +106,7 @@ test.describe("API Tests for ProjectTeams", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { permission: string } }; expect(responseBody.data.permission).toBe("read"); }); diff --git a/apps/web/playwright/api/organization/team.spec.ts b/apps/web/playwright/api/organization/team.spec.ts index 1632d2b7ac..7da1ab533a 100644 --- a/apps/web/playwright/api/organization/team.spec.ts +++ b/apps/web/playwright/api/organization/team.spec.ts @@ -6,7 +6,7 @@ import { loginAndGetApiKey } from "../../lib/utils"; test.describe("API Tests for Teams", () => { test("Create, Retrieve, Update, and Delete Teams via API", async ({ page, users, request }) => { - let apiKey; + let apiKey: string; try { ({ apiKey } = await loginAndGetApiKey(page, users)); } catch (error) { @@ -14,7 +14,8 @@ test.describe("API Tests for Teams", () => { throw error; } - let organizationId, createdTeamId: string; + let organizationId: string; + let createdTeamId: string; // Get organization ID using the me endpoint await test.step("Get Organization ID", async () => { @@ -24,7 +25,7 @@ test.describe("API Tests for Teams", () => { }, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { organizationId: string } }; expect(responseBody.data).toBeTruthy(); expect(responseBody.data.organizationId).toBeTruthy(); @@ -47,7 +48,7 @@ test.describe("API Tests for Teams", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string; id: string } }; expect(responseBody.data.name).toEqual("New Team from API"); createdTeamId = responseBody.data.id; }); @@ -62,9 +63,9 @@ test.describe("API Tests for Teams", () => { params: queryParams, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string }[] }; expect(Array.isArray(responseBody.data)).toBe(true); - expect(responseBody.data.find((team: any) => team.id === createdTeamId)).toBeTruthy(); + expect(responseBody.data.find((team) => team.id === createdTeamId)).toBeTruthy(); }); await test.step("Update Team by ID via API", async () => { @@ -80,7 +81,7 @@ test.describe("API Tests for Teams", () => { data: updatedTeamBody, }); expect(response.ok()).toBe(true); - const responseJson = await response.json(); + const responseJson = (await response.json()) as { data: { name: string } }; expect(responseJson.data.name).toBe("Updated Team from API"); }); @@ -91,7 +92,7 @@ test.describe("API Tests for Teams", () => { }, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string; name: string } }; expect(responseBody.data.id).toEqual(createdTeamId); expect(responseBody.data.name).toEqual("Updated Team from API"); }); diff --git a/apps/web/playwright/api/organization/user.spec.ts b/apps/web/playwright/api/organization/user.spec.ts index 83e3a832e6..c7c17c87e4 100644 --- a/apps/web/playwright/api/organization/user.spec.ts +++ b/apps/web/playwright/api/organization/user.spec.ts @@ -6,7 +6,7 @@ import { loginAndGetApiKey } from "../../lib/utils"; test.describe("API Tests for Users", () => { test("Create, Retrieve, Filter, and Update Users via API", async ({ page, users, request }) => { - let apiKey; + let apiKey: string; let organizationId: string; let createdUserId: string; let teamName = "New Team from API"; @@ -26,7 +26,7 @@ test.describe("API Tests for Users", () => { headers: { "x-api-key": apiKey }, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { organizationId: string } }; expect(responseBody.data?.organizationId).toBeTruthy(); organizationId = responseBody.data.organizationId; }); @@ -47,7 +47,7 @@ test.describe("API Tests for Users", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string } }; expect(responseBody.data.name).toEqual(teamName); }); @@ -69,7 +69,7 @@ test.describe("API Tests for Users", () => { }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string; id: string } }; expect(responseBody.data.name).toEqual("E2E Test User"); createdUserId = responseBody.data.id; }); @@ -79,9 +79,9 @@ test.describe("API Tests for Users", () => { headers: { "x-api-key": apiKey }, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { id: string }[] }; expect(Array.isArray(responseBody.data)).toBe(true); - expect(responseBody.data.some((user: any) => user.id === createdUserId)).toBe(true); + expect(responseBody.data.some((user) => user.id === createdUserId)).toBe(true); }); await test.step("Filter Users by Email via API", async () => { @@ -91,7 +91,7 @@ test.describe("API Tests for Users", () => { params: queryParams, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { email: string }[] }; expect(responseBody.data.length).toBeGreaterThan(0); expect(responseBody.data[0].email).toBe(userEmail); @@ -104,7 +104,7 @@ test.describe("API Tests for Users", () => { data: patchData, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { data: { name: string } }; expect(responseBody.data.name).toBe("Updated E2E Name"); }); @@ -121,7 +121,9 @@ test.describe("API Tests for Users", () => { data: patchData, }); expect(response.ok()).toBe(true); - const responseBody = await response.json(); + const responseBody = (await response.json()) as { + data: { name: string; role: string; isActive: boolean; teams: string[] }; + }; expect(responseBody.data.name).toBe("Fully Updated E2E"); expect(responseBody.data.role).toBe("member"); expect(responseBody.data.isActive).toBe(false); diff --git a/apps/web/playwright/api/role.spec.ts b/apps/web/playwright/api/role.spec.ts index c176786bd1..385f8fbb8d 100644 --- a/apps/web/playwright/api/role.spec.ts +++ b/apps/web/playwright/api/role.spec.ts @@ -6,7 +6,7 @@ import { loginAndGetApiKey } from "../lib/utils"; test.describe("API Tests for Roles", () => { test("Retrieve Roles via API", async ({ page, users, request }) => { - let apiKey; + let apiKey: string; try { ({ apiKey } = await loginAndGetApiKey(page, users)); diff --git a/apps/web/playwright/lib/utils.ts b/apps/web/playwright/lib/utils.ts index 06aacab846..ddefedf045 100644 --- a/apps/web/playwright/lib/utils.ts +++ b/apps/web/playwright/lib/utils.ts @@ -39,7 +39,7 @@ export async function loginAndGetApiKey(page: Page, users: UsersFixture) { await page.locator(".copyApiKeyIcon").first().click(); - const apiKey = await page.evaluate("navigator.clipboard.readText()"); + const apiKey = (await page.evaluate("navigator.clipboard.readText()")) as string; return { environmentId, apiKey }; } diff --git a/packages/config-typescript/nextjs.json b/packages/config-typescript/nextjs.json index 3547f0134c..25401b229e 100644 --- a/packages/config-typescript/nextjs.json +++ b/packages/config-typescript/nextjs.json @@ -12,7 +12,7 @@ "noEmit": true, "plugins": [{ "name": "next" }], "resolveJsonModule": true, - "strict": false, + "strict": true, "target": "esnext" }, "display": "Next.js",