From 295754480efb9fbcb95fe87479f7c33c24c49fa5 Mon Sep 17 00:00:00 2001 From: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com> Date: Mon, 27 May 2024 11:11:21 +0530 Subject: [PATCH] chore: Rename Teams to Organizations (#2656) Co-authored-by: Matthias Nannt --- .env.example | 10 +- .github/workflows/kamal-deploy.yml | 131 +++++++++++++ .github/workflows/kamal-setup.yml | 128 ++++++++++++ README.md | 2 +- apps/docs/app/app-surveys/actions/page.mdx | 2 +- .../app/best-practices/pmf-survey/page.mdx | 2 +- .../app/self-hosting/configuration/page.mdx | 4 +- apps/docs/app/self-hosting/license/page.mdx | 29 +-- .../actions-and-targeting/page.mdx | 2 +- .../environments/[environmentId]/layout.tsx | 18 +- .../components/FileUploadQuestionForm.tsx | 4 +- .../surveys/[surveyId]/edit/page.tsx | 18 +- .../components/CreateFirstOrganization.tsx} | 32 +-- .../page.tsx | 4 +- .../components/AttributeSettingsTab.tsx | 2 +- .../[personId]/components/ActivitySection.tsx | 10 +- .../(people)/people/[personId]/page.tsx | 29 +-- .../(people)/segments/page.tsx | 12 +- .../environments/[environmentId]/actions.ts | 22 +-- .../[environmentId]/actions/actions.ts | 38 ++-- .../[environmentId]/actions/page.tsx | 12 +- .../components/AddProductModal.tsx | 2 +- .../components/EnvironmentLayout.tsx | 25 +-- .../components/MainNavigation.tsx | 73 ++++--- .../components/PosthogIdentify.tsx | 20 +- .../[environmentId]/integrations/page.tsx | 14 +- .../environments/[environmentId]/layout.tsx | 18 +- .../[environmentId]/product/api-keys/page.tsx | 14 +- .../product/general/actions.ts | 18 +- .../general/components/DeleteProduct.tsx | 14 +- .../[environmentId]/product/general/page.tsx | 18 +- .../product/languages/page.tsx | 10 +- .../[environmentId]/product/layout.tsx | 10 +- .../[environmentId]/product/look/actions.ts | 2 +- .../[environmentId]/product/look/page.tsx | 20 +- .../[environmentId]/product/setup/page.tsx | 12 +- .../[environmentId]/product/tags/page.tsx | 14 +- .../settings/(account)/layout.tsx | 10 +- .../notifications/components/EditAlerts.tsx | 14 +- .../components/EditWeeklySummary.tsx | 8 +- .../components/NotificationSwitch.tsx | 41 ++-- .../settings/(account)/notifications/page.tsx | 6 +- .../settings/(account)/notifications/types.ts | 2 +- .../profile/components/DeleteAccount.tsx | 8 +- .../settings/(account)/profile/loading.tsx | 2 +- .../settings/(account)/profile/page.tsx | 4 +- .../billing/actions.ts | 26 +-- .../billing/components/PricingTable.tsx | 36 ++-- .../billing/layout.tsx | 12 +- .../billing/loading.tsx | 0 .../billing/page.tsx | 30 +-- .../billing/unlimited/page.tsx | 10 +- .../billing/unlimited99/page.tsx | 10 +- .../OrganizationSettingsNavbar.tsx} | 2 +- .../enterprise/loading.tsx | 0 .../enterprise/page.tsx | 20 +- .../{(team) => (organization)}/layout.tsx | 10 +- .../members/actions.ts | 60 +++--- .../members/components/AddMemberModal.tsx | 2 +- .../members/components/BulkInviteTab.tsx | 4 +- .../components/DeleteOrganization.tsx} | 70 ++++--- .../EditMemberships/EditMemberships.tsx | 20 +- .../EditMemberships/MemberActions.tsx | 18 +- .../EditMemberships/MembersInfo.tsx | 12 +- .../EditMemberships/OrganizationActions.tsx} | 71 ++++--- .../components/EditMemberships/index.ts | 0 .../components/EditOrganizationName.tsx | 96 +++++++++ .../components/IndividualInviteTab.tsx | 0 .../members/components/ShareInviteModal.tsx | 8 +- .../members/loading.tsx | 6 +- .../members/page.tsx | 57 +++--- .../members/components/EditTeamName.tsx | 96 --------- .../responses/components/ResponseTimeline.tsx | 4 +- .../[surveyId]/(analysis)/responses/page.tsx | 12 +- .../summary/components/ShareSurveyResults.tsx | 4 +- .../[surveyId]/(analysis)/summary/page.tsx | 12 +- .../[environmentId]/surveys/page.tsx | 12 +- apps/web/app/(app)/onboarding/actions.ts | 16 +- .../inapp/ConnectWithFormbricks.tsx | 10 +- ...eamMate.tsx => InviteOrganizationMate.tsx} | 23 ++- .../onboarding/components/onboarding.tsx | 14 +- apps/web/app/(app)/onboarding/page.tsx | 10 +- .../components/InviteContentComponents.tsx | 2 +- apps/web/app/(auth)/invite/page.tsx | 2 +- .../[organizationId]}/route.ts | 12 +- .../(redirects)/products/[productId]/route.ts | 4 +- apps/web/app/api/cron/report-usage/route.ts | 29 +-- apps/web/app/api/cron/weekly-summary/route.ts | 42 ++-- apps/web/app/api/pipeline/route.ts | 4 +- .../people/[userId]/set-attribute/route.ts | 8 +- .../people/[personId]/set-attribute/route.ts | 8 +- .../app/api/v1/(legacy)/js/sync/lib/sync.ts | 30 +-- .../client/[environmentId]/actions/route.ts | 6 +- .../app/sync/[userId]/route.ts | 30 +-- .../[environmentId]/storage/local/route.ts | 15 +- .../client/[environmentId]/storage/route.ts | 15 +- .../[environmentId]/website/sync/route.ts | 19 +- apps/web/app/api/v1/users/me/route.ts | 40 ++-- apps/web/app/api/v1/users/route.ts | 45 +++-- apps/web/app/lib/api/apiHelper.ts | 18 +- apps/web/app/page.tsx | 10 +- apps/web/app/s/[surveyId]/page.tsx | 10 +- apps/web/playwright/onboarding.spec.ts | 4 +- .../{team.spec.ts => organization.spec.ts} | 16 +- apps/web/playwright/utils/mock.ts | 16 +- ...mbricks-organization-members-template.csv} | 0 docker-compose.yml | 9 +- docker/docker-compose.yml | 8 +- kamal/deploy.yml | 2 +- packages/database/jsonTypes.ts | 4 +- .../migration.sql | 28 +++ packages/database/schema.prisma | 60 +++--- packages/database/zod-utils.ts | 2 +- .../components/EditMembershipRole.tsx | 10 +- .../components/TransferOwnershipModal.tsx | 6 +- packages/ee/RoleManagement/lib/actions.ts | 32 +-- .../handlers/checkoutSessionCompleted.ts | 30 +-- .../handlers/subscriptionCreatedOrUpdated.ts | 54 ++--- .../billing/handlers/subscriptionDeleted.ts | 48 ++--- packages/ee/billing/lib/createSubscription.ts | 52 ++--- packages/ee/billing/lib/downgradePlan.ts | 12 +- packages/ee/billing/lib/removeSubscription.ts | 32 +-- packages/ee/lib/service.ts | 22 +-- packages/ee/multiLanguage/lib/actions.ts | 6 +- .../email/components/invite/InviteEmail.tsx | 2 +- .../invite/OnboardingInviteEmail.tsx | 4 +- .../survey/ResponseFinishedEmail.tsx | 8 +- packages/email/index.tsx | 15 +- packages/lib/actionClass/auth.ts | 12 +- packages/lib/auth.ts | 34 ++-- packages/lib/authOptions.ts | 39 ++-- packages/lib/constants.ts | 4 +- packages/lib/env.ts | 8 +- packages/lib/environment/auth.ts | 9 +- packages/lib/environment/service.ts | 16 +- packages/lib/invite/cache.ts | 12 +- packages/lib/invite/service.ts | 36 ++-- packages/lib/membership/cache.ts | 12 +- packages/lib/membership/hooks/actions.ts | 14 +- .../membership/hooks/useMembershipRole.tsx | 4 +- packages/lib/membership/service.ts | 78 ++++---- packages/lib/{team => organization}/auth.ts | 26 +-- packages/lib/{team => organization}/cache.ts | 8 +- packages/lib/organization/hooks/actions.ts | 25 +++ .../hooks/useGetBillingInfo.ts | 12 +- .../lib/{team => organization}/service.ts | 184 +++++++++--------- packages/lib/product/auth.ts | 18 +- packages/lib/product/cache.ts | 12 +- packages/lib/product/service.ts | 24 +-- packages/lib/survey/auth.ts | 12 +- packages/lib/survey/service.ts | 4 +- .../lib/survey/tests/__mock__/survey.mock.ts | 10 +- packages/lib/survey/tests/survey.test.ts | 4 +- packages/lib/tag/auth.ts | 12 +- packages/lib/tagOnResponse/auth.ts | 12 +- packages/lib/team/hooks/actions.ts | 25 --- packages/lib/user/service.ts | 40 ++-- packages/types/invites.ts | 2 +- packages/types/memberships.ts | 2 +- packages/types/{teams.ts => organizations.ts} | 22 +-- packages/types/product.ts | 4 +- packages/types/user.ts | 2 +- packages/types/weeklySummary.ts | 6 +- .../index.tsx | 36 ++-- packages/ui/PricingCard/index.tsx | 24 +-- turbo.json | 4 +- 166 files changed, 1863 insertions(+), 1459 deletions(-) create mode 100644 .github/workflows/kamal-deploy.yml create mode 100644 .github/workflows/kamal-setup.yml rename apps/web/app/(app)/{create-first-team/components/CreateFirstTeam.tsx => create-first-organization/components/CreateFirstOrganization.tsx} (65%) rename apps/web/app/(app)/{create-first-team => create-first-organization}/page.tsx (66%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/actions.ts (64%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/components/PricingTable.tsx (91%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/layout.tsx (67%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/loading.tsx (100%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/page.tsx (57%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/unlimited/page.tsx (66%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/billing/unlimited99/page.tsx (66%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team)/components/TeamSettingsNavbar.tsx => (organization)/components/OrganizationSettingsNavbar.tsx} (97%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/enterprise/loading.tsx (100%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/enterprise/page.tsx (89%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/layout.tsx (64%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/actions.ts (66%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/AddMemberModal.tsx (96%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/BulkInviteTab.tsx (97%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team)/members/components/DeleteTeam.tsx => (organization)/members/components/DeleteOrganization.tsx} (56%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/EditMemberships/EditMemberships.tsx (73%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/EditMemberships/MemberActions.tsx (87%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/EditMemberships/MembersInfo.tsx (91%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team)/members/components/EditMemberships/TeamActions.tsx => (organization)/members/components/EditMemberships/OrganizationActions.tsx} (55%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/EditMemberships/index.ts (100%) create mode 100644 apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditOrganizationName.tsx rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/IndividualInviteTab.tsx (100%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/components/ShareInviteModal.tsx (90%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/loading.tsx (93%) rename apps/web/app/(app)/environments/[environmentId]/settings/{(team) => (organization)}/members/page.tsx (66%) delete mode 100644 apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx rename apps/web/app/(app)/onboarding/components/inapp/{InviteTeamMate.tsx => InviteOrganizationMate.tsx} (87%) rename apps/web/app/(redirects)/{teams/[teamId] => organizations/[organizationId]}/route.ts (71%) rename apps/web/playwright/{team.spec.ts => organization.spec.ts} (88%) rename apps/web/public/sample-csv/{formbricks-team-members-template.csv => formbricks-organization-members-template.csv} (100%) create mode 100644 packages/database/migrations/20240516122752_rename_teams_to_organizations/migration.sql rename packages/lib/{team => organization}/auth.ts (61%) rename packages/lib/{team => organization}/cache.ts (76%) create mode 100644 packages/lib/organization/hooks/actions.ts rename packages/lib/{team => organization}/hooks/useGetBillingInfo.ts (62%) rename packages/lib/{team => organization}/service.ts (61%) delete mode 100644 packages/lib/team/hooks/actions.ts rename packages/types/{teams.ts => organizations.ts} (56%) rename packages/ui/{CreateTeamModal => CreateOrganizationModal}/index.tsx (68%) diff --git a/.env.example b/.env.example index c203e9d9ba..3eb1a41126 100644 --- a/.env.example +++ b/.env.example @@ -88,7 +88,7 @@ PASSWORD_RESET_DISABLED=1 # Email login. Disable the ability for users to login with email. # EMAIL_AUTH_DISABLED=1 -# Team Invite. Disable the ability for invited users to create an account. +# Organization Invite. Disable the ability for invited users to create an account. # INVITE_DISABLED=1 ########## @@ -154,11 +154,11 @@ SLACK_CLIENT_SECRET= # Enterprise License Key ENTERPRISE_LICENSE_KEY= -# Automatically assign new users to a specific team and role within that team -# Insert an existing team id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn) +# Automatically assign new users to a specific organization and role within that organization +# Insert an existing organization id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn) # (Role Management is an Enterprise feature) -# DEFAULT_TEAM_ID= -# DEFAULT_TEAM_ROLE=admin +# DEFAULT_ORGANIZATION_ID= +# DEFAULT_ORGANIZATION_ROLE=admin # set to 1 to skip onboarding for new users # ONBOARDING_DISABLED=1 diff --git a/.github/workflows/kamal-deploy.yml b/.github/workflows/kamal-deploy.yml new file mode 100644 index 0000000000..6ac078e1e4 --- /dev/null +++ b/.github/workflows/kamal-deploy.yml @@ -0,0 +1,131 @@ +name: Kamal Deploy +concurrency: + group: deploy-to-kamal + cancel-in-progress: false + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + Deploy: + runs-on: ubuntu-latest + environment: production + env: + DOCKER_BUILDKIT: 1 + IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }} + WEBAPP_URL: ${{ vars.WEBAPP_URL }} + MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }} + NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} + ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} + SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }} + MAIL_FROM: ${{ secrets.MAIL_FROM }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SMTP_PORT: ${{ secrets.SMTP_PORT }} + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + PRIVACY_URL: ${{ vars.PRIVACY_URL }} + TERMS_URL: ${{ vars.TERMS_URL }} + IMPRINT_URL: ${{ vars.IMPRINT_URL }} + GITHUB_ID: ${{ secrets.FB_GITHUB_ID }} + GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }} + AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }} + AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }} + OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} + OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }} + OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }} + OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }} + OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }} + CRON_SECRET: ${{ secrets.CRON_SECRET }} + ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }} + NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }} + NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }} + SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }} + SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }} + STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} + STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} + GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }} + GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }} + GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }} + AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} + ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} + DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} + ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }} + CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} + CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} + NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} + NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }} + NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }} + NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + NODE_ENV: production + CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} + CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + S3_REGION: ${{ vars.S3_REGION }} + S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} + OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }} + RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} + KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_NAME }} + REDIS_URL: ${{ secrets.REDIS_URL }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + + - name: Install dependencies + run: | + gem install kamal + + - uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Create builder + run: docker buildx create --use --name formbricks-gh-actions-builder + if: steps.buildx.outputs.should_create_builder == 'true' + + - name: Push env variables to Kamal + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + kamal env push + + - name: Run deploy command + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + set +e + DEPLOY_OUTPUT=$(kamal deploy 2>&1) + DEPLOY_EXIT_CODE=$? + echo "$DEPLOY_OUTPUT" + if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then + echo "Deployment reported healthy container. Considering as success." + kamal lock release + exit 0 + else + exit $DEPLOY_EXIT_CODE + fi + shell: bash diff --git a/.github/workflows/kamal-setup.yml b/.github/workflows/kamal-setup.yml new file mode 100644 index 0000000000..5dc3d81ea8 --- /dev/null +++ b/.github/workflows/kamal-setup.yml @@ -0,0 +1,128 @@ +name: Kamal Setup +concurrency: + group: setup-kamal + cancel-in-progress: false + +on: + workflow_dispatch: # Only to be triggered when accessories are updated + +jobs: + Setup: + runs-on: ubuntu-latest + environment: production + env: + DOCKER_BUILDKIT: 1 + IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }} + WEBAPP_URL: ${{ vars.WEBAPP_URL }} + NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} + ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} + SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }} + MAIL_FROM: ${{ secrets.MAIL_FROM }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SMTP_PORT: ${{ secrets.SMTP_PORT }} + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + PRIVACY_URL: ${{ vars.PRIVACY_URL }} + TERMS_URL: ${{ vars.TERMS_URL }} + IMPRINT_URL: ${{ vars.IMPRINT_URL }} + GITHUB_ID: ${{ secrets.FB_GITHUB_ID }} + GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }} + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }} + AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }} + AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }} + OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} + OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }} + OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }} + OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }} + OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }} + CRON_SECRET: ${{ secrets.CRON_SECRET }} + ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }} + NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }} + NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }} + SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }} + SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }} + STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} + STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} + GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }} + GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }} + GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }} + AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} + ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} + DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} + ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }} + CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} + CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} + NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} + NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }} + NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }} + NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }} + NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + NODE_ENV: production + CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} + CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + S3_REGION: ${{ vars.S3_REGION }} + S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} + OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }} + RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} + KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_NAME }} + REDIS_URL: ${{ secrets.REDIS_URL }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + + - name: Install dependencies + run: | + gem install kamal + + - uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Create builder + run: docker buildx create --use --name formbricks-gh-actions-builder + if: steps.buildx.outputs.should_create_builder == 'true' + + - name: Push env variables to Kamal + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + kamal env push + + - name: Run setup command + run: | + kamal() { command kamal "$@" -c kamal/deploy.yml; } + set +e + DEPLOY_OUTPUT=$(kamal setup 2>&1) + DEPLOY_EXIT_CODE=$? + echo "$DEPLOY_OUTPUT" + if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then + echo "Deployment reported healthy container. Considering as success." + kamal lock release + exit 0 + else + exit $DEPLOY_EXIT_CODE + fi + shell: bash diff --git a/README.md b/README.md index 0dca1381d7..7cc37fb178 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Formbricks is both a free and open source survey platform - and a privacy-first - πŸ”— Create shareable **link surveys**. -- πŸ‘¨β€πŸ‘©β€πŸ‘¦ Invite your team members to **collaborate** on your surveys. +- πŸ‘¨β€πŸ‘©β€πŸ‘¦ Invite your organization members to **collaborate** on your surveys. - πŸ”Œ Integrate Formbricks with **Slack, Notion, Zapier, n8n and more**. diff --git a/apps/docs/app/app-surveys/actions/page.mdx b/apps/docs/app/app-surveys/actions/page.mdx index 41670b5e69..5cad62ed80 100644 --- a/apps/docs/app/app-surveys/actions/page.mdx +++ b/apps/docs/app/app-surveys/actions/page.mdx @@ -6,7 +6,7 @@ import I2 from "./images/I2.webp"; export const metadata = { title: "Using Actions in Formbricks | Fine-tuning User Moments", description: - "Dive deep into how actions in Formbricks help products and teams to engage users at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine user targeting and generate richer, more detailed user insights.", + "Dive deep into how actions in Formbricks help products and organizations to engage users at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine user targeting and generate richer, more detailed user insights.", }; #### App Surveys diff --git a/apps/docs/app/best-practices/pmf-survey/page.mdx b/apps/docs/app/best-practices/pmf-survey/page.mdx index fe45efe6fb..977fc17393 100644 --- a/apps/docs/app/best-practices/pmf-survey/page.mdx +++ b/apps/docs/app/best-practices/pmf-survey/page.mdx @@ -85,7 +85,7 @@ To run this survey properly, you should pre-segment your user base. As touched u - Check the time passed since sign-up (e.g. signed up 4 weeks ago) - User has performed a specific action a certain number of times or (e.g. created 5 reports) -- User has performed a combination of actions (e.g. created a report **and** invited a team member) +- User has performed a combination of actions (e.g. created a report **and** invited a organization member) This way you make sure that you separate potentially misleading opinions from valuable insights. diff --git a/apps/docs/app/self-hosting/configuration/page.mdx b/apps/docs/app/self-hosting/configuration/page.mdx index 7a67046f51..46944693b1 100644 --- a/apps/docs/app/self-hosting/configuration/page.mdx +++ b/apps/docs/app/self-hosting/configuration/page.mdx @@ -51,8 +51,8 @@ These variables are present inside your machine’s docker-compose file. Restart | INSTANCE_ID | Instance ID for Formbricks Cloud to be sent to Telemetry. | optional | | | INTERNAL_SECRET | Internal Secret (Currently we overwrite the value with a random value). | optional | | | DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | #64748b | -| DEFAULT_TEAM_ID | Automatically assign new users to a specific team when joining | optional | | -| DEFAULT_TEAM_ROLE | Role of the user in the default team. | optional | admin | +| DEFAULT_ORGANIZATION_ID | Automatically assign new users to a specific organization when joining | optional | | +| DEFAULT_ORGANIZATION_ROLE | Role of the user in the default organization. | optional | admin | | ONBOARDING_DISABLED | Disables onboarding for new users if set toΒ 1 | optional | | | OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | | | OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | | diff --git a/apps/docs/app/self-hosting/license/page.mdx b/apps/docs/app/self-hosting/license/page.mdx index b0177e1b7e..96d84f1cee 100644 --- a/apps/docs/app/self-hosting/license/page.mdx +++ b/apps/docs/app/self-hosting/license/page.mdx @@ -12,7 +12,9 @@ export const metadata = { The Formbricks Core source code is licensed under AGPLv3 and available on GitHub. Additionally, we offer features for bigger organisations & enterprises for self-hostesr under a separate Enterprise License. -Want to present a proof of concept? Request a free 30-day Enterprise Edition trial by [filling out the form below.](#30-day-trial-license-request) No call needed or strings attached: Just give us 24h to set up the key and send it over πŸ€™ + Want to present a proof of concept? Request a free 30-day Enterprise Edition trial by [filling out the form + below.](#30-day-trial-license-request) No call needed or strings attached: Just give us 24h to set up the + key and send it over πŸ€™ ## Enterprise Edition License @@ -21,23 +23,24 @@ Additional to the AGPLv3 licensed Formbricks core, the Formbricks repository con ### When do I need an Enterprise License? -| | Community Edition | Enterprise License | -| -------------------------------------------------------- | ----------------- | -------------------- | -| Self-host for commercial purposes | βœ… | No EE license needed | +| | Community Edition | Enterprise License | +| ----------------------------------------------------------- | ----------------- | -------------------- | +| Self-host for commercial purposes | βœ… | No EE license needed | | Make changes to the code base (have to publish all changes) | βœ… | No EE license needed | -| Unlimited responses | βœ… | No EE license needed | -| Unlimited surveys | βœ… | No EE license needed | -| Remove branding | βœ… | No EE license needed | -| SSO | βœ… | No EE license needed | -| Use any of the other 100 features | βœ… | No EE license needed | -| Team access roles | ❌ | βœ… | -| Multi-language surveys | ❌ | βœ… | -| Advanced targeting / Segments | ❌ | βœ… | -| Make code changes and **keep private** | ❌ | βœ… | +| Unlimited responses | βœ… | No EE license needed | +| Unlimited surveys | βœ… | No EE license needed | +| Remove branding | βœ… | No EE license needed | +| SSO | βœ… | No EE license needed | +| Use any of the other 100 features | βœ… | No EE license needed | +| Organization access roles | ❌ | βœ… | +| Multi-language surveys | ❌ | βœ… | +| Advanced targeting / Segments | ❌ | βœ… | +| Make code changes and **keep private** | ❌ | βœ… | Ready to get started with the Enterprise Edition? Fill out our form below and we'll reach out to you. ## 30-day Trial License Request + Many organisations want to do an internal test run with the Enterprise Edition. To make that really easy, we now offer a 30-day trial license. Just fill out the form below and we'll send you a license key within 24 hours (business days):
{ throw new AuthorizationError("Not authorized"); } - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(params.environmentId); + if (!organization) { + throw new Error("Organization not found"); } const environment = await getEnvironment(params.environmentId); @@ -39,11 +39,11 @@ const EnvLayout = async ({ children, params }) => { diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx index 48cc213c21..e18e717a14 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx @@ -6,7 +6,7 @@ import { toast } from "react-hot-toast"; import { extractLanguageCodes } from "@formbricks/lib/i18n/utils"; import { createI18nString } from "@formbricks/lib/i18n/utils"; -import { useGetBillingInfo } from "@formbricks/lib/team/hooks/useGetBillingInfo"; +import { useGetBillingInfo } from "@formbricks/lib/organization/hooks/useGetBillingInfo"; import { TAllowedFileExtension, ZAllowedFileExtension } from "@formbricks/types/common"; import { TProduct } from "@formbricks/types/product"; import { TSurvey, TSurveyFileUploadQuestion } from "@formbricks/types/surveys"; @@ -43,7 +43,7 @@ export const FileUploadQuestionForm = ({ billingInfo, error: billingInfoError, isLoading: billingInfoLoading, - } = useGetBillingInfo(product?.teamId ?? ""); + } = useGetBillingInfo(product?.organizationId ?? ""); const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages); const handleInputChange = (event) => { diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx index b5ceb3c7c8..8f8917c725 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx @@ -6,13 +6,13 @@ import { getAttributeClasses } from "@formbricks/lib/attributeClass/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { IS_FORMBRICKS_CLOUD, SURVEY_BG_COLORS, UNSPLASH_ACCESS_KEY } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSegments } from "@formbricks/lib/segment/service"; import { getSurvey } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { SurveyEditor } from "./components/SurveyEditor"; @@ -32,7 +32,7 @@ const Page = async ({ params }) => { actionClasses, attributeClasses, responseCount, - team, + organization, session, segments, ] = await Promise.all([ @@ -42,7 +42,7 @@ const Page = async ({ params }) => { getActionClasses(params.environmentId), getAttributeClasses(params.environmentId), getResponseCountBySurveyId(params.surveyId), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), getServerSession(authOptions), getSegments(params.environmentId), ]); @@ -51,16 +51,16 @@ const Page = async ({ params }) => { throw new Error("Session not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); const isSurveyCreationDeletionDisabled = isViewer; - const isUserTargetingAllowed = await getAdvancedTargetingPermission(team); - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isUserTargetingAllowed = await getAdvancedTargetingPermission(organization); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); if ( !survey || diff --git a/apps/web/app/(app)/create-first-team/components/CreateFirstTeam.tsx b/apps/web/app/(app)/create-first-organization/components/CreateFirstOrganization.tsx similarity index 65% rename from apps/web/app/(app)/create-first-team/components/CreateFirstTeam.tsx rename to apps/web/app/(app)/create-first-organization/components/CreateFirstOrganization.tsx index 7baab4696f..0553c6c913 100644 --- a/apps/web/app/(app)/create-first-team/components/CreateFirstTeam.tsx +++ b/apps/web/app/(app)/create-first-organization/components/CreateFirstOrganization.tsx @@ -1,6 +1,6 @@ "use client"; -import { createTeamAction } from "@/app/(app)/environments/[environmentId]/actions"; +import { createOrganizationAction } from "@/app/(app)/environments/[environmentId]/actions"; import FormbricksLogo from "@/images/logo.svg"; import Image from "next/image"; import { useRouter } from "next/navigation"; @@ -15,28 +15,28 @@ type FormValues = { name: string; }; -export const CreateFirstTeam = () => { +export const CreateFirstOrganization = () => { const router = useRouter(); const { register, handleSubmit } = useForm(); const [loading, setLoading] = useState(false); - const [teamName, setTeamName] = useState(""); - const isTeamNameValid = teamName.trim() !== ""; + const [organizationName, setOrganizationName] = useState(""); + const isOrganizationNameValid = organizationName.trim() !== ""; - const onCreateTeam = async (data: FormValues) => { + const onCreateOrganization = async (data: FormValues) => { data.name = data.name.trim(); if (!data.name) return; try { setLoading(true); - const newTeam = await createTeamAction(data.name); + const newOrganization = await createOrganizationAction(data.name); - toast.success("Team created successfully!"); - router.push(`/teams/${newTeam.id}`); + toast.success("Organization created successfully!"); + router.push(`/organizations/${newOrganization.id}`); } catch (error) { console.error(error); - toast.error(`Unable to create team`); + toast.error(`Unable to create organization`); } finally { setLoading(false); } @@ -49,22 +49,22 @@ export const CreateFirstTeam = () => {

Formbricks

-
+

- Let's create a team πŸ‘‡ + Let's create an organization πŸ‘‡

- We couldn't find a team for you. Please create one + We couldn't find an organization for you. Please create one

setTeamName(e.target.value)} + value={organizationName} + onChange={(e) => setOrganizationName(e.target.value)} />
@@ -75,8 +75,8 @@ export const CreateFirstTeam = () => { variant="darkCTA" type="submit" loading={loading} - disabled={!isTeamNameValid}> - Create team + disabled={!isOrganizationNameValid}> + Create organization
diff --git a/apps/web/app/(app)/create-first-team/page.tsx b/apps/web/app/(app)/create-first-organization/page.tsx similarity index 66% rename from apps/web/app/(app)/create-first-team/page.tsx rename to apps/web/app/(app)/create-first-organization/page.tsx index 24e324c996..550e213092 100644 --- a/apps/web/app/(app)/create-first-team/page.tsx +++ b/apps/web/app/(app)/create-first-organization/page.tsx @@ -1,4 +1,4 @@ -import { CreateFirstTeam } from "@/app/(app)/create-first-team/components/CreateFirstTeam"; +import { CreateFirstOrganization } from "@/app/(app)/create-first-organization/components/CreateFirstOrganization"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; @@ -11,7 +11,7 @@ const Page = async () => { redirect("/auth/login"); } - return ; + return ; }; export default Page; diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/components/AttributeSettingsTab.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/components/AttributeSettingsTab.tsx index 61a418000e..cc20e92eea 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/components/AttributeSettingsTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/components/AttributeSettingsTab.tsx @@ -45,7 +45,7 @@ export const AttributeSettingsTab = async ({ attributeClass, setOpen }: Attribut { - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } // On Formbricks Cloud only render the timeline if the user targeting feature is booked const isUserTargetingEnabled = IS_FORMBRICKS_CLOUD - ? team.billing.features.userTargeting.status === "active" + ? organization.billing.features.userTargeting.status === "active" : true; const [environment, actions] = await Promise.all([ diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/page.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/page.tsx index 3edee51a56..838fb331fc 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/page.tsx @@ -7,26 +7,27 @@ import { getServerSession } from "next-auth"; import { getAttributes } from "@formbricks/lib/attribute/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getPerson } from "@formbricks/lib/person/service"; import { getPersonIdentifier } from "@formbricks/lib/person/utils"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; const Page = async ({ params }) => { - const [environment, environmentTags, product, session, team, person, attributes] = await Promise.all([ - getEnvironment(params.environmentId), - getTagsByEnvironmentId(params.environmentId), - getProductByEnvironmentId(params.environmentId), - getServerSession(authOptions), - getTeamByEnvironmentId(params.environmentId), - getPerson(params.personId), - getAttributes(params.personId), - ]); + const [environment, environmentTags, product, session, organization, person, attributes] = + await Promise.all([ + getEnvironment(params.environmentId), + getTagsByEnvironmentId(params.environmentId), + getProductByEnvironmentId(params.environmentId), + getServerSession(authOptions), + getOrganizationByEnvironmentId(params.environmentId), + getPerson(params.personId), + getAttributes(params.personId), + ]); if (!product) { throw new Error("Product not found"); @@ -40,15 +41,15 @@ const Page = async ({ params }) => { throw new Error("Session not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!person) { throw new Error("Person not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); const getDeletePersonButton = () => { diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx index a6631ab3a8..09ef8b2bfc 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/page.tsx @@ -9,29 +9,29 @@ import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { getAttributeClasses } from "@formbricks/lib/attributeClass/service"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getSegments } from "@formbricks/lib/segment/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; const Page = async ({ params }) => { - const [environment, segments, attributeClasses, actionClassesFromServer, team] = await Promise.all([ + const [environment, segments, attributeClasses, actionClassesFromServer, organization] = await Promise.all([ getEnvironment(params.environmentId), getSegments(params.environmentId), getAttributeClasses(params.environmentId), getActionClasses(params.environmentId), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), ]); if (!environment) { throw new Error("Environment not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const isAdvancedTargetingAllowed = await getAdvancedTargetingPermission(team); + const isAdvancedTargetingAllowed = await getAdvancedTargetingPermission(organization); if (!segments) { throw new Error("Failed to fetch segments"); diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts index bcb7461fe7..f5189ddb18 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts @@ -1,15 +1,15 @@ "use server"; -import { Team } from "@prisma/client"; +import { Organization } from "@prisma/client"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { SHORT_URL_BASE, WEBAPP_URL } from "@formbricks/lib/constants"; import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; import { createMembership } from "@formbricks/lib/membership/service"; +import { createOrganization, getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { createProduct } from "@formbricks/lib/product/service"; import { createShortUrl } from "@formbricks/lib/shortUrl/service"; -import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { updateUser } from "@formbricks/lib/user/service"; import { AuthenticationError, AuthorizationError, ResourceNotFoundError } from "@formbricks/types/errors"; @@ -27,20 +27,20 @@ export const createShortUrlAction = async (url: string) => { return fullShortUrl; }; -export const createTeamAction = async (teamName: string): Promise => { +export const createOrganizationAction = async (organizationName: string): Promise => { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const newTeam = await createTeam({ - name: teamName, + const newOrganization = await createOrganization({ + name: organizationName, }); - await createMembership(newTeam.id, session.user.id, { + await createMembership(newOrganization.id, session.user.id, { role: "owner", accepted: true, }); - const product = await createProduct(newTeam.id, { + const product = await createProduct(newOrganization.id, { name: "My Product", }); @@ -59,7 +59,7 @@ export const createTeamAction = async (teamName: string): Promise => { notificationSettings: updatedNotificationSettings, }); - return newTeam; + return newOrganization; }; export const createProductAction = async (environmentId: string, productName: string) => { @@ -69,10 +69,10 @@ export const createProductAction = async (environmentId: string, productName: st const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId); if (!isAuthorized) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); - if (!team) throw new ResourceNotFoundError("Team from environment", environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); + if (!organization) throw new ResourceNotFoundError("Organization from environment", environmentId); - const product = await createProduct(team.id, { + const product = await createProduct(organization.id, { name: productName, }); const updatedNotificationSettings = { diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts index 302ae1ba83..d135f4ee01 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts @@ -11,8 +11,8 @@ import { canUserUpdateActionClass, verifyUserRoleAccess } from "@formbricks/lib/ import { createActionClass, deleteActionClass, updateActionClass } from "@formbricks/lib/actionClass/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getSurveysByActionClassId } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { TActionClassInput } from "@formbricks/types/actionClasses"; import { AuthorizationError } from "@formbricks/types/errors"; @@ -20,10 +20,10 @@ export const deleteActionClassAction = async (environmentId, actionClassId: stri const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); @@ -43,10 +43,10 @@ export const updateActionClassAction = async ( const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); @@ -72,10 +72,10 @@ export const getActionCountInLastHourAction = async (actionClassId: string, envi const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); @@ -88,10 +88,10 @@ export const getActionCountInLast24HoursAction = async (actionClassId: string, e const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); @@ -104,10 +104,10 @@ export const getActionCountInLast7DaysAction = async (actionClassId: string, env const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); @@ -123,10 +123,10 @@ export const getActiveInactiveSurveysAction = async ( const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const isAuthorized = await canUserUpdateActionClass(session.user.id, actionClassId); diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx b/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx index ff62c211e8..aa394f8298 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx @@ -6,7 +6,7 @@ import { Metadata } from "next"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -15,18 +15,18 @@ export const metadata: Metadata = { }; const Page = async ({ params }) => { - const [actionClasses, team] = await Promise.all([ + const [actionClasses, organization] = await Promise.all([ getActionClasses(params.environmentId), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), ]); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } // On Formbricks Cloud only render the timeline if the user targeting feature is booked const isUserTargetingEnabled = IS_FORMBRICKS_CLOUD - ? team.billing.features.userTargeting.status === "active" + ? organization.billing.features.userTargeting.status === "active" : true; const renderAddActionButton = () => ( diff --git a/apps/web/app/(app)/environments/[environmentId]/components/AddProductModal.tsx b/apps/web/app/(app)/environments/[environmentId]/components/AddProductModal.tsx index ccc05e4c41..683b58c9b7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/AddProductModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/AddProductModal.tsx @@ -55,7 +55,7 @@ export const AddProductModal = ({ environmentId, open, setOpen }: AddProductModa
Add Product
-
Create a new product for your team.
+
Create a new product for your organization.
diff --git a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx index 388fa4f940..cecc0a95d7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/EnvironmentLayout.tsx @@ -4,9 +4,12 @@ import type { Session } from "next-auth"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; +import { + getOrganizationByEnvironmentId, + getOrganizationsByUserId, +} from "@formbricks/lib/organization/service"; import { getProducts } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId, getTeamsByUserId } from "@formbricks/lib/team/service"; import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; @@ -17,25 +20,25 @@ interface EnvironmentLayoutProps { } export const EnvironmentLayout = async ({ environmentId, session, children }: EnvironmentLayoutProps) => { - const [environment, teams, team] = await Promise.all([ + const [environment, organizations, organization] = await Promise.all([ getEnvironment(environmentId), - getTeamsByUserId(session.user.id), - getTeamByEnvironmentId(environmentId), + getOrganizationsByUserId(session.user.id), + getOrganizationByEnvironmentId(environmentId), ]); - if (!team || !environment) { + if (!organization || !environment) { return ; } const [products, environments] = await Promise.all([ - getProducts(team.id), + getProducts(organization.id), getEnvironments(environment.productId), ]); - if (!products || !environments || !teams) { + if (!products || !environments || !organizations) { return ; } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); return (
@@ -43,8 +46,8 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
{ - if (team && team.name !== "") { - setCurrentTeamName(team.name); - setCurrentTeamId(team.id); + if (organization && organization.name !== "") { + setCurrentOrganizationName(organization.name); + setCurrentOrganizationId(organization.id); } - }, [team]); + }, [organization]); - const sortedTeams = useMemo(() => { - return [...teams].sort((a, b) => a.name.localeCompare(b.name)); - }, [teams]); + const sortedOrganizations = useMemo(() => { + return [...organizations].sort((a, b) => a.name.localeCompare(b.name)); + }, [organizations]); const sortedProducts = useMemo(() => { return [...products].sort((a, b) => a.name.localeCompare(b.name)); @@ -123,8 +123,8 @@ export const MainNavigation = ({ router.push(`/products/${productId}/`); }; - const handleEnvironmentChangeByTeam = (teamId: string) => { - router.push(`/teams/${teamId}/`); + const handleEnvironmentChangeByOrganization = (organizationId: string) => { + router.push(`/organizations/${organizationId}/`); }; const mainNavigation = useMemo( @@ -177,7 +177,7 @@ export const MainNavigation = ({ icon: UserCircleIcon, }, { - label: "Team", + label: "Organization", href: `/environments/${environment.id}/settings/members`, icon: UsersIcon, }, @@ -363,7 +363,9 @@ export const MainNavigation = ({ {truncate(session?.user?.email, 30)} )}

-

{capitalizeFirstLetter(team?.name)}

+

+ {capitalizeFirstLetter(organization?.name)} +

@@ -410,13 +412,13 @@ export const MainNavigation = ({ Logout - {/* Team Switch */} + {/* Organization Switch */}
-

{currentTeamName}

-

Switch team

+

{currentOrganizationName}

+

Switch organization

@@ -425,21 +427,25 @@ export const MainNavigation = ({ sideOffset={10} alignOffset={5}> handleEnvironmentChangeByTeam(teamId)}> - {sortedTeams.map((team) => ( + value={currentOrganizationId} + onValueChange={(organizationId) => + handleEnvironmentChangeByOrganization(organizationId) + }> + {sortedOrganizations.map((organization) => ( - {team.name} + key={organization.id}> + {organization.name} ))} - setShowCreateTeamModal(true)} className="rounded-lg"> + setShowCreateOrganizationModal(true)} + className="rounded-lg"> - Create new team + Create new organization @@ -450,7 +456,10 @@ export const MainNavigation = ({
)} - setShowCreateTeamModal(val)} /> + setShowCreateOrganizationModal(val)} + /> setShowAddProductModal(val)} diff --git a/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.tsx b/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.tsx index 8f53f224d0..6387b2c9f2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/components/PosthogIdentify.tsx @@ -5,15 +5,15 @@ import { usePostHog } from "posthog-js/react"; import { useEffect } from "react"; import { env } from "@formbricks/lib/env"; -import { TSubscriptionStatus } from "@formbricks/types/teams"; +import { TSubscriptionStatus } from "@formbricks/types/organizations"; const posthogEnabled = env.NEXT_PUBLIC_POSTHOG_API_KEY && env.NEXT_PUBLIC_POSTHOG_API_HOST; interface PosthogIdentifyProps { session: Session; environmentId?: string; - teamId?: string; - teamName?: string; + organizationId?: string; + organizationName?: string; inAppSurveyBillingStatus?: TSubscriptionStatus; linkSurveyBillingStatus?: TSubscriptionStatus; userTargetingBillingStatus?: TSubscriptionStatus; @@ -22,8 +22,8 @@ interface PosthogIdentifyProps { export const PosthogIdentify = ({ session, environmentId, - teamId, - teamName, + organizationId, + organizationName, inAppSurveyBillingStatus, linkSurveyBillingStatus, userTargetingBillingStatus, @@ -41,9 +41,9 @@ export const PosthogIdentify = ({ if (environmentId) { posthog.group("environment", environmentId, { name: environmentId }); } - if (teamId) { - posthog.group("team", teamId, { - name: teamName, + if (organizationId) { + posthog.group("organization", organizationId, { + name: organizationName, inAppSurveyBillingStatus, linkSurveyBillingStatus, userTargetingBillingStatus, @@ -54,8 +54,8 @@ export const PosthogIdentify = ({ posthog, session.user, environmentId, - teamId, - teamName, + organizationId, + organizationName, inAppSurveyBillingStatus, linkSurveyBillingStatus, userTargetingBillingStatus, diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index 054c496f7d..49e646d6eb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -13,9 +13,9 @@ import Image from "next/image"; import { authOptions } from "@formbricks/lib/authOptions"; import { getEnvironment } from "@formbricks/lib/environment/service"; import { getIntegrations } from "@formbricks/lib/integration/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getWebhookCountBySource } from "@formbricks/lib/webhook/service"; import { TIntegrationType } from "@formbricks/types/integration"; import { Card } from "@formbricks/ui/Card"; @@ -29,7 +29,7 @@ const Page = async ({ params }) => { const [ environment, integrations, - team, + organization, session, userWebhookCount, zapierWebhookCount, @@ -38,7 +38,7 @@ const Page = async ({ params }) => { ] = await Promise.all([ getEnvironment(environmentId), getIntegrations(environmentId), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), getServerSession(authOptions), getWebhookCountBySource(environmentId, "user"), getWebhookCountBySource(environmentId, "zapier"), @@ -52,11 +52,11 @@ const Page = async ({ params }) => { throw new Error("Session not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); const isGoogleSheetsIntegrationConnected = isIntegrationConnected("googleSheets"); diff --git a/apps/web/app/(app)/environments/[environmentId]/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/layout.tsx index 3727d35ee1..78e5cce4bc 100644 --- a/apps/web/app/(app)/environments/[environmentId]/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/layout.tsx @@ -5,7 +5,7 @@ import { redirect } from "next/navigation"; import { authOptions } from "@formbricks/lib/authOptions"; import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { AuthorizationError } from "@formbricks/types/errors"; import { ToasterClient } from "@formbricks/ui/ToasterClient"; @@ -22,9 +22,9 @@ const EnvLayout = async ({ children, params }) => { throw new AuthorizationError("Not authorized"); } - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(params.environmentId); + if (!organization) { + throw new Error("Organization not found"); } return ( @@ -33,11 +33,11 @@ const EnvLayout = async ({ children, params }) => { diff --git a/apps/web/app/(app)/environments/[environmentId]/product/api-keys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/api-keys/page.tsx index c30611755a..a50538b6e6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/api-keys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/api-keys/page.tsx @@ -4,9 +4,9 @@ import { getServerSession } from "next-auth"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { EnvironmentNotice } from "@formbricks/ui/EnvironmentNotice"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; @@ -17,22 +17,22 @@ import { ApiKeyList } from "./components/ApiKeyList"; const Page = async ({ params }) => { const environment = await getEnvironment(params.environmentId); - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); const session = await getServerSession(authOptions); if (!environment) { throw new Error("Environment not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!session) { throw new Error("Unauthenticated"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); return !isViewer ? ( diff --git a/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts index 9538d75fff..d4eebf96e9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts @@ -5,9 +5,9 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { deleteProduct, getProducts, updateProduct } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { TEnvironment } from "@formbricks/types/environment"; import { AuthenticationError, AuthorizationError, ResourceNotFoundError } from "@formbricks/types/errors"; import { TProduct, TProductUpdateInput } from "@formbricks/types/product"; @@ -40,8 +40,10 @@ export const updateProductAction = async ( throw new AuthorizationError("Not authorized"); } - const team = await getTeamByEnvironmentId(environmentId); - const membership = team ? await getMembershipByUserIdTeamId(session.user.id, team.id) : null; + const organization = await getOrganizationByEnvironmentId(environmentId); + const membership = organization + ? await getMembershipByUserIdOrganizationId(session.user.id, organization.id) + : null; if (!membership) { throw new AuthorizationError("Not authorized"); @@ -52,7 +54,7 @@ export const updateProductAction = async ( } if (membership.role === "developer") { - if (!!data.name || !!data.brandColor || !!data.teamId || !!data.environments) { + if (!!data.name || !!data.brandColor || !!data.organizationId || !!data.environments) { throw new AuthorizationError("Not authorized"); } } @@ -85,14 +87,14 @@ export const deleteProductAction = async (environmentId: string, userId: string, throw new AuthorizationError("Not authorized"); } - const team = await getTeamByEnvironmentId(environmentId); - const membership = team ? await getMembershipByUserIdTeamId(userId, team.id) : null; + const organization = await getOrganizationByEnvironmentId(environmentId); + const membership = organization ? await getMembershipByUserIdOrganizationId(userId, organization.id) : null; if (membership?.role !== "admin" && membership?.role !== "owner") { throw new AuthorizationError("You are not allowed to delete products."); } - const availableProducts = team ? await getProducts(team.id) : null; + const availableProducts = organization ? await getProducts(organization.id) : null; if (!!availableProducts && availableProducts?.length <= 1) { throw new Error("You can't delete the last product in the environment."); diff --git a/apps/web/app/(app)/environments/[environmentId]/product/general/components/DeleteProduct.tsx b/apps/web/app/(app)/environments/[environmentId]/product/general/components/DeleteProduct.tsx index 00087c3e56..fabb8f2e79 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/general/components/DeleteProduct.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/general/components/DeleteProduct.tsx @@ -2,9 +2,9 @@ import { DeleteProductRender } from "@/app/(app)/environments/[environmentId]/pr import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProducts } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { TProduct } from "@formbricks/types/product"; type DeleteProductProps = { @@ -17,13 +17,13 @@ export const DeleteProduct = async ({ environmentId, product }: DeleteProductPro if (!session) { throw new Error("Session not found"); } - const team = await getTeamByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(environmentId); + if (!organization) { + throw new Error("Organization not found"); } - const availableProducts = team ? await getProducts(team.id) : null; + const availableProducts = organization ? await getProducts(organization.id) : null; - const membership = await getMembershipByUserIdTeamId(session.user.id, team.id); + const membership = await getMembershipByUserIdOrganizationId(session.user.id, organization.id); if (!membership) { throw new Error("Membership not found"); } diff --git a/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx index 889fdbd2e5..d23e0d9467 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx @@ -3,11 +3,10 @@ import { getServerSession } from "next-auth"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; -import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -19,11 +18,10 @@ import { EditProductNameForm } from "./components/EditProductNameForm"; import { EditWaitingTimeForm } from "./components/EditWaitingTimeForm"; const Page = async ({ params }: { params: { environmentId: string } }) => { - const [, product, session, team] = await Promise.all([ - getEnvironment(params.environmentId), + const [product, session, organization] = await Promise.all([ getProductByEnvironmentId(params.environmentId), getServerSession(authOptions), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), ]); if (!product) { @@ -32,11 +30,11 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { if (!session) { throw new Error("Unauthorized"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isDeveloper, isViewer } = getAccessFlags(currentUserMembership?.role); const isProductNameEditDisabled = isDeveloper ? true : isViewer; @@ -44,7 +42,7 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { return ; } - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); return ( diff --git a/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx index 657aa0ed42..8e3dbdaf0f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx @@ -4,8 +4,8 @@ import { notFound } from "next/navigation"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { EditLanguage } from "@formbricks/ee/multiLanguage/components/EditLanguage"; +import { getOrganization } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeam } from "@formbricks/lib/team/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -16,13 +16,13 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { throw new Error("Product not found"); } - const team = await getTeam(product?.teamId); + const organization = await getOrganization(product?.organizationId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); if (!isMultiLanguageAllowed) { notFound(); diff --git a/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx index 371a3617ff..1548d106f6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx @@ -2,22 +2,22 @@ import { Metadata } from "next"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; export const metadata: Metadata = { title: "Config", }; const ConfigLayout = async ({ children, params }) => { - const [team, product, session] = await Promise.all([ - getTeamByEnvironmentId(params.environmentId), + const [organization, product, session] = await Promise.all([ + getOrganizationByEnvironmentId(params.environmentId), getProductByEnvironmentId(params.environmentId), getServerSession(authOptions), ]); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!product) { diff --git a/apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts index 696faf2df2..9efa8c9d87 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts @@ -17,7 +17,7 @@ export const updateProductAction = async (productId: string, inputProduct: TProd const product = await getProduct(productId); - const { hasCreateOrUpdateAccess } = await verifyUserRoleAccess(product!.teamId, session.user.id); + const { hasCreateOrUpdateAccess } = await verifyUserRoleAccess(product!.organizationId, session.user.id); if (!hasCreateOrUpdateAccess) throw new AuthorizationError("Not authorized"); return await updateProduct(productId, inputProduct); diff --git a/apps/web/app/(app)/environments/[environmentId]/product/look/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/look/page.tsx index ce39be5c1f..9fca1d9d02 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/look/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/page.tsx @@ -9,10 +9,10 @@ import { } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { SURVEY_BG_COLORS, UNSPLASH_ACCESS_KEY } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -23,9 +23,9 @@ import { EditPlacementForm } from "./components/EditPlacementForm"; import { ThemeStyling } from "./components/ThemeStyling"; const Page = async ({ params }: { params: { environmentId: string } }) => { - const [session, team, product] = await Promise.all([ + const [session, organization, product] = await Promise.all([ getServerSession(authOptions), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), getProductByEnvironmentId(params.environmentId), ]); @@ -35,21 +35,21 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { if (!session) { throw new Error("Unauthorized"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const canRemoveInAppBranding = getRemoveInAppBrandingPermission(team); - const canRemoveLinkBranding = getRemoveLinkBrandingPermission(team); + const canRemoveInAppBranding = getRemoveInAppBrandingPermission(organization); + const canRemoveLinkBranding = getRemoveLinkBrandingPermission(organization); - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); if (isViewer) { return ; } - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); return ( diff --git a/apps/web/app/(app)/environments/[environmentId]/product/setup/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/setup/page.tsx index 81690fb2c8..45b016c3b2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/setup/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/setup/page.tsx @@ -4,7 +4,7 @@ import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { EnvironmentNotice } from "@formbricks/ui/EnvironmentNotice"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -14,20 +14,20 @@ import { EnvironmentIdField } from "./components/EnvironmentIdField"; import { SetupInstructions } from "./components/SetupInstructions"; const Page = async ({ params }) => { - const [environment, team] = await Promise.all([ + const [environment, organization] = await Promise.all([ getEnvironment(params.environmentId), - getTeamByEnvironmentId(params.environmentId), + getOrganizationByEnvironmentId(params.environmentId), ]); if (!environment) { throw new Error("Environment not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); return ( diff --git a/apps/web/app/(app)/environments/[environmentId]/product/tags/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/tags/page.tsx index ae247d939a..696ce54c80 100644 --- a/apps/web/app/(app)/environments/[environmentId]/product/tags/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/tags/page.tsx @@ -5,11 +5,11 @@ import { getServerSession } from "next-auth"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; import { getTagsOnResponsesCount } from "@formbricks/lib/tagOnResponse/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -23,25 +23,25 @@ const Page = async ({ params }) => { } const tags = await getTagsByEnvironmentId(params.environmentId); const environmentTagsCount = await getTagsOnResponsesCount(params.environmentId); - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); const session = await getServerSession(authOptions); if (!environment) { throw new Error("Environment not found"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!session) { throw new Error("Unauthenticated"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isViewer } = getAccessFlags(currentUserMembership?.role); const isTagSettingDisabled = isViewer; - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); return !isTagSettingDisabled ? ( 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 0905be6b5c..6745860296 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/layout.tsx @@ -1,18 +1,18 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; const AccountSettingsLayout = async ({ children, params }) => { - const [team, product, session] = await Promise.all([ - getTeamByEnvironmentId(params.environmentId), + const [organization, product, session] = await Promise.all([ + getOrganizationByEnvironmentId(params.environmentId), getProductByEnvironmentId(params.environmentId), getServerSession(authOptions), ]); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!product) { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.tsx index bee8b966c4..d20ec65b38 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditAlerts.tsx @@ -30,15 +30,15 @@ export const EditAlerts = ({
-

{membership.team.name}

+

{membership.organization.name}

Auto-subscribe to new surveys

@@ -60,11 +60,11 @@ export const EditAlerts = ({
- {membership.team.products.some((product) => + {membership.organization.products.some((product) => product.environments.some((environment) => environment.surveys.length > 0) ) ? (
- {membership.team.products.map((product) => ( + {membership.organization.products.map((product) => (
{product.environments.map((environment) => (
@@ -78,7 +78,7 @@ export const EditAlerts = ({
)}

- Want to loop in team mates?{" "} + Want to loop in organization mates?{" "} Invite them. diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx index 2d745ffddc..eb4c435961 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/EditWeeklySummary.tsx @@ -20,7 +20,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler

-

{membership.team.name}

+

{membership.organization.name}

@@ -28,14 +28,14 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler
Weekly Summary
- {membership.team.products.map((product) => ( + {membership.organization.products.map((product) => (
{product?.name}
@@ -44,7 +44,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler ))}

- Want to loop in team mates?{" "} + Want to loop in organization mates?{" "} Invite them. diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx index 027a93595c..e97f70a18c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/components/NotificationSwitch.tsx @@ -9,15 +9,15 @@ import { Switch } from "@formbricks/ui/Switch"; import { updateNotificationSettingsAction } from "../actions"; interface NotificationSwitchProps { - surveyOrProductOrTeamId: string; + surveyOrProductOrOrganizationId: string; notificationSettings: TUserNotificationSettings; - notificationType: "alert" | "weeklySummary" | "unsubscribedTeamIds"; + notificationType: "alert" | "weeklySummary" | "unsubscribedOrganizationIds"; autoDisableNotificationType?: string; autoDisableNotificationElementId?: string; } export const NotificationSwitch = ({ - surveyOrProductOrTeamId, + surveyOrProductOrOrganizationId, notificationSettings, notificationType, autoDisableNotificationType, @@ -26,26 +26,29 @@ export const NotificationSwitch = ({ const [isLoading, setIsLoading] = useState(false); const isChecked = - notificationType === "unsubscribedTeamIds" - ? !notificationSettings.unsubscribedTeamIds?.includes(surveyOrProductOrTeamId) - : notificationSettings[notificationType][surveyOrProductOrTeamId] === true; + notificationType === "unsubscribedOrganizationIds" + ? !notificationSettings.unsubscribedOrganizationIds?.includes(surveyOrProductOrOrganizationId) + : notificationSettings[notificationType][surveyOrProductOrOrganizationId] === true; const handleSwitchChange = async () => { setIsLoading(true); let updatedNotificationSettings = { ...notificationSettings }; - if (notificationType === "unsubscribedTeamIds") { - const unsubscribedTeamIds = updatedNotificationSettings.unsubscribedTeamIds ?? []; - if (unsubscribedTeamIds.includes(surveyOrProductOrTeamId)) { - updatedNotificationSettings.unsubscribedTeamIds = unsubscribedTeamIds.filter( - (id) => id !== surveyOrProductOrTeamId + if (notificationType === "unsubscribedOrganizationIds") { + const unsubscribedOrganizationIds = updatedNotificationSettings.unsubscribedOrganizationIds ?? []; + if (unsubscribedOrganizationIds.includes(surveyOrProductOrOrganizationId)) { + updatedNotificationSettings.unsubscribedOrganizationIds = unsubscribedOrganizationIds.filter( + (id) => id !== surveyOrProductOrOrganizationId ); } else { - updatedNotificationSettings.unsubscribedTeamIds = [...unsubscribedTeamIds, surveyOrProductOrTeamId]; + updatedNotificationSettings.unsubscribedOrganizationIds = [ + ...unsubscribedOrganizationIds, + surveyOrProductOrOrganizationId, + ]; } } else { - updatedNotificationSettings[notificationType][surveyOrProductOrTeamId] = - !updatedNotificationSettings[notificationType][surveyOrProductOrTeamId]; + updatedNotificationSettings[notificationType][surveyOrProductOrOrganizationId] = + !updatedNotificationSettings[notificationType][surveyOrProductOrOrganizationId]; } await updateNotificationSettingsAction(updatedNotificationSettings); @@ -55,12 +58,12 @@ export const NotificationSwitch = ({ useEffect(() => { if ( autoDisableNotificationType && - autoDisableNotificationElementId === surveyOrProductOrTeamId && + autoDisableNotificationElementId === surveyOrProductOrOrganizationId && isChecked ) { switch (notificationType) { case "alert": - if (notificationSettings[notificationType][surveyOrProductOrTeamId] === true) { + if (notificationSettings[notificationType][surveyOrProductOrOrganizationId] === true) { handleSwitchChange(); toast.success("You will not receive any more emails for responses on this survey!", { id: "notification-switch", @@ -68,10 +71,10 @@ export const NotificationSwitch = ({ } break; - case "unsubscribedTeamIds": - if (!notificationSettings.unsubscribedTeamIds?.includes(surveyOrProductOrTeamId)) { + case "unsubscribedOrganizationIds": + if (!notificationSettings.unsubscribedOrganizationIds?.includes(surveyOrProductOrOrganizationId)) { handleSwitchChange(); - toast.success("You will not be auto-subscribed to this team's surveys anymore!", { + toast.success("You will not be auto-subscribed to this organization's surveys anymore!", { id: "notification-switch", }); } 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 9423ae1c15..22297e0f48 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 @@ -21,10 +21,10 @@ const setCompleteNotificationSettings = ( const newNotificationSettings = { alert: {}, weeklySummary: {}, - unsubscribedTeamIds: notificationSettings.unsubscribedTeamIds || [], + unsubscribedOrganizationIds: notificationSettings.unsubscribedOrganizationIds || [], }; for (const membership of memberships) { - for (const product of membership.team.products) { + for (const product of membership.organization.products) { // set default values for weekly summary newNotificationSettings.weeklySummary[product.id] = (notificationSettings.weeklySummary && notificationSettings.weeklySummary[product.id]) || false; @@ -48,7 +48,7 @@ const getMemberships = async (userId: string): Promise => { userId, }, select: { - team: { + organization: { select: { id: true, name: true, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/types.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/types.ts index 57f3d30b0e..ca3f2848a7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/types.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/notifications/types.ts @@ -1,7 +1,7 @@ import { TUserNotificationSettings } from "@formbricks/types/user"; export interface Membership { - team: { + organization: { id: string; name: string; products: { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx index 4002fff3e0..beba3685e4 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx @@ -72,12 +72,12 @@ const DeleteAccountModal = ({ setOpen, open, session, IS_FORMBRICKS_CLOUD }: Del

  • Permanent removal of all of your personal information and data.
  • - If you are the owner of a team with other admins, the ownership of that team will be transferred - to another admin. + If you are the owner of an organization with other admins, the ownership of that organization will + be transferred to another admin.
  • - If you are the only member of a team or there is no other admin present, the team will be - irreversibly deleted along with all associated data. + If you are the only member of an organization or there is no other admin present, the organization + will be irreversibly deleted along with all associated data.
  • This action cannot be undone. If it's gone, it's gone.
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx index c9eb7a23b0..d35cd41d44 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx @@ -33,7 +33,7 @@ const Loading = () => { }, { title: "Avatar", - description: "Assist your team in identifying you on Formbricks.", + description: "Assist your organization in identifying you on Formbricks.", skeletonLines: [{ classes: "h-10 w-10" }, { classes: "h-8 w-24" }], }, { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx index 0e41f8ea02..83e0d91cdd 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx @@ -32,7 +32,9 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { - + {user.identityProvider === "email" && ( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts similarity index 64% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/actions.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts index 86830725d6..f36165365a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts @@ -8,56 +8,56 @@ import { createSubscription } from "@formbricks/ee/billing/lib/createSubscriptio import { removeSubscription } from "@formbricks/ee/billing/lib/removeSubscription"; import { authOptions } from "@formbricks/lib/authOptions"; import { WEBAPP_URL } from "@formbricks/lib/constants"; -import { canUserAccessTeam } from "@formbricks/lib/team/auth"; -import { getTeam } from "@formbricks/lib/team/service"; +import { canUserAccessOrganization } from "@formbricks/lib/organization/auth"; +import { getOrganization } from "@formbricks/lib/organization/service"; import { AuthorizationError } from "@formbricks/types/errors"; export const upgradePlanAction = async ( - teamId: string, + organizationId: string, environmentId: string, priceLookupKeys: StripePriceLookupKeys[] ) => { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const isAuthorized = await canUserAccessTeam(session.user.id, teamId); + const isAuthorized = await canUserAccessOrganization(session.user.id, organizationId); if (!isAuthorized) throw new AuthorizationError("Not authorized"); - const subscriptionSession = await createSubscription(teamId, environmentId, priceLookupKeys); + const subscriptionSession = await createSubscription(organizationId, environmentId, priceLookupKeys); return subscriptionSession; }; -export const manageSubscriptionAction = async (teamId: string, environmentId: string) => { +export const manageSubscriptionAction = async (organizationId: string, environmentId: string) => { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const isAuthorized = await canUserAccessTeam(session.user.id, teamId); + const isAuthorized = await canUserAccessOrganization(session.user.id, organizationId); if (!isAuthorized) throw new AuthorizationError("Not authorized"); - const team = await getTeam(teamId); - if (!team || !team.billing.stripeCustomerId) + const organization = await getOrganization(organizationId); + if (!organization || !organization.billing.stripeCustomerId) throw new AuthorizationError("You do not have an associated Stripe CustomerId"); const sessionUrl = await createCustomerPortalSession( - team.billing.stripeCustomerId, + organization.billing.stripeCustomerId, `${WEBAPP_URL}/environments/${environmentId}/settings/billing` ); return sessionUrl; }; export const removeSubscriptionAction = async ( - teamId: string, + organizationId: string, environmentId: string, priceLookupKeys: StripePriceLookupKeys[] ) => { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const isAuthorized = await canUserAccessTeam(session.user.id, teamId); + const isAuthorized = await canUserAccessOrganization(session.user.id, organizationId); if (!isAuthorized) throw new AuthorizationError("Not authorized"); - const removedSubscription = await removeSubscription(teamId, environmentId, priceLookupKeys); + const removedSubscription = await removeSubscription(organizationId, environmentId, priceLookupKeys); return removedSubscription.url; }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx similarity index 91% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx index 9ba1a1bcda..33b4d854f4 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx @@ -4,20 +4,20 @@ import { manageSubscriptionAction, removeSubscriptionAction, upgradePlanAction, -} from "@/app/(app)/environments/[environmentId]/settings/(team)/billing/actions"; +} from "@/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions"; import { useRouter } from "next/navigation"; import { useState } from "react"; import toast from "react-hot-toast"; import { ProductFeatureKeys, StripePriceLookupKeys } from "@formbricks/ee/billing/lib/constants"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; import { AlertDialog } from "@formbricks/ui/AlertDialog"; import { Button } from "@formbricks/ui/Button"; import { LoadingSpinner } from "@formbricks/ui/LoadingSpinner"; import { PricingCard } from "@formbricks/ui/PricingCard"; interface PricingTableProps { - team: TTeam; + organization: TOrganization; environmentId: string; peopleCount: number; responseCount: number; @@ -26,7 +26,7 @@ interface PricingTableProps { } export const PricingTable = ({ - team, + organization, environmentId, peopleCount, responseCount, @@ -41,7 +41,7 @@ export const PricingTable = ({ const openCustomerPortal = async () => { setLoadingCustomerPortal(true); - const sessionUrl = await manageSubscriptionAction(team.id, environmentId); + const sessionUrl = await manageSubscriptionAction(organization.id, environmentId); router.push(sessionUrl); setLoadingCustomerPortal(false); }; @@ -49,7 +49,11 @@ export const PricingTable = ({ const upgradePlan = async (priceLookupKeys: StripePriceLookupKeys[]) => { try { setUpgradingPlan(true); - const { status, newPlan, url } = await upgradePlanAction(team.id, environmentId, priceLookupKeys); + const { status, newPlan, url } = await upgradePlanAction( + organization.id, + environmentId, + priceLookupKeys + ); setUpgradingPlan(false); if (status != 200) { throw new Error("Something went wrong"); @@ -82,7 +86,7 @@ export const PricingTable = ({ const handleDeleteSubscription = async () => { try { if (!activeLookupKey) throw new Error("No active lookup key"); - await removeSubscriptionAction(team.id, environmentId, [activeLookupKey]); + await removeSubscriptionAction(organization.id, environmentId, [activeLookupKey]); router.refresh(); toast.success("Subscription deleted successfully"); } catch (err) { @@ -98,7 +102,7 @@ export const PricingTable = ({ comingSoon: false, }, { - title: "Team Roles", + title: "Organization Roles", comingSoon: false, }, { @@ -174,7 +178,7 @@ export const PricingTable = ({
)}
- {team.billing.stripeCustomerId ? ( + {organization.billing.stripeCustomerId ? (
) : ( @@ -283,13 +289,13 @@ export const PricingTable = ({ featureName={ProductFeatureKeys[ProductFeatureKeys.inAppSurvey]} monthlyPrice={0} actionText={"Starting at"} - team={team} + organization={organization} metric="responses" sliderValue={responseCount} sliderLimit={350} freeTierLimit={appSurveyFreeResponses} paidFeatures={coreAndWebAppSurveyFeatures.filter((feature) => { - if (team.billing.features.inAppSurvey.unlimited) { + if (organization.billing.features.inAppSurvey.unlimited) { return feature.unlimited !== false; } else { return feature.unlimited !== true; @@ -307,7 +313,7 @@ export const PricingTable = ({ featureName={ProductFeatureKeys[ProductFeatureKeys.linkSurvey]} monthlyPrice={30} actionText={""} - team={team} + organization={organization} paidFeatures={linkSurveysFeatures} loading={upgradingPlan} onUpgrade={() => upgradePlan([StripePriceLookupKeys.linkSurvey])} @@ -320,13 +326,13 @@ export const PricingTable = ({ featureName={ProductFeatureKeys[ProductFeatureKeys.userTargeting]} monthlyPrice={0} actionText={"Starting at"} - team={team} + organization={organization} metric="people" sliderValue={peopleCount} sliderLimit={3500} freeTierLimit={userTargetingFreeMtu} paidFeatures={userTargetingFeatures.filter((feature) => { - if (team.billing.features.userTargeting.unlimited) { + if (organization.billing.features.userTargeting.unlimited) { return feature.unlimited !== false; } else { return feature.unlimited !== true; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/layout.tsx similarity index 67% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/layout.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/layout.tsx index 74386fbb1d..4ec2e9ac3e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/layout.tsx @@ -4,9 +4,9 @@ import { notFound } from "next/navigation"; import { authOptions } from "@formbricks/lib/authOptions"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; export const metadata: Metadata = { @@ -19,16 +19,16 @@ const BillingLayout = async ({ children, params }) => { } const session = await getServerSession(authOptions); - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); if (!session) { throw new Error("Unauthorized"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isAdmin, isOwner } = getAccessFlags(currentUserMembership?.role); const isPricingDisabled = !isOwner && !isAdmin; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/loading.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/loading.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/loading.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/page.tsx similarity index 57% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/page.tsx index f95e51f04d..8baed6ba3e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/page.tsx @@ -1,4 +1,4 @@ -import { TeamSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar"; +import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; @@ -7,21 +7,21 @@ import { PRICING_APPSURVEYS_FREE_RESPONSES, PRICING_USERTARGETING_FREE_MTU, } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { - getMonthlyActiveTeamPeopleCount, - getMonthlyTeamResponseCount, - getTeamByEnvironmentId, -} from "@formbricks/lib/team/service"; + getMonthlyActiveOrganizationPeopleCount, + getMonthlyOrganizationResponseCount, + getOrganizationByEnvironmentId, +} from "@formbricks/lib/organization/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; import { PricingTable } from "./components/PricingTable"; const Page = async ({ params }) => { - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(params.environmentId); + if (!organization) { + throw new Error("Organization not found"); } const session = await getServerSession(authOptions); @@ -30,16 +30,16 @@ const Page = async ({ params }) => { } const [peopleCount, responseCount] = await Promise.all([ - getMonthlyActiveTeamPeopleCount(team.id), - getMonthlyTeamResponseCount(team.id), + getMonthlyActiveOrganizationPeopleCount(organization.id), + getMonthlyOrganizationResponseCount(organization.id), ]); - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); return ( - - + { /> { - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(params.environmentId); + if (!organization) { + throw new Error("Organization not found"); } - const { status, newPlan, url } = await upgradePlanAction(team.id, params.environmentId, [ + const { status, newPlan, url } = await upgradePlanAction(organization.id, params.environmentId, [ StripePriceLookupKeys.inAppSurveyUnlimitedPlan90, StripePriceLookupKeys.linkSurveyUnlimitedPlan19, StripePriceLookupKeys.userTargetingUnlimitedPlan90, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/unlimited99/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/unlimited99/page.tsx similarity index 66% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/unlimited99/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/unlimited99/page.tsx index a07ea3dd26..0d2cf4cd1e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/unlimited99/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/unlimited99/page.tsx @@ -1,17 +1,17 @@ import { redirect } from "next/navigation"; import { StripePriceLookupKeys } from "@formbricks/ee/billing/lib/constants"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { upgradePlanAction } from "../actions"; const Page = async ({ params }) => { - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(params.environmentId); + if (!organization) { + throw new Error("Organization not found"); } - const { status, newPlan, url } = await upgradePlanAction(team.id, params.environmentId, [ + const { status, newPlan, url } = await upgradePlanAction(organization.id, params.environmentId, [ StripePriceLookupKeys.inAppSurveyUnlimitedPlan33, StripePriceLookupKeys.linkSurveyUnlimitedPlan33, StripePriceLookupKeys.userTargetingUnlimitedPlan33, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar.tsx index 07ff1131f4..d9eb753fd9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar.tsx @@ -7,7 +7,7 @@ import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { TMembershipRole } from "@formbricks/types/memberships"; import { SecondaryNavigation } from "@formbricks/ui/SecondaryNavigation"; -export const TeamSettingsNavbar = ({ +export const OrganizationSettingsNavbar = ({ environmentId, isFormbricksCloud, membershipRole, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/enterprise/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/loading.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/enterprise/loading.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/loading.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/enterprise/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx similarity index 89% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/enterprise/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx index 0b776f45e5..f422e99b12 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/enterprise/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/enterprise/page.tsx @@ -1,4 +1,4 @@ -import { TeamSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar"; +import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; import { CheckIcon } from "lucide-react"; import { getServerSession } from "next-auth"; import { notFound } from "next/navigation"; @@ -6,9 +6,9 @@ import { notFound } from "next/navigation"; import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { Button } from "@formbricks/ui/Button"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -20,17 +20,17 @@ const Page = async ({ params }) => { const session = await getServerSession(authOptions); - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); if (!session) { throw new Error("Unauthorized"); } - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isAdmin, isOwner } = getAccessFlags(currentUserMembership?.role); const isPricingDisabled = !isOwner && !isAdmin; @@ -47,7 +47,7 @@ const Page = async ({ params }) => { onRequest: false, }, { - title: "Team Access Roles (Admin, Editor, Developer, etc.)", + title: "Organization Roles (Admin, Editor, Developer, etc.)", comingSoon: false, onRequest: false, }, @@ -85,8 +85,8 @@ const Page = async ({ params }) => { return ( - - + { - const [team, product, session] = await Promise.all([ - getTeamByEnvironmentId(params.environmentId), + const [organization, product, session] = await Promise.all([ + getOrganizationByEnvironmentId(params.environmentId), getProductByEnvironmentId(params.environmentId), getServerSession(authOptions), ]); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } if (!product) { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts similarity index 66% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/actions.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts index 477a5905e9..39dccc1972 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts @@ -3,43 +3,43 @@ import { getServerSession } from "next-auth"; import { sendInviteMemberEmail } from "@formbricks/email"; -import { hasTeamAuthority } from "@formbricks/lib/auth"; +import { hasOrganizationAuthority } from "@formbricks/lib/auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { INVITE_DISABLED } from "@formbricks/lib/constants"; import { deleteInvite, getInvite, inviteUser, resendInvite } from "@formbricks/lib/invite/service"; import { createInviteToken } from "@formbricks/lib/jwt"; import { deleteMembership, - getMembershipByUserIdTeamId, + getMembershipByUserIdOrganizationId, getMembershipsByUserId, } from "@formbricks/lib/membership/service"; -import { verifyUserRoleAccess } from "@formbricks/lib/team/auth"; -import { deleteTeam, updateTeam } from "@formbricks/lib/team/service"; +import { verifyUserRoleAccess } from "@formbricks/lib/organization/auth"; +import { deleteOrganization, updateOrganization } from "@formbricks/lib/organization/service"; import { AuthenticationError, AuthorizationError, ValidationError } from "@formbricks/types/errors"; import { TMembershipRole } from "@formbricks/types/memberships"; -export const updateTeamNameAction = async (teamId: string, teamName: string) => { +export const updateOrganizationNameAction = async (organizationId: string, organizationName: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId); + const isUserAuthorized = await hasOrganizationAuthority(session.user.id, organizationId); if (!isUserAuthorized) { throw new AuthenticationError("Not authorized"); } - return await updateTeam(teamId, { name: teamName }); + return await updateOrganization(organizationId, { name: organizationName }); }; -export const deleteInviteAction = async (inviteId: string, teamId: string) => { +export const deleteInviteAction = async (inviteId: string, organizationId: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId); + const isUserAuthorized = await hasOrganizationAuthority(session.user.id, organizationId); if (!isUserAuthorized) { throw new AuthenticationError("Not authorized"); @@ -48,54 +48,54 @@ export const deleteInviteAction = async (inviteId: string, teamId: string) => { return await deleteInvite(inviteId); }; -export const deleteMembershipAction = async (userId: string, teamId: string) => { +export const deleteMembershipAction = async (userId: string, organizationId: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId); + const isUserAuthorized = await hasOrganizationAuthority(session.user.id, organizationId); if (!isUserAuthorized) { throw new AuthenticationError("Not authorized"); } - const { hasDeleteMembersAccess } = await verifyUserRoleAccess(teamId, session.user.id); + const { hasDeleteMembersAccess } = await verifyUserRoleAccess(organizationId, session.user.id); if (!hasDeleteMembersAccess) { throw new AuthenticationError("Not authorized"); } if (userId === session.user.id) { - throw new AuthenticationError("You cannot delete yourself from the team"); + throw new AuthenticationError("You cannot delete yourself from the organization"); } - return await deleteMembership(userId, teamId); + return await deleteMembership(userId, organizationId); }; -export const leaveTeamAction = async (teamId: string) => { +export const leaveOrganizationAction = async (organizationId: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const membership = await getMembershipByUserIdTeamId(session.user.id, teamId); + const membership = await getMembershipByUserIdOrganizationId(session.user.id, organizationId); if (!membership) { - throw new AuthenticationError("Not a member of this team"); + throw new AuthenticationError("Not a member of this organization"); } if (membership.role === "owner") { - throw new ValidationError("You cannot leave a team you own"); + throw new ValidationError("You cannot leave a organization you own"); } const memberships = await getMembershipsByUserId(session.user.id); if (!memberships || memberships?.length <= 1) { - throw new ValidationError("You cannot leave the only team you are a member of"); + throw new ValidationError("You cannot leave the only organization you are a member of"); } - await deleteMembership(session.user.id, teamId); + await deleteMembership(session.user.id, organizationId); }; export const createInviteTokenAction = async (inviteId: string) => { @@ -110,14 +110,14 @@ export const createInviteTokenAction = async (inviteId: string) => { return { inviteToken: encodeURIComponent(inviteToken) }; }; -export const resendInviteAction = async (inviteId: string, teamId: string) => { +export const resendInviteAction = async (inviteId: string, organizationId: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId); + const isUserAuthorized = await hasOrganizationAuthority(session.user.id, organizationId); if (INVITE_DISABLED) { throw new AuthenticationError("Invite disabled"); @@ -127,7 +127,7 @@ export const resendInviteAction = async (inviteId: string, teamId: string) => { throw new AuthenticationError("Not authorized"); } - const { hasCreateOrUpdateMembersAccess } = await verifyUserRoleAccess(teamId, session.user.id); + const { hasCreateOrUpdateMembersAccess } = await verifyUserRoleAccess(organizationId, session.user.id); if (!hasCreateOrUpdateMembersAccess) { throw new AuthenticationError("Not authorized"); } @@ -143,7 +143,7 @@ export const resendInviteAction = async (inviteId: string, teamId: string) => { }; export const inviteUserAction = async ( - teamId: string, + organizationId: string, email: string, name: string, role: TMembershipRole @@ -154,7 +154,7 @@ export const inviteUserAction = async ( throw new AuthenticationError("Not authenticated"); } - const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId); + const isUserAuthorized = await hasOrganizationAuthority(session.user.id, organizationId); if (INVITE_DISABLED) { throw new AuthenticationError("Invite disabled"); @@ -164,13 +164,13 @@ export const inviteUserAction = async ( throw new AuthenticationError("Not authorized"); } - const { hasCreateOrUpdateMembersAccess } = await verifyUserRoleAccess(teamId, session.user.id); + const { hasCreateOrUpdateMembersAccess } = await verifyUserRoleAccess(organizationId, session.user.id); if (!hasCreateOrUpdateMembersAccess) { throw new AuthenticationError("Not authorized"); } const invite = await inviteUser({ - teamId, + organizationId, currentUser: { id: session.user.id, name: session.user.name }, invitee: { email, @@ -186,17 +186,17 @@ export const inviteUserAction = async ( return invite; }; -export const deleteTeamAction = async (teamId: string) => { +export const deleteOrganizationAction = async (organizationId: string) => { const session = await getServerSession(authOptions); if (!session) { throw new AuthenticationError("Not authenticated"); } - const { hasDeleteAccess } = await verifyUserRoleAccess(teamId, session.user.id); + const { hasDeleteAccess } = await verifyUserRoleAccess(organizationId, session.user.id); if (!hasDeleteAccess) { throw new AuthorizationError("Not authorized"); } - return await deleteTeam(teamId); + return await deleteOrganization(organizationId); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/AddMemberModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/AddMemberModal.tsx similarity index 96% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/AddMemberModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/AddMemberModal.tsx index c0ee79e0e2..a0b2d57eff 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/AddMemberModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/AddMemberModal.tsx @@ -55,7 +55,7 @@ export const AddMemberModal = ({ open={open} setOpen={setOpen} tabs={tabs} - label={"Invite Team Member"} + label={"Invite Organization Member"} closeOnOutsideClick={true} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/BulkInviteTab.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/BulkInviteTab.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/BulkInviteTab.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/BulkInviteTab.tsx index f6f073b932..ed44018a7a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/BulkInviteTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/BulkInviteTab.tsx @@ -89,7 +89,7 @@ export const BulkInviteTab = ({ setOpen, onSubmit, canDoRoleManagement }: BulkIn

- Warning: Please note that on the Free Plan, all team members are + Warning: Please note that on the Free Plan, all organization members are automatically assigned the "Admin" role regardless of the role specified in the CSV file.

@@ -101,7 +101,7 @@ export const BulkInviteTab = ({ setOpen, onSubmit, canDoRoleManagement }: BulkIn
); }; -interface DeleteTeamModalProps { +interface DeleteOrganizationModalProps { open: boolean; setOpen: Dispatch>; - // teamData: { name: string; id: string; plan: string }; - teamData: TTeam; - deleteTeam: () => void; + // organizationData: { name: string; id: string; plan: string }; + organizationData: TOrganization; + deleteOrganization: () => void; isDeleting?: boolean; } -const DeleteTeamModal = ({ setOpen, open, teamData, deleteTeam, isDeleting }: DeleteTeamModalProps) => { +const DeleteOrganizationModal = ({ + setOpen, + open, + organizationData, + deleteOrganization, + isDeleting, +}: DeleteOrganizationModalProps) => { const [inputValue, setInputValue] = useState(""); const handleInputChange = (e) => { @@ -92,32 +102,32 @@ const DeleteTeamModal = ({ setOpen, open, teamData, deleteTeam, isDeleting }: De
  • - Permanent removal of all products linked to this team. This includes all surveys, + Permanent removal of all products linked to this organization. This includes all surveys, responses, user actions and attributes associated with these products.
  • This action cannot be undone. If it's gone, it's gone.
e.preventDefault()}> -
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/EditMemberships.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/EditMemberships.tsx similarity index 73% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/EditMemberships.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/EditMemberships.tsx index 10257ab894..a21157c86f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/EditMemberships.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/EditMemberships.tsx @@ -1,29 +1,29 @@ -import { MembersInfo } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/MembersInfo"; +import { MembersInfo } from "@/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo"; import { getRoleManagementPermission } from "@formbricks/ee/lib/service"; -import { getInvitesByTeamId } from "@formbricks/lib/invite/service"; -import { getMembersByTeamId } from "@formbricks/lib/membership/service"; +import { getInvitesByOrganizationId } from "@formbricks/lib/invite/service"; +import { getMembersByOrganizationId } from "@formbricks/lib/membership/service"; import { TMembership } from "@formbricks/types/memberships"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; type EditMembershipsProps = { - team: TTeam; + organization: TOrganization; currentUserId: string; currentUserMembership: TMembership; allMemberships: TMembership[]; }; export const EditMemberships = async ({ - team, + organization, currentUserId, currentUserMembership: membership, }: EditMembershipsProps) => { - const members = await getMembersByTeamId(team.id); - const invites = await getInvitesByTeamId(team.id); + const members = await getMembersByOrganizationId(organization.id); + const invites = await getInvitesByOrganizationId(organization.id); const currentUserRole = membership?.role; const isUserAdminOrOwner = membership?.role === "admin" || membership?.role === "owner"; - const canDoRoleManagement = await getRoleManagementPermission(team); + const canDoRoleManagement = await getRoleManagementPermission(organization); return (
@@ -37,7 +37,7 @@ export const EditMemberships = async ({ {currentUserRole && ( { +export const MemberActions = ({ organization, member, invite, showDeleteButton }: MemberActionsProps) => { const router = useRouter(); const [isDeleteMemberModalOpen, setDeleteMemberModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); @@ -40,14 +40,14 @@ export const MemberActions = ({ team, member, invite, showDeleteButton }: Member if (!member && invite) { // This is an invite - await deleteInviteAction(invite?.id, team.id); + await deleteInviteAction(invite?.id, organization.id); toast.success("Invite deleted successfully"); } if (member && !invite) { // This is a member - await deleteMembershipAction(member.userId, team.id); + await deleteMembershipAction(member.userId, organization.id); toast.success("Member deleted successfully"); } @@ -87,7 +87,7 @@ export const MemberActions = ({ team, member, invite, showDeleteButton }: Member try { if (!invite) return; - await resendInviteAction(invite.id, team.id); + await resendInviteAction(invite.id, organization.id); toast.success("Invitation sent once more."); } catch (err) { toast.error(`Error: ${err.message}`); @@ -140,7 +140,7 @@ export const MemberActions = ({ team, member, invite, showDeleteButton }: Member diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/MembersInfo.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx similarity index 91% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/MembersInfo.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx index 5f7a71a58f..6f675e48fa 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/MembersInfo.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MembersInfo.tsx @@ -1,14 +1,14 @@ -import { MemberActions } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/MemberActions"; +import { MemberActions } from "@/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/MemberActions"; import { isInviteExpired } from "@/app/lib/utils"; import { EditMembershipRole } from "@formbricks/ee/RoleManagement/components/EditMembershipRole"; import { TInvite } from "@formbricks/types/invites"; import { TMember, TMembershipRole } from "@formbricks/types/memberships"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; import { Badge } from "@formbricks/ui/Badge"; type MembersInfoProps = { - team: TTeam; + organization: TOrganization; members: TMember[]; invites: TInvite[]; isUserAdminOrOwner: boolean; @@ -23,7 +23,7 @@ const isInvitee = (member: TMember | TInvite): member is TInvite => { }; export const MembersInfo = async ({ - team, + organization, invites, isUserAdminOrOwner, members, @@ -53,7 +53,7 @@ export const MembersInfo = async ({ memberRole={member.role} memberId={!isInvitee(member) ? member.userId : ""} memberName={member.name ?? ""} - teamId={team.id} + organizationId={organization.id} userId={currentUserId} memberAccepted={member.accepted} inviteId={isInvitee(member) ? member.id : ""} @@ -72,7 +72,7 @@ export const MembersInfo = async ({ ))} { +}: OrganizationActionsProps) => { const router = useRouter(); - const [isLeaveTeamModalOpen, setLeaveTeamModalOpen] = useState(false); - const [isCreateTeamModalOpen, setCreateTeamModalOpen] = useState(false); + const [isLeaveOrganizationModalOpen, setLeaveOrganizationModalOpen] = useState(false); + const [isCreateOrganizationModalOpen, setCreateOrganizationModalOpen] = useState(false); const [isAddMemberModalOpen, setAddMemberModalOpen] = useState(false); const [loading, setLoading] = useState(false); - const handleLeaveTeam = async () => { + const handleLeaveOrganization = async () => { setLoading(true); try { - await leaveTeamAction(team.id); - toast.success("You left the team successfully"); + await leaveOrganizationAction(organization.id); + toast.success("You left the organization successfully"); router.refresh(); setLoading(false); router.push("/"); @@ -61,7 +61,7 @@ export const TeamActions = ({ try { await Promise.all( data.map(async ({ name, email, role }) => { - await inviteUserAction(team.id, email, name, role); + await inviteUserAction(organization.id, email, name, role); }) ); toast.success("Member invited successfully"); @@ -75,17 +75,21 @@ export const TeamActions = ({ <>
{role !== "owner" && ( - )} {!isInviteDisabled && isAdminOrOwner && (
- - setCreateTeamModalOpen(val)} /> + setCreateOrganizationModalOpen(val)} + /> - {isLeaveTeamDisabled && ( + {isLeaveOrganizationDisabled && (

- You cannot leave this team as it is your only team. Create a new team first. + You cannot leave this organization as it is your only organization. Create a new organization + first.

)}
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/index.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/index.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/index.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/index.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditOrganizationName.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditOrganizationName.tsx new file mode 100644 index 0000000000..92c8f6c8ed --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditOrganizationName.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { updateOrganizationNameAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/members/actions"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { SubmitHandler, useForm, useWatch } from "react-hook-form"; +import toast from "react-hot-toast"; + +import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { TMembershipRole } from "@formbricks/types/memberships"; +import { TOrganization } from "@formbricks/types/organizations"; +import { Button } from "@formbricks/ui/Button"; +import { Input } from "@formbricks/ui/Input"; +import { Label } from "@formbricks/ui/Label"; + +interface EditOrganizationNameForm { + name: string; +} + +interface EditOrganizationNameProps { + environmentId: string; + organization: TOrganization; + membershipRole?: TMembershipRole; +} + +export const EditOrganizationName = ({ organization, membershipRole }: EditOrganizationNameProps) => { + const router = useRouter(); + const { + register, + control, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + name: organization.name, + }, + }); + const [isUpdatingOrganization, setIsUpdatingOrganization] = useState(false); + const { isViewer } = getAccessFlags(membershipRole); + + const organizationName = useWatch({ + control, + name: "name", + }); + + const isOrganizationNameInputEmpty = !organizationName?.trim(); + const currentOrganizationName = organizationName?.trim().toLowerCase() ?? ""; + const previousOrganizationName = organization?.name?.trim().toLowerCase() ?? ""; + + const handleUpdateOrganizationName: SubmitHandler = async (data) => { + try { + data.name = data.name.trim(); + setIsUpdatingOrganization(true); + await updateOrganizationNameAction(organization.id, data.name); + + setIsUpdatingOrganization(false); + toast.success("Organization name updated successfully."); + + router.refresh(); + } catch (err) { + setIsUpdatingOrganization(false); + toast.error(`Error: ${err.message}`); + } + }; + + return isViewer ? ( +

You are not authorized to perform this action.

+ ) : ( +
+ + + + {errors?.name?.message &&

{errors.name.message}

} + + +
+ ); +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/IndividualInviteTab.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/IndividualInviteTab.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/IndividualInviteTab.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/ShareInviteModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/ShareInviteModal.tsx similarity index 90% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/ShareInviteModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/ShareInviteModal.tsx index 646fefe7b5..b8b687112d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/ShareInviteModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/components/ShareInviteModal.tsx @@ -36,9 +36,13 @@ export const ShareInviteModal = ({ inviteToken, open, setOpen }: ShareInviteModa
-

Your team invite link is ready!

+

+ Your organization invite link is ready! +

-

Share this link to let your team member join your team:

+

+ Share this link to let your organization member join your organization: +

{ const cards = [ { title: "Manage members", - description: "Add or remove members in your team", + description: "Add or remove members in your organization", skeleton: (

@@ -47,8 +47,8 @@ const Loading = () => { ), }, { - title: "Team Name", - description: "Give your team a descriptive name", + title: "Organization Name", + description: "Give your organization a descriptive name", skeleton: (
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/page.tsx similarity index 66% rename from apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/page.tsx index faf9fb253c..dc843d6443 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/page.tsx @@ -1,23 +1,26 @@ -import { TeamSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar"; -import { TeamActions } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/TeamActions"; +import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar"; +import { OrganizationActions } from "@/app/(app)/environments/[environmentId]/settings/(organization)/members/components/EditMemberships/OrganizationActions"; import { getServerSession } from "next-auth"; import { Suspense } from "react"; import { getRoleManagementPermission } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { INVITE_DISABLED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId, getMembershipsByUserId } from "@formbricks/lib/membership/service"; +import { + getMembershipByUserIdOrganizationId, + getMembershipsByUserId, +} from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; import { SettingsId } from "@formbricks/ui/SettingsId"; import { Skeleton } from "@formbricks/ui/Skeleton"; import { SettingsCard } from "../../components/SettingsCard"; -import { DeleteTeam } from "./components/DeleteTeam"; +import { DeleteOrganization } from "./components/DeleteOrganization"; import { EditMemberships } from "./components/EditMemberships"; -import { EditTeamName } from "./components/EditTeamName"; +import { EditOrganizationName } from "./components/EditOrganizationName"; const MembersLoading = () => (
@@ -48,40 +51,40 @@ const Page = async ({ params }: { params: { environmentId: string } }) => { if (!session) { throw new Error("Unauthenticated"); } - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const canDoRoleManagement = await getRoleManagementPermission(team); + const canDoRoleManagement = await getRoleManagementPermission(organization); - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isOwner, isAdmin } = getAccessFlags(currentUserMembership?.role); const userMemberships = await getMembershipsByUserId(session.user.id); const isDeleteDisabled = !isOwner; const currentUserRole = currentUserMembership?.role; - const isLeaveTeamDisabled = userMemberships.length <= 1; + const isLeaveOrganizationDisabled = userMemberships.length <= 1; const isUserAdminOrOwner = isAdmin || isOwner; return ( - - + - + {currentUserRole && ( - { {currentUserMembership && ( }> { )} - - + - + - + ); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx deleted file mode 100644 index 93aab122ac..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx +++ /dev/null @@ -1,96 +0,0 @@ -"use client"; - -import { updateTeamNameAction } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/actions"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { SubmitHandler, useForm, useWatch } from "react-hook-form"; -import toast from "react-hot-toast"; - -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { TMembershipRole } from "@formbricks/types/memberships"; -import { TTeam } from "@formbricks/types/teams"; -import { Button } from "@formbricks/ui/Button"; -import { Input } from "@formbricks/ui/Input"; -import { Label } from "@formbricks/ui/Label"; - -interface EditTeamNameForm { - name: string; -} - -interface EditTeamNameProps { - environmentId: string; - team: TTeam; - membershipRole?: TMembershipRole; -} - -export const EditTeamName = ({ team, membershipRole }: EditTeamNameProps) => { - const router = useRouter(); - const { - register, - control, - handleSubmit, - formState: { errors }, - } = useForm({ - defaultValues: { - name: team.name, - }, - }); - const [isUpdatingTeam, setIsUpdatingTeam] = useState(false); - const { isViewer } = getAccessFlags(membershipRole); - - const teamName = useWatch({ - control, - name: "name", - }); - - const isTeamNameInputEmpty = !teamName?.trim(); - const currentTeamName = teamName?.trim().toLowerCase() ?? ""; - const previousTeamName = team?.name?.trim().toLowerCase() ?? ""; - - const handleUpdateTeamName: SubmitHandler = async (data) => { - try { - data.name = data.name.trim(); - setIsUpdatingTeam(true); - await updateTeamNameAction(team.id, data.name); - - setIsUpdatingTeam(false); - toast.success("Team name updated successfully."); - - router.refresh(); - } catch (err) { - setIsUpdatingTeam(false); - toast.error(`Error: ${err.message}`); - } - }; - - return isViewer ? ( -

You are not authorized to perform this action.

- ) : ( -
- - - - {errors?.name?.message &&

{errors.name.message}

} - - -
- ); -}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTimeline.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTimeline.tsx index 50a3546377..dfa7349442 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTimeline.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTimeline.tsx @@ -3,7 +3,7 @@ import { EmptyAppSurveys } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys"; import { useEffect, useRef, useState } from "react"; -import { getMembershipByUserIdTeamIdAction } from "@formbricks/lib/membership/hooks/actions"; +import { getMembershipByUserIdOrganizationIdAction } from "@formbricks/lib/membership/hooks/actions"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { TEnvironment } from "@formbricks/types/environment"; import { TResponse } from "@formbricks/types/responses"; @@ -76,7 +76,7 @@ export const ResponseTimeline = ({ const getRole = async () => { if (isSharingPage) return setIsViewer(true); - const membershipRole = await getMembershipByUserIdTeamIdAction(survey.environmentId); + const membershipRole = await getMembershipByUserIdOrganizationIdAction(survey.environmentId); const { isViewer } = getAccessFlags(membershipRole); setIsViewer(isViewer); }; 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 47c5b7523c..fd527d4f1d 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 @@ -6,13 +6,13 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { RESPONSES_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { getUser } from "@formbricks/lib/user/service"; import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; import { PageHeader } from "@formbricks/ui/PageHeader"; @@ -43,13 +43,13 @@ const Page = async ({ params }) => { throw new Error("User not found"); } const tags = await getTagsByEnvironmentId(params.environmentId); - const team = await getTeamByEnvironmentId(params.environmentId); + const organization = await getOrganizationByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const totalResponseCount = await getResponseCountBySurveyId(params.surveyId); const { isViewer } = getAccessFlags(currentUserMembership?.role); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareSurveyResults.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareSurveyResults.tsx index 5b905c67c3..012d9fb263 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareSurveyResults.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareSurveyResults.tsx @@ -81,8 +81,8 @@ export const ShareSurveyResults = ({ You are about to release these survey results to the public.

- Your survey results will be public. Anyone outside your team can access them if they have the - link. + Your survey results will be public. Anyone outside your organization can access them if they + have the link.

@@ -145,10 +145,10 @@ export const ConnectWithFormbricks = ({ jsPackageVersion={jsPackageVersion} webAppUrl={webAppUrl} environment={environment} - goToTeamInvitePage={() => { + goToOrganizationInvitePage={() => { setCurrentStep(5); localStorage.setItem("onboardingCurrentStep", "5"); - goToTeamInvitePage(); + goToOrganizationInvitePage(); }} /> ); diff --git a/apps/web/app/(app)/onboarding/components/inapp/InviteTeamMate.tsx b/apps/web/app/(app)/onboarding/components/inapp/InviteOrganizationMate.tsx similarity index 87% rename from apps/web/app/(app)/onboarding/components/inapp/InviteTeamMate.tsx rename to apps/web/app/(app)/onboarding/components/inapp/InviteOrganizationMate.tsx index 36e5d97355..1cd2ff2f0b 100644 --- a/apps/web/app/(app)/onboarding/components/inapp/InviteTeamMate.tsx +++ b/apps/web/app/(app)/onboarding/components/inapp/InviteOrganizationMate.tsx @@ -5,14 +5,14 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "react-hot-toast"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; import { Button } from "@formbricks/ui/Button"; import { Input } from "@formbricks/ui/Input"; -import { finishOnboardingAction, inviteTeamMateAction } from "../../actions"; +import { finishOnboardingAction, inviteOrganizationMemberAction } from "../../actions"; -interface InviteTeamMateProps { - team: TTeam; +interface InviteOrganizationMemberProps { + organization: TOrganization; environmentId: string; setCurrentStep: (currentStep: number) => void; } @@ -38,7 +38,11 @@ const InviteMessageInput = ({ value, onChange }) => { ); }; -export const InviteTeamMate = ({ team, environmentId, setCurrentStep }: InviteTeamMateProps) => { +export const InviteOrganizationMember = ({ + organization, + environmentId, + setCurrentStep, +}: InviteOrganizationMemberProps) => { const [formState, setFormState] = useState(INITIAL_FORM_STATE); const [isLoading, setIsLoading] = useState(false); const router = useRouter(); @@ -54,7 +58,12 @@ export const InviteTeamMate = ({ team, environmentId, setCurrentStep }: InviteTe return; } try { - await inviteTeamMateAction(team.id, formState.email, "developer", formState.inviteMessage); + await inviteOrganizationMemberAction( + organization.id, + formState.email, + "developer", + formState.inviteMessage + ); toast.success("Invite sent successful"); goToProduct(); } catch (error) { @@ -85,7 +94,7 @@ export const InviteTeamMate = ({ team, environmentId, setCurrentStep }: InviteTe return (
diff --git a/apps/web/app/(app)/onboarding/components/onboarding.tsx b/apps/web/app/(app)/onboarding/components/onboarding.tsx index 5997a5c6f9..ca4593c042 100644 --- a/apps/web/app/(app)/onboarding/components/onboarding.tsx +++ b/apps/web/app/(app)/onboarding/components/onboarding.tsx @@ -3,7 +3,7 @@ import jsPackageJson from "@/../../packages/js/package.json"; import { finishOnboardingAction } from "@/app/(app)/onboarding/actions"; import { ConnectWithFormbricks } from "@/app/(app)/onboarding/components/inapp/ConnectWithFormbricks"; -import { InviteTeamMate } from "@/app/(app)/onboarding/components/inapp/InviteTeamMate"; +import { InviteOrganizationMember } from "@/app/(app)/onboarding/components/inapp/InviteOrganizationMate"; import { Objective } from "@/app/(app)/onboarding/components/inapp/SurveyObjective"; import { Role } from "@/app/(app)/onboarding/components/inapp/SurveyRole"; import { CreateFirstSurvey } from "@/app/(app)/onboarding/components/link/CreateFirstSurvey"; @@ -12,7 +12,7 @@ import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { TEnvironment } from "@formbricks/types/environment"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; import { TUser } from "@formbricks/types/user"; import { PathwaySelect } from "./PathwaySelect"; @@ -23,7 +23,7 @@ interface OnboardingProps { session: Session; environment: TEnvironment; user: TUser; - team: TTeam; + organization: TOrganization; webAppUrl: string; } @@ -32,7 +32,7 @@ export const Onboarding = ({ session, environment, user, - team, + organization, webAppUrl, }: OnboardingProps) => { const router = useRouter(); @@ -140,7 +140,11 @@ export const Onboarding = ({ return selectedPathway === "link" ? ( ) : ( - + ); default: return null; diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index e72b9bf21e..793da3afe6 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -5,7 +5,7 @@ import { redirect } from "next/navigation"; import { authOptions } from "@formbricks/lib/authOptions"; import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants"; import { getFirstEnvironmentByUserId } from "@formbricks/lib/environment/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { getUser } from "@formbricks/lib/user/service"; const Page = async () => { @@ -24,11 +24,11 @@ const Page = async () => { const userId = session.user.id; const environment = await getFirstEnvironmentByUserId(userId); const user = await getUser(userId); - const team = environment ? await getTeamByEnvironmentId(environment.id) : null; + const organization = environment ? await getOrganizationByEnvironmentId(environment.id) : null; // Ensure all necessary data is available - if (!environment || !user || !team) { - throw new Error("Failed to get necessary user, environment, or team information"); + if (!environment || !user || !organization) { + throw new Error("Failed to get necessary user, environment, or organization information"); } return ( @@ -37,7 +37,7 @@ const Page = async () => { session={session} environment={environment} user={user} - team={team} + organization={organization} webAppUrl={WEBAPP_URL} /> ); diff --git a/apps/web/app/(auth)/invite/components/InviteContentComponents.tsx b/apps/web/app/(auth)/invite/components/InviteContentComponents.tsx index ce541d6c99..66bb007d38 100644 --- a/apps/web/app/(auth)/invite/components/InviteContentComponents.tsx +++ b/apps/web/app/(auth)/invite/components/InviteContentComponents.tsx @@ -51,7 +51,7 @@ export const WrongAccountContent = () => { export const RightAccountContent = () => { return ( - + diff --git a/apps/web/app/(auth)/invite/page.tsx b/apps/web/app/(auth)/invite/page.tsx index aac7801d0d..064506e4ce 100644 --- a/apps/web/app/(auth)/invite/page.tsx +++ b/apps/web/app/(auth)/invite/page.tsx @@ -41,7 +41,7 @@ const Page = async ({ searchParams }) => { } else if (session.user?.email !== email) { return ; } else { - await createMembership(invite.teamId, session.user.id, { accepted: true, role: invite.role }); + await createMembership(invite.organizationId, session.user.id, { accepted: true, role: invite.role }); await deleteInvite(inviteId); sendInviteAcceptedEmail(invite.creator.name ?? "", session.user?.name ?? "", invite.creator.email); diff --git a/apps/web/app/(redirects)/teams/[teamId]/route.ts b/apps/web/app/(redirects)/organizations/[organizationId]/route.ts similarity index 71% rename from apps/web/app/(redirects)/teams/[teamId]/route.ts rename to apps/web/app/(redirects)/organizations/[organizationId]/route.ts index 730c8197df..a39e0e5008 100644 --- a/apps/web/app/(redirects)/teams/[teamId]/route.ts +++ b/apps/web/app/(redirects)/organizations/[organizationId]/route.ts @@ -1,4 +1,4 @@ -import { hasTeamAccess } from "@/app/lib/api/apiHelper"; +import { hasOrganizationAccess } from "@/app/lib/api/apiHelper"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { notFound } from "next/navigation"; @@ -8,16 +8,16 @@ import { getEnvironments } from "@formbricks/lib/environment/service"; import { getProducts } from "@formbricks/lib/product/service"; import { AuthenticationError, AuthorizationError } from "@formbricks/types/errors"; -export const GET = async (_: Request, context: { params: { teamId: string } }) => { - const teamId = context?.params?.teamId; - if (!teamId) return notFound(); +export const GET = async (_: Request, context: { params: { organizationId: string } }) => { + const organizationId = context?.params?.organizationId; + if (!organizationId) return notFound(); // check auth const session = await getServerSession(authOptions); if (!session) throw new AuthenticationError("Not authenticated"); - const hasAccess = await hasTeamAccess(session.user, teamId); + const hasAccess = await hasOrganizationAccess(session.user, organizationId); if (!hasAccess) throw new AuthorizationError("Unauthorized"); // redirect to first product's production environment - const products = await getProducts(teamId); + const products = await getProducts(organizationId); if (products.length === 0) return notFound(); const firstProduct = products[0]; const environments = await getEnvironments(firstProduct.id); diff --git a/apps/web/app/(redirects)/products/[productId]/route.ts b/apps/web/app/(redirects)/products/[productId]/route.ts index 11b08500c6..d0e915d59c 100644 --- a/apps/web/app/(redirects)/products/[productId]/route.ts +++ b/apps/web/app/(redirects)/products/[productId]/route.ts @@ -1,4 +1,4 @@ -import { hasTeamAccess } from "@/app/lib/api/apiHelper"; +import { hasOrganizationAccess } from "@/app/lib/api/apiHelper"; import { getServerSession } from "next-auth"; import { notFound, redirect } from "next/navigation"; @@ -15,7 +15,7 @@ export const GET = async (_: Request, context: { params: { productId: string } } if (!session) throw new AuthenticationError("Not authenticated"); const product = await getProduct(productId); if (!product) return notFound(); - const hasAccess = await hasTeamAccess(session.user, product.teamId); + const hasAccess = await hasOrganizationAccess(session.user, product.organizationId); if (!hasAccess) throw new AuthorizationError("Unauthorized"); // redirect to product's production environment const environments = await getEnvironments(product.id); diff --git a/apps/web/app/api/cron/report-usage/route.ts b/apps/web/app/api/cron/report-usage/route.ts index f837103634..005cdb68b4 100644 --- a/apps/web/app/api/cron/report-usage/route.ts +++ b/apps/web/app/api/cron/report-usage/route.ts @@ -5,14 +5,14 @@ import { ProductFeatureKeys } from "@formbricks/ee/billing/lib/constants"; import { reportUsageToStripe } from "@formbricks/ee/billing/lib/reportUsage"; import { CRON_SECRET, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { - getMonthlyActiveTeamPeopleCount, - getMonthlyTeamResponseCount, - getTeamsWithPaidPlan, -} from "@formbricks/lib/team/service"; -import { TTeam } from "@formbricks/types/teams"; + getMonthlyActiveOrganizationPeopleCount, + getMonthlyOrganizationResponseCount, + getOrganizationsWithPaidPlan, +} from "@formbricks/lib/organization/service"; +import { TOrganization } from "@formbricks/types/organizations"; -const reportTeamUsage = async (team: TTeam) => { - const stripeCustomerId = team.billing.stripeCustomerId; +const reportOrganizationUsage = async (organization: TOrganization) => { + const stripeCustomerId = organization.billing.stripeCustomerId; if (!stripeCustomerId) { return; } @@ -22,16 +22,17 @@ const reportTeamUsage = async (team: TTeam) => { } let calculateResponses = - team.billing.features.inAppSurvey.status !== "inactive" && !team.billing.features.inAppSurvey.unlimited; + organization.billing.features.inAppSurvey.status !== "inactive" && + !organization.billing.features.inAppSurvey.unlimited; let calculatePeople = - team.billing.features.userTargeting.status !== "inactive" && - !team.billing.features.userTargeting.unlimited; + organization.billing.features.userTargeting.status !== "inactive" && + !organization.billing.features.userTargeting.unlimited; if (!calculatePeople && !calculateResponses) { return; } - let people = await getMonthlyActiveTeamPeopleCount(team.id); - let responses = await getMonthlyTeamResponseCount(team.id); + let people = await getMonthlyActiveOrganizationPeopleCount(organization.id); + let responses = await getMonthlyOrganizationResponseCount(organization.id); if (calculatePeople) { await reportUsageToStripe( @@ -60,8 +61,8 @@ export const POST = async (): Promise => { } try { - const teamsWithPaidPlan = await getTeamsWithPaidPlan(); - await Promise.all(teamsWithPaidPlan.map(reportTeamUsage)); + const organizationsWithPaidPlan = await getOrganizationsWithPaidPlan(); + await Promise.all(organizationsWithPaidPlan.map(reportOrganizationUsage)); return responses.successResponse({}, true); } catch (error) { diff --git a/apps/web/app/api/cron/weekly-summary/route.ts b/apps/web/app/api/cron/weekly-summary/route.ts index 3549489247..b7a6427620 100644 --- a/apps/web/app/api/cron/weekly-summary/route.ts +++ b/apps/web/app/api/cron/weekly-summary/route.ts @@ -25,41 +25,43 @@ export const POST = async (): Promise => { const emailSendingPromises: Promise[] = []; - // Fetch all team IDs - const teamIds = await getTeamIds(); + // Fetch all organization IDs + const organizationIds = await getOrganizationIds(); - // Paginate through teams - for (let i = 0; i < teamIds.length; i += BATCH_SIZE) { - const batchedTeamIds = teamIds.slice(i, i + BATCH_SIZE); - // Fetch products for batched teams asynchronously - const batchedProductsPromises = batchedTeamIds.map((teamId) => getProductsByTeamId(teamId)); + // Paginate through organizations + for (let i = 0; i < organizationIds.length; i += BATCH_SIZE) { + const batchedOrganizationIds = organizationIds.slice(i, i + BATCH_SIZE); + // Fetch products for batched organizations asynchronously + const batchedProductsPromises = batchedOrganizationIds.map((organizationId) => + getProductsByOrganizationId(organizationId) + ); const batchedProducts = await Promise.all(batchedProductsPromises); for (const products of batchedProducts) { for (const product of products) { - const teamMembers = product.team.memberships; - const teamMembersWithNotificationEnabled = teamMembers.filter( + const organizationMembers = product.organization.memberships; + const organizationMembersWithNotificationEnabled = organizationMembers.filter( (member) => member.user.notificationSettings?.weeklySummary && member.user.notificationSettings.weeklySummary[product.id] ); - if (teamMembersWithNotificationEnabled.length === 0) continue; + if (organizationMembersWithNotificationEnabled.length === 0) continue; const notificationResponse = getNotificationResponse(product.environments[0], product.name); if (notificationResponse.insights.numLiveSurvey === 0) { - for (const teamMember of teamMembersWithNotificationEnabled) { + for (const organizationMember of organizationMembersWithNotificationEnabled) { emailSendingPromises.push( - sendNoLiveSurveyNotificationEmail(teamMember.user.email, notificationResponse) + sendNoLiveSurveyNotificationEmail(organizationMember.user.email, notificationResponse) ); } continue; } - for (const teamMember of teamMembersWithNotificationEnabled) { + for (const organizationMember of organizationMembersWithNotificationEnabled) { emailSendingPromises.push( - sendWeeklySummaryNotificationEmail(teamMember.user.email, notificationResponse) + sendWeeklySummaryNotificationEmail(organizationMember.user.email, notificationResponse) ); } } @@ -70,22 +72,22 @@ export const POST = async (): Promise => { return responses.successResponse({}, true); }; -const getTeamIds = async (): Promise => { - const teams = await prisma.team.findMany({ +const getOrganizationIds = async (): Promise => { + const organizations = await prisma.organization.findMany({ select: { id: true, }, }); - return teams.map((team) => team.id); + return organizations.map((organization) => organization.id); }; -const getProductsByTeamId = async (teamId: string): Promise => { +const getProductsByOrganizationId = async (organizationId: string): Promise => { const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); return await prisma.product.findMany({ where: { - teamId: teamId, + organizationId: organizationId, }, select: { id: true, @@ -152,7 +154,7 @@ const getProductsByTeamId = async (teamId: string): Promise { if (event === "responseFinished") { // check for email notifications - // get all users that have a membership of this environment's team + // get all users that have a membership of this environment's organization const users = await prisma.user.findMany({ where: { memberships: { some: { - team: { + organization: { products: { some: { environments: { diff --git a/apps/web/app/api/v1/(legacy)/client/[environmentId]/people/[userId]/set-attribute/route.ts b/apps/web/app/api/v1/(legacy)/client/[environmentId]/people/[userId]/set-attribute/route.ts index f71c297b38..35f2d6dc48 100644 --- a/apps/web/app/api/v1/(legacy)/client/[environmentId]/people/[userId]/set-attribute/route.ts +++ b/apps/web/app/api/v1/(legacy)/client/[environmentId]/people/[userId]/set-attribute/route.ts @@ -3,12 +3,12 @@ import { transformErrorToDetails } from "@/app/lib/api/validator"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { updateAttributes } from "@formbricks/lib/attribute/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { personCache } from "@formbricks/lib/person/cache"; import { getPerson } from "@formbricks/lib/person/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { surveyCache } from "@formbricks/lib/survey/cache"; import { getSyncSurveys } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ZJsPeopleAttributeInput } from "@formbricks/types/js"; interface Context { @@ -58,10 +58,10 @@ export const POST = async (req: Request, context: Context): Promise => environmentId, }); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const [surveys, noCodeActionClasses, product] = await Promise.all([ diff --git a/apps/web/app/api/v1/(legacy)/client/people/[personId]/set-attribute/route.ts b/apps/web/app/api/v1/(legacy)/client/people/[personId]/set-attribute/route.ts index b852651ef2..ab08ccb989 100644 --- a/apps/web/app/api/v1/(legacy)/client/people/[personId]/set-attribute/route.ts +++ b/apps/web/app/api/v1/(legacy)/client/people/[personId]/set-attribute/route.ts @@ -3,12 +3,12 @@ import { transformErrorToDetails } from "@/app/lib/api/validator"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { updateAttributes } from "@formbricks/lib/attribute/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { personCache } from "@formbricks/lib/person/cache"; import { getPerson } from "@formbricks/lib/person/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { surveyCache } from "@formbricks/lib/survey/cache"; import { getSyncSurveys } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ZJsPeopleAttributeInput } from "@formbricks/types/js"; interface Context { @@ -57,10 +57,10 @@ export const POST = async (req: Request, context: Context): Promise => environmentId, }); - const team = await getTeamByEnvironmentId(environmentId); + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team not found"); + if (!organization) { + throw new Error("Organization not found"); } const [surveys, noCodeActionClasses, product] = await Promise.all([ diff --git a/apps/web/app/api/v1/(legacy)/js/sync/lib/sync.ts b/apps/web/app/api/v1/(legacy)/js/sync/lib/sync.ts index 913810fddd..10f7eacf2c 100644 --- a/apps/web/app/api/v1/(legacy)/js/sync/lib/sync.ts +++ b/apps/web/app/api/v1/(legacy)/js/sync/lib/sync.ts @@ -7,15 +7,15 @@ import { } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; import { reverseTranslateSurvey } from "@formbricks/lib/i18n/reverseTranslation"; +import { + getMonthlyActiveOrganizationPeopleCount, + getMonthlyOrganizationResponseCount, + getOrganizationByEnvironmentId, +} from "@formbricks/lib/organization/service"; import { getPerson } from "@formbricks/lib/person/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants"; import { getSurveys, getSyncSurveys } from "@formbricks/lib/survey/service"; -import { - getMonthlyActiveTeamPeopleCount, - getMonthlyTeamResponseCount, - getTeamByEnvironmentId, -} from "@formbricks/lib/team/service"; import { TEnvironment } from "@formbricks/types/environment"; import { TJsLegacyState, TSurveyWithTriggers } from "@formbricks/types/js"; import { TPerson } from "@formbricks/types/people"; @@ -43,19 +43,19 @@ export const getUpdatedState = async (environmentId: string, personId?: string): throw new Error("Environment does not exist"); } - // check team subscriptons - const team = await getTeamByEnvironmentId(environmentId); + // check organization subscriptons + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team does not exist"); + if (!organization) { + throw new Error("Organization does not exist"); } // check if Monthly Active Users limit is reached if (IS_FORMBRICKS_CLOUD) { const hasUserTargetingSubscription = - team?.billing?.features.userTargeting.status && - ["active", "canceled"].includes(team?.billing?.features.userTargeting.status); - const currentMau = await getMonthlyActiveTeamPeopleCount(team.id); + organization?.billing?.features.userTargeting.status && + ["active", "canceled"].includes(organization?.billing?.features.userTargeting.status); + const currentMau = await getMonthlyActiveOrganizationPeopleCount(organization.id); const isMauLimitReached = !hasUserTargetingSubscription && currentMau >= PRICING_USERTARGETING_FREE_MTU; if (isMauLimitReached) { const errorMessage = `Monthly Active Users limit reached in ${environmentId} (${currentMau}/${MAU_LIMIT})`; @@ -82,9 +82,9 @@ export const getUpdatedState = async (environmentId: string, personId?: string): let isAppSurveyLimitReached = false; if (IS_FORMBRICKS_CLOUD) { const hasAppSurveySubscription = - team?.billing?.features.inAppSurvey.status && - ["active", "canceled"].includes(team?.billing?.features.inAppSurvey.status); - const monthlyResponsesCount = await getMonthlyTeamResponseCount(team.id); + organization?.billing?.features.inAppSurvey.status && + ["active", "canceled"].includes(organization?.billing?.features.inAppSurvey.status); + const monthlyResponsesCount = await getMonthlyOrganizationResponseCount(organization.id); isAppSurveyLimitReached = IS_FORMBRICKS_CLOUD && !hasAppSurveySubscription && diff --git a/apps/web/app/api/v1/client/[environmentId]/actions/route.ts b/apps/web/app/api/v1/client/[environmentId]/actions/route.ts index c34583cf93..e81febe032 100644 --- a/apps/web/app/api/v1/client/[environmentId]/actions/route.ts +++ b/apps/web/app/api/v1/client/[environmentId]/actions/route.ts @@ -3,7 +3,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator"; import { createAction } from "@formbricks/lib/action/service"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { ZActionInput } from "@formbricks/types/actions"; interface Context { @@ -36,8 +36,8 @@ export const POST = async (req: Request, context: Context): Promise => // Formbricks Cloud: Make sure environment is part of a paid plan if (IS_FORMBRICKS_CLOUD) { - const team = await getTeamByEnvironmentId(context.params.environmentId); - if (!team || team.billing.features.userTargeting.status !== "active") { + const organization = await getOrganizationByEnvironmentId(context.params.environmentId); + if (!organization || organization.billing.features.userTargeting.status !== "active") { // temporary return status code 200 to avoid CORS issues; will be changed to 400 in the future return responses.successResponse({}, true); //return responses.badRequestResponse("Storing actions is only possible in a paid plan", {}, true); diff --git a/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts b/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts index 247506f96d..d9513736a8 100644 --- a/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts +++ b/apps/web/app/api/v1/client/[environmentId]/app/sync/[userId]/route.ts @@ -11,15 +11,15 @@ import { PRICING_USERTARGETING_FREE_MTU, } from "@formbricks/lib/constants"; import { getEnvironment, updateEnvironment } from "@formbricks/lib/environment/service"; +import { + getMonthlyActiveOrganizationPeopleCount, + getMonthlyOrganizationResponseCount, + getOrganizationByEnvironmentId, +} from "@formbricks/lib/organization/service"; import { createPerson, getIsPersonMonthlyActive, getPersonByUserId } from "@formbricks/lib/person/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants"; import { getSyncSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service"; -import { - getMonthlyActiveTeamPeopleCount, - getMonthlyTeamResponseCount, - getTeamByEnvironmentId, -} from "@formbricks/lib/team/service"; import { isVersionGreaterThanOrEqualTo } from "@formbricks/lib/utils/version"; import { TLegacySurvey } from "@formbricks/types/LegacySurvey"; import { TEnvironment } from "@formbricks/types/environment"; @@ -75,11 +75,11 @@ export const GET = async ( await updateEnvironment(environment.id, { widgetSetupCompleted: true }); } - // check team subscriptions - const team = await getTeamByEnvironmentId(environmentId); + // check organization subscriptions + const organization = await getOrganizationByEnvironmentId(environmentId); - if (!team) { - throw new Error("Team does not exist"); + if (!organization) { + throw new Error("Organization does not exist"); } // check if MAU limit is reached @@ -88,15 +88,15 @@ export const GET = async ( if (IS_FORMBRICKS_CLOUD) { // check userTargeting subscription const hasUserTargetingSubscription = - team.billing.features.userTargeting.status && - ["active", "canceled"].includes(team.billing.features.userTargeting.status); - const currentMau = await getMonthlyActiveTeamPeopleCount(team.id); + organization.billing.features.userTargeting.status && + ["active", "canceled"].includes(organization.billing.features.userTargeting.status); + const currentMau = await getMonthlyActiveOrganizationPeopleCount(organization.id); isMauLimitReached = !hasUserTargetingSubscription && currentMau >= PRICING_USERTARGETING_FREE_MTU; // check inAppSurvey subscription const hasInAppSurveySubscription = - team.billing.features.inAppSurvey.status && - ["active", "canceled"].includes(team.billing.features.inAppSurvey.status); - const currentResponseCount = await getMonthlyTeamResponseCount(team.id); + organization.billing.features.inAppSurvey.status && + ["active", "canceled"].includes(organization.billing.features.inAppSurvey.status); + const currentResponseCount = await getMonthlyOrganizationResponseCount(organization.id); isInAppSurveyLimitReached = !hasInAppSurveySubscription && currentResponseCount >= PRICING_APPSURVEYS_FREE_RESPONSES; } diff --git a/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts b/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts index 3e8e4f1cb2..7a98029048 100644 --- a/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts +++ b/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts @@ -7,9 +7,9 @@ import { NextRequest } from "next/server"; import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants"; import { validateLocalSignedUrl } from "@formbricks/lib/crypto"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { putFileToLocalStorage } from "@formbricks/lib/storage/service"; import { getSurvey } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; interface Context { params: { @@ -69,14 +69,17 @@ export const POST = async (req: NextRequest, context: Context): Promise= PRICING_APPSURVEYS_FREE_RESPONSES; if (isInAppSurveyLimitReached) { diff --git a/apps/web/app/api/v1/users/me/route.ts b/apps/web/app/api/v1/users/me/route.ts index f2582340e4..fc997b46bc 100644 --- a/apps/web/app/api/v1/users/me/route.ts +++ b/apps/web/app/api/v1/users/me/route.ts @@ -53,12 +53,12 @@ const deleteUser = async (userId: string) => { }); }; -const updateUserMembership = async (teamId: string, userId: string, role: MembershipRole) => { +const updateUserMembership = async (organizationId: string, userId: string, role: MembershipRole) => { await prisma.membership.update({ where: { - userId_teamId: { + userId_organizationId: { userId, - teamId, + organizationId, }, }, data: { @@ -70,10 +70,10 @@ const updateUserMembership = async (teamId: string, userId: string, role: Member const getAdminMemberships = (memberships: Membership[]) => memberships.filter((membership) => membership.role === MembershipRole.admin); -const deleteTeam = async (teamId: string) => { - await prisma.team.delete({ +const deleteOrganization = async (organizationId: string) => { + await prisma.organization.delete({ where: { - id: teamId, + id: organizationId, }, }); }; @@ -93,7 +93,7 @@ export const DELETE = async () => { userId: currentUser.id, }, include: { - team: { + organization: { select: { id: true, name: true, @@ -109,22 +109,22 @@ export const DELETE = async () => { }); for (const currentUserMembership of currentUserMemberships) { - const teamMemberships = currentUserMembership.team.memberships; + const organizationMemberships = currentUserMembership.organization.memberships; const role = currentUserMembership.role; - const teamId = currentUserMembership.teamId; + const organizationId = currentUserMembership.organizationId; - const teamAdminMemberships = getAdminMemberships(teamMemberships); - const teamHasAtLeastOneAdmin = teamAdminMemberships.length > 0; - const teamHasOnlyOneMember = teamMemberships.length === 1; - const currentUserIsTeamOwner = role === MembershipRole.owner; + const organizationAdminMemberships = getAdminMemberships(organizationMemberships); + const organizationHasAtLeastOneAdmin = organizationAdminMemberships.length > 0; + const organizationHasOnlyOneMember = organizationMemberships.length === 1; + const currentUserIsOrganizationOwner = role === MembershipRole.owner; - if (teamHasOnlyOneMember) { - await deleteTeam(teamId); - } else if (currentUserIsTeamOwner && teamHasAtLeastOneAdmin) { - const firstAdmin = teamAdminMemberships[0]; - await updateUserMembership(teamId, firstAdmin.userId, MembershipRole.owner); - } else if (currentUserIsTeamOwner) { - await deleteTeam(teamId); + if (organizationHasOnlyOneMember) { + await deleteOrganization(organizationId); + } else if (currentUserIsOrganizationOwner && organizationHasAtLeastOneAdmin) { + const firstAdmin = organizationAdminMemberships[0]; + await updateUserMembership(organizationId, firstAdmin.userId, MembershipRole.owner); + } else if (currentUserIsOrganizationOwner) { + await deleteOrganization(organizationId); } } diff --git a/apps/web/app/api/v1/users/route.ts b/apps/web/app/api/v1/users/route.ts index e4b9ede0c3..1558477078 100644 --- a/apps/web/app/api/v1/users/route.ts +++ b/apps/web/app/api/v1/users/route.ts @@ -1,8 +1,8 @@ import { prisma } from "@formbricks/database"; import { sendInviteAcceptedEmail, sendVerificationEmail } from "@formbricks/email"; import { - DEFAULT_TEAM_ID, - DEFAULT_TEAM_ROLE, + DEFAULT_ORGANIZATION_ID, + DEFAULT_ORGANIZATION_ROLE, EMAIL_AUTH_ENABLED, EMAIL_VERIFICATION_DISABLED, INVITE_DISABLED, @@ -11,8 +11,8 @@ import { import { deleteInvite } from "@formbricks/lib/invite/service"; import { verifyInviteToken } from "@formbricks/lib/jwt"; import { createMembership } from "@formbricks/lib/membership/service"; +import { createOrganization, getOrganization } from "@formbricks/lib/organization/service"; import { createProduct } from "@formbricks/lib/product/service"; -import { createTeam, getTeam } from "@formbricks/lib/team/service"; import { createUser, updateUser } from "@formbricks/lib/user/service"; export const POST = async (request: Request) => { @@ -53,10 +53,10 @@ export const POST = async (request: Request) => { // create the user user = await createUser(user); - // User is invited to team + // User is invited to organization if (isInviteValid) { - // assign user to existing team - await createMembership(invite.teamId, user.id, { + // assign user to existing organization + await createMembership(invite.organizationId, user.id, { accepted: true, role: invite.role, }); @@ -72,24 +72,27 @@ export const POST = async (request: Request) => { } // User signs up without invite - // Default team assignment is enabled - if (DEFAULT_TEAM_ID && DEFAULT_TEAM_ID.length > 0) { - // check if team exists - let team = await getTeam(DEFAULT_TEAM_ID); - let isNewTeam = false; - if (!team) { - // create team with id from env - team = await createTeam({ id: DEFAULT_TEAM_ID, name: user.name + "'s Team" }); - isNewTeam = true; + // Default organization assignment is enabled + if (DEFAULT_ORGANIZATION_ID && DEFAULT_ORGANIZATION_ID.length > 0) { + // check if organization exists + let organization = await getOrganization(DEFAULT_ORGANIZATION_ID); + let isNewOrganization = false; + if (!organization) { + // create organization with id from env + organization = await createOrganization({ + id: DEFAULT_ORGANIZATION_ID, + name: user.name + "'s Organization", + }); + isNewOrganization = true; } - const role = isNewTeam ? "owner" : DEFAULT_TEAM_ROLE || "admin"; - await createMembership(team.id, user.id, { role, accepted: true }); + const role = isNewOrganization ? "owner" : DEFAULT_ORGANIZATION_ROLE || "admin"; + await createMembership(organization.id, user.id, { role, accepted: true }); } - // Without default team assignment + // Without default organization assignment else { - const team = await createTeam({ name: user.name + "'s Team" }); - await createMembership(team.id, user.id, { role: "owner", accepted: true }); - const product = await createProduct(team.id, { name: "My Product" }); + const organization = await createOrganization({ name: user.name + "'s Organization" }); + await createMembership(organization.id, user.id, { role: "owner", accepted: true }); + const product = await createProduct(organization.id, { name: "My Product" }); const updatedNotificationSettings = { ...user.notificationSettings, diff --git a/apps/web/app/lib/api/apiHelper.ts b/apps/web/app/lib/api/apiHelper.ts index b481faf150..a4b0ea4747 100644 --- a/apps/web/app/lib/api/apiHelper.ts +++ b/apps/web/app/lib/api/apiHelper.ts @@ -49,12 +49,12 @@ export const hasApiEnvironmentAccess = async (apiKey, environmentId) => { return false; }; -export const hasTeamAccess = async (user, teamId) => { +export const hasOrganizationAccess = async (user, organizationId) => { const membership = await prisma.membership.findUnique({ where: { - userId_teamId: { + userId_organizationId: { userId: user.id, - teamId: teamId, + organizationId: organizationId, }, }, }); @@ -75,12 +75,12 @@ export const getSessionUser = async (req?: NextApiRequest, res?: NextApiResponse if (session && "user" in session) return session.user; }; -export const isOwner = async (user, teamId) => { +export const isOwner = async (user, organizationId) => { const membership = await prisma.membership.findUnique({ where: { - userId_teamId: { + userId_organizationId: { userId: user.id, - teamId: teamId, + organizationId: organizationId, }, }, }); @@ -90,12 +90,12 @@ export const isOwner = async (user, teamId) => { return false; }; -export const isAdminOrOwner = async (user, teamId) => { +export const isAdminOrOwner = async (user, organizationId) => { const membership = await prisma.membership.findUnique({ where: { - userId_teamId: { + userId_organizationId: { userId: user.id, - teamId: teamId, + organizationId: organizationId, }, }, }); diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 019d9b0041..06761dcca6 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -5,7 +5,7 @@ import { redirect } from "next/navigation"; import { authOptions } from "@formbricks/lib/authOptions"; import { ONBOARDING_DISABLED } from "@formbricks/lib/constants"; import { getFirstEnvironmentByUserId } from "@formbricks/lib/environment/service"; -import { getTeamsByUserId } from "@formbricks/lib/team/service"; +import { getOrganizationsByUserId } from "@formbricks/lib/organization/service"; import { ClientLogout } from "@formbricks/ui/ClientLogout"; const Page = async () => { @@ -19,10 +19,10 @@ const Page = async () => { return ; } - const teams = await getTeamsByUserId(session.user.id); - if (!teams || teams.length === 0) { - console.error("Failed to get teams, redirecting to create-first-team"); - return redirect("/create-first-team"); + const organizations = await getOrganizationsByUserId(session.user.id); + if (!organizations || organizations.length === 0) { + console.error("Failed to get organizations, redirecting to create-first-organization"); + return redirect("/create-first-organization"); } if (!ONBOARDING_DISABLED && !session.user.onboardingCompleted) { diff --git a/apps/web/app/s/[surveyId]/page.tsx b/apps/web/app/s/[surveyId]/page.tsx index ef6da6f4dc..f9f64b6f6b 100644 --- a/apps/web/app/s/[surveyId]/page.tsx +++ b/apps/web/app/s/[surveyId]/page.tsx @@ -9,11 +9,11 @@ import { notFound } from "next/navigation"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { IMPRINT_URL, IS_FORMBRICKS_CLOUD, PRIVACY_URL, WEBAPP_URL } from "@formbricks/lib/constants"; +import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; import { createPerson, getPersonByUserId } from "@formbricks/lib/person/service"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getResponseBySingleUseId, getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ZId } from "@formbricks/types/environment"; import { TResponse } from "@formbricks/types/responses"; import { MediaBackground } from "@formbricks/ui/MediaBackground"; @@ -57,11 +57,11 @@ const Page = async ({ params, searchParams }: LinkSurveyPageProps) => { notFound(); } - const team = await getTeamByEnvironmentId(survey?.environmentId); - if (!team) { - throw new Error("Team not found"); + const organization = await getOrganizationByEnvironmentId(survey?.environmentId); + if (!organization) { + throw new Error("Organization not found"); } - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + const isMultiLanguageAllowed = await getMultiLanguagePermission(organization); if (survey && survey.status !== "inProgress") { return ( diff --git a/apps/web/playwright/onboarding.spec.ts b/apps/web/playwright/onboarding.spec.ts index 3b7c3dda92..a91cc1532a 100644 --- a/apps/web/playwright/onboarding.spec.ts +++ b/apps/web/playwright/onboarding.spec.ts @@ -1,9 +1,9 @@ import { expect, test } from "@playwright/test"; import { signUpAndLogin } from "./utils/helper"; -import { teams, users } from "./utils/mock"; +import { organizations, users } from "./utils/mock"; -const { productName } = teams.onboarding[0]; +const { productName } = organizations.onboarding[0]; test.describe("Onboarding Flow Test", async () => { test("link survey", async ({ page }) => { diff --git a/apps/web/playwright/team.spec.ts b/apps/web/playwright/organization.spec.ts similarity index 88% rename from apps/web/playwright/team.spec.ts rename to apps/web/playwright/organization.spec.ts index 70544d34d7..67210844e2 100644 --- a/apps/web/playwright/team.spec.ts +++ b/apps/web/playwright/organization.spec.ts @@ -3,12 +3,12 @@ import { expect, test } from "playwright/test"; import { finishOnboarding, login, signUpAndLogin, signupUsingInviteToken } from "./utils/helper"; import { invites, users } from "./utils/mock"; -test.describe("Invite, accept and remove team member", async () => { +test.describe("Invite, accept and remove organization member", async () => { test.describe.configure({ mode: "serial" }); - const { email, password, name } = users.team[0]; + const { email, password, name } = users.organization[0]; let inviteLink: string; - test("Invite team member", async ({ page }) => { + test("Invite organization member", async ({ page }) => { await signUpAndLogin(page, name, email, password); await finishOnboarding(page); @@ -19,7 +19,7 @@ test.describe("Invite, accept and remove team member", async () => { const dropdownInnerContentWrapper = page.locator("#userDropdownInnerContentWrapper"); await expect(dropdownInnerContentWrapper).toBeVisible(); - await page.getByRole("link", { name: "Team" }).click(); + await page.getByRole("link", { name: "Organization" }).click(); await page.waitForURL(/\/environments\/[^/]+\/settings\/members/); // Add member button @@ -50,7 +50,7 @@ test.describe("Invite, accept and remove team member", async () => { const dropdownInnerContentWrapper = page.locator("#userDropdownInnerContentWrapper"); await expect(dropdownInnerContentWrapper).toBeVisible(); - await page.getByRole("link", { name: "Team" }).click(); + await page.getByRole("link", { name: "Organization" }).click(); await expect(page.locator("#membersInfoWrapper")).toBeVisible(); @@ -76,7 +76,7 @@ test.describe("Invite, accept and remove team member", async () => { }); test("Accept invite", async ({ page }) => { - const { email, name, password } = users.team[1]; + const { email, name, password } = users.organization[1]; page.goto(inviteLink); await page.waitForURL(/\/invite\?token=[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/); @@ -99,7 +99,7 @@ test.describe("Invite, accept and remove team member", async () => { const dropdownInnerContentWrapper = page.locator("#userDropdownInnerContentWrapper"); await expect(dropdownInnerContentWrapper).toBeVisible(); - await page.getByRole("link", { name: "Team" }).click(); + await page.getByRole("link", { name: "Organization" }).click(); await expect(page.locator("#membersInfoWrapper")).toBeVisible(); @@ -114,6 +114,6 @@ test.describe("Invite, accept and remove team member", async () => { await expect(page.getByRole("button", { name: "Delete", exact: true })).toBeVisible(); await page.getByRole("button", { name: "Delete", exact: true }).click(); - await expect(page.getByText("team2@formbricks.com")).not.toBeVisible(); + await expect(page.getByText("organization2@formbricks.com")).not.toBeVisible(); }); }); diff --git a/apps/web/playwright/utils/mock.ts b/apps/web/playwright/utils/mock.ts index 2f2b457c73..1230ea949a 100644 --- a/apps/web/playwright/utils/mock.ts +++ b/apps/web/playwright/utils/mock.ts @@ -74,21 +74,21 @@ export const users = { password: "XpP%X9UU3efj8vJa", }, ], - team: [ + organization: [ { - name: "Team User 1", - email: "team1@formbricks.com", + name: "Organization User 1", + email: "organization1@formbricks.com", password: "Test#1234", }, { - name: "Team User 2", - email: "team2@formbricks.com", + name: "Organization User 2", + email: "organization2@formbricks.com", password: "Test#1234", }, ], }; -export const teams = { +export const organizations = { onboarding: [ { role: "Founder", @@ -301,7 +301,7 @@ export const actions = { export const invites = { addMember: { - name: "Team User 2", - email: "team2@formbricks.com", + name: "Organization User 2", + email: "organization2@formbricks.com", }, }; diff --git a/apps/web/public/sample-csv/formbricks-team-members-template.csv b/apps/web/public/sample-csv/formbricks-organization-members-template.csv similarity index 100% rename from apps/web/public/sample-csv/formbricks-team-members-template.csv rename to apps/web/public/sample-csv/formbricks-organization-members-template.csv diff --git a/docker-compose.yml b/docker-compose.yml index dcb53c4d5e..661ca1bce0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,14 +5,15 @@ x-webapp-url: &webapp_url http://localhost:3000 x-database-url: &database_url postgresql://postgres:postgres@postgres:5432/formbricks?schema=public x-redis-url: &redis_url + # NextJS Auth + # @see: https://next-auth.js.org/configuration/options#nextauth_secret + # You can use: `openssl rand -hex 32` to generate one -# NextJS Auth -# @see: https://next-auth.js.org/configuration/options#nextauth_secret -# You can use: `openssl rand -hex 32` to generate one x-nextauth-secret: &nextauth_secret # Set this to your public-facing URL, e.g., https://example.com # You do not need the NEXTAUTH_URL environment variable in Vercel. + x-nextauth-url: &nextauth_url http://localhost:3000 # Encryption key @@ -43,7 +44,7 @@ x-signup-disabled: &signup_disabled 0 # Email login. Disable the ability for users to login with email. x-auth-disabled: &email_auth_disabled 0 -# Team Invite. Disable the ability for invited users to create an account. +# Organization Invite. Disable the ability for invited users to create an account. x-invite-disabled: &invite_disabled 0 # Set the below values to display privacy policy, imprint and terms of service links in the footer of signup & public pages. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 02e23b4269..55fd147919 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -154,11 +154,11 @@ x-environment: &environment ############################################# OPTIONAL (OTHER) ############################################# - # Set the below to automatically assign new users to a specific team and role within that team - # Insert an existing team id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn) + # Set the below to automatically assign new users to a specific organization and role within that organization + # Insert an existing organization id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn) # (Role Management is an Enterprise feature) - # DEFAULT_TEAM_ID: - # DEFAULT_TEAM_ROLE: admin + # DEFAULT_ORGANIZATION_ID: + # DEFAULT_ORGANIZATION_ROLE: admin services: postgres: diff --git a/kamal/deploy.yml b/kamal/deploy.yml index 77265e9486..2f31f97060 100644 --- a/kamal/deploy.yml +++ b/kamal/deploy.yml @@ -76,7 +76,7 @@ env: - GOOGLE_SHEETS_REDIRECT_URL - AIRTABLE_CLIENT_ID - ENTERPRISE_LICENSE_KEY - - DEFAULT_TEAM_ID + - DEFAULT_ORGANIZATION_ID - ONBOARDING_DISABLED - CUSTOMER_IO_API_KEY - CUSTOMER_IO_SITE_ID diff --git a/packages/database/jsonTypes.ts b/packages/database/jsonTypes.ts index 886c15b7af..727cf28b0b 100644 --- a/packages/database/jsonTypes.ts +++ b/packages/database/jsonTypes.ts @@ -1,5 +1,6 @@ import { TActionClassNoCodeConfig } from "@formbricks/types/actionClasses"; import { TIntegrationConfig } from "@formbricks/types/integration"; +import { TOrganizationBilling } from "@formbricks/types/organizations"; import { TProductStyling } from "@formbricks/types/product"; import { TResponseData, TResponseMeta, TResponsePersonAttributes } from "@formbricks/types/responses"; import { TBaseFilters } from "@formbricks/types/segment"; @@ -14,7 +15,6 @@ import { TSurveyVerifyEmail, TSurveyWelcomeCard, } from "@formbricks/types/surveys"; -import { TTeamBilling } from "@formbricks/types/teams"; import { TUserNotificationSettings } from "@formbricks/types/user"; declare global { @@ -34,7 +34,7 @@ declare global { export type SurveyClosedMessage = TSurveyClosedMessage; export type SurveySingleUse = TSurveySingleUse; export type SurveyVerifyEmail = TSurveyVerifyEmail; - export type TeamBilling = TTeamBilling; + export type OrganizationBilling = TOrganizationBilling; export type UserNotificationSettings = TUserNotificationSettings; export type SegmentFilter = TBaseFilters; export type Styling = TProductStyling; diff --git a/packages/database/migrations/20240516122752_rename_teams_to_organizations/migration.sql b/packages/database/migrations/20240516122752_rename_teams_to_organizations/migration.sql new file mode 100644 index 0000000000..5e663d94a1 --- /dev/null +++ b/packages/database/migrations/20240516122752_rename_teams_to_organizations/migration.sql @@ -0,0 +1,28 @@ +-- Rename table from "Team" to "Organization" +ALTER TABLE "Team" RENAME TO "Organization"; +ALTER TABLE "Organization" RENAME CONSTRAINT "Team_pkey" TO "Organization_pkey"; + +-- Rename column in the "Product" table +ALTER TABLE "Product" RENAME COLUMN "teamId" TO "organizationId"; + +-- Rename column in the "Invite" table +ALTER TABLE "Invite" RENAME COLUMN "teamId" TO "organizationId"; + +-- Rename column in the "Membership" table +ALTER TABLE "Membership" RENAME COLUMN "teamId" TO "organizationId"; + +-- Rename foreign key constraints +ALTER TABLE "Invite" RENAME CONSTRAINT "Invite_teamId_fkey" TO "Invite_organizationId_fkey"; +ALTER TABLE "Membership" RENAME CONSTRAINT "Membership_teamId_fkey" TO "Membership_organizationId_fkey"; +ALTER TABLE "Product" RENAME CONSTRAINT "Product_teamId_fkey" TO "Product_organizationId_fkey"; + +-- Rename indexes +ALTER INDEX "Invite_email_teamId_idx" RENAME TO "Invite_email_organizationId_idx"; +ALTER INDEX "Invite_teamId_idx" RENAME TO "Invite_organizationId_idx"; +ALTER INDEX "Membership_teamId_idx" RENAME TO "Membership_organizationId_idx"; +ALTER INDEX "Product_teamId_idx" RENAME TO "Product_organizationId_idx"; +ALTER INDEX "Product_teamId_name_key" RENAME TO "Product_organizationId_name_key"; + +-- Drop and recreate primary key on Membership table +ALTER TABLE "Membership" DROP CONSTRAINT "Membership_pkey"; +ALTER TABLE "Membership" ADD CONSTRAINT "Membership_pkey" PRIMARY KEY ("userId", "organizationId"); diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 82ab12a623..0f405016b4 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -422,8 +422,8 @@ model Product { createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") name String - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - teamId String + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) + organizationId String environments Environment[] brandColor String? highlightBorderColor String? @@ -441,19 +441,19 @@ model Product { /// [Logo] logo Json? - @@unique([teamId, name]) - @@index([teamId]) + @@unique([organizationId, name]) + @@index([organizationId]) } -model Team { +model Organization { id String @id @default(cuid()) createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") name String memberships Membership[] products Product[] - /// @zod.custom(imports.ZTeamBilling) - /// [TeamBilling] + /// @zod.custom(imports.ZOrganizationBilling) + /// [OrganizationBilling] billing Json @default("{\"stripeCustomerId\": null, \"features\": {\"inAppSurvey\": {\"status\": \"inactive\", \"unlimited\": false}, \"linkSurvey\": {\"status\": \"inactive\", \"unlimited\": false}, \"userTargeting\": {\"status\": \"inactive\", \"unlimited\": false}}}") invites Invite[] } @@ -467,35 +467,35 @@ enum MembershipRole { } model Membership { - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - teamId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId String - accepted Boolean @default(false) - role MembershipRole + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) + organizationId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + accepted Boolean @default(false) + role MembershipRole - @@id([userId, teamId]) + @@id([userId, organizationId]) @@index([userId]) - @@index([teamId]) + @@index([organizationId]) } model Invite { - id String @id @default(uuid()) - email String - name String? - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - teamId String - creator User @relation("inviteCreatedBy", fields: [creatorId], references: [id]) - creatorId String - acceptor User? @relation("inviteAcceptedBy", fields: [acceptorId], references: [id], onDelete: Cascade) - acceptorId String? - accepted Boolean @default(false) - createdAt DateTime @default(now()) - expiresAt DateTime - role MembershipRole @default(admin) + id String @id @default(uuid()) + email String + name String? + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) + organizationId String + creator User @relation("inviteCreatedBy", fields: [creatorId], references: [id]) + creatorId String + acceptor User? @relation("inviteAcceptedBy", fields: [acceptorId], references: [id], onDelete: Cascade) + acceptorId String? + accepted Boolean @default(false) + createdAt DateTime @default(now()) + expiresAt DateTime + role MembershipRole @default(admin) - @@index([email, teamId]) - @@index([teamId]) + @@index([email, organizationId]) + @@index([organizationId]) } model ApiKey { diff --git a/packages/database/zod-utils.ts b/packages/database/zod-utils.ts index 14432218a8..2225477a1d 100644 --- a/packages/database/zod-utils.ts +++ b/packages/database/zod-utils.ts @@ -25,5 +25,5 @@ export { } from "@formbricks/types/surveys"; export { ZSegmentFilters } from "@formbricks/types/segment"; -export { ZTeamBilling } from "@formbricks/types/teams"; +export { ZOrganizationBilling } from "@formbricks/types/organizations"; export { ZUserNotificationSettings } from "@formbricks/types/user"; diff --git a/packages/ee/RoleManagement/components/EditMembershipRole.tsx b/packages/ee/RoleManagement/components/EditMembershipRole.tsx index cca09136f4..ab0bfb5d84 100644 --- a/packages/ee/RoleManagement/components/EditMembershipRole.tsx +++ b/packages/ee/RoleManagement/components/EditMembershipRole.tsx @@ -23,7 +23,7 @@ import { TransferOwnershipModal } from "./TransferOwnershipModal"; interface Role { isAdminOrOwner: boolean; memberRole: TMembershipRole; - teamId: string; + organizationId: string; memberId?: string; memberName: string; userId: string; @@ -35,7 +35,7 @@ interface Role { export const EditMembershipRole = ({ isAdminOrOwner, memberRole, - teamId, + organizationId, memberId, memberName, userId, @@ -55,11 +55,11 @@ export const EditMembershipRole = ({ try { if (memberAccepted && memberId) { - await updateMembershipAction(memberId, teamId, { role }); + await updateMembershipAction(memberId, organizationId, { role }); } if (inviteId) { - await updateInviteAction(inviteId, teamId, { role }); + await updateInviteAction(inviteId, organizationId, { role }); } } catch (error) { toast.error("Something went wrong"); @@ -73,7 +73,7 @@ export const EditMembershipRole = ({ setLoading(true); try { if (memberId) { - await transferOwnershipAction(teamId, memberId); + await transferOwnershipAction(organizationId, memberId); } setLoading(false); diff --git a/packages/ee/RoleManagement/components/TransferOwnershipModal.tsx b/packages/ee/RoleManagement/components/TransferOwnershipModal.tsx index a5bb073c6e..fcd5559bfa 100644 --- a/packages/ee/RoleManagement/components/TransferOwnershipModal.tsx +++ b/packages/ee/RoleManagement/components/TransferOwnershipModal.tsx @@ -39,10 +39,10 @@ export const TransferOwnershipModal = ({
  • - There can only be one owner of each team. If you transfer your ownership to {memberName}, - you will lose all of your ownership rights. + There can only be one owner of each organization. If you transfer your ownership to{" "} + {memberName}, you will lose all of your ownership rights.
  • -
  • When you transfer the ownership, you will remain an Admin of the team.
  • +
  • When you transfer the ownership, you will remain an Admin of the organization.
-
Create team
+
Create organization
- Create a new team to handle a different set of products. + Create a new organization to handle a different set of products.
- +
- + setTeamName(e.target.value)} + value={organizationName} + onChange={(e) => setOrganizationName(e.target.value)} />
@@ -88,8 +88,8 @@ export const CreateTeamModal = ({ open, setOpen }: CreateTeamModalProps) => { }}> Cancel -
diff --git a/packages/ui/PricingCard/index.tsx b/packages/ui/PricingCard/index.tsx index f8499b3b53..3e6e8ecc38 100644 --- a/packages/ui/PricingCard/index.tsx +++ b/packages/ui/PricingCard/index.tsx @@ -1,6 +1,6 @@ import { CheckIcon } from "lucide-react"; -import { TTeam } from "@formbricks/types/teams"; +import { TOrganization } from "@formbricks/types/organizations"; import { Badge } from "../Badge"; import { BillingSlider } from "../BillingSlider"; @@ -12,7 +12,7 @@ export const PricingCard = ({ featureName, monthlyPrice, actionText, - team, + organization, metric, sliderValue, sliderLimit, @@ -28,7 +28,7 @@ export const PricingCard = ({ featureName: string; monthlyPrice: number; actionText: string; - team: TTeam; + organization: TOrganization; metric?: string; sliderValue?: number; sliderLimit?: number; @@ -43,13 +43,13 @@ export const PricingCard = ({ onUpgrade: any; onUnsubscribe: any; }) => { - const featureNameKey = featureName as keyof typeof team.billing.features; + const featureNameKey = featureName as keyof typeof organization.billing.features; return (

{title}

- {team.billing.features[featureNameKey].status === "active" ? ( - team.billing.features[featureNameKey].unlimited ? ( + {organization.billing.features[featureNameKey].status === "active" ? ( + organization.billing.features[featureNameKey].unlimited ? ( ) : ( <> @@ -62,7 +62,7 @@ export const PricingCard = ({ ) - ) : team.billing.features[featureNameKey].status === "cancelled" ? ( + ) : organization.billing.features[featureNameKey].status === "cancelled" ? ( ) : null} @@ -71,7 +71,7 @@ export const PricingCard = ({ {metric && perMetricCharge && (
- {team.billing.features[featureNameKey].unlimited ? ( + {organization.billing.features[featureNameKey].unlimited ? (

Usage this month: {sliderValue} {metric} @@ -94,7 +94,7 @@ export const PricingCard = ({

- {team.billing.features[featureNameKey].status === "inactive" && ( + {organization.billing.features[featureNameKey].status === "inactive" && (

You're on the Free plan in {title}.
Upgrade now to unlock the following: @@ -119,9 +119,9 @@ export const PricingCard = ({

- {!team.billing.features[featureNameKey].unlimited && ( + {!organization.billing.features[featureNameKey].unlimited && (
- {team.billing.features[featureNameKey].status !== "inactive" ? ( + {organization.billing.features[featureNameKey].status !== "inactive" ? (
{perMetricCharge ? ( <> @@ -158,7 +158,7 @@ export const PricingCard = ({ )}
)} - {team.billing.features[featureNameKey].status === "inactive" && ( + {organization.billing.features[featureNameKey].status === "inactive" && ( diff --git a/turbo.json b/turbo.json index da84783eaf..a9bc538082 100644 --- a/turbo.json +++ b/turbo.json @@ -59,8 +59,8 @@ "AZUREAD_CLIENT_ID", "AZUREAD_CLIENT_SECRET", "AZUREAD_TENANT_ID", - "DEFAULT_TEAM_ID", - "DEFAULT_TEAM_ROLE", + "DEFAULT_ORGANIZATION_ID", + "DEFAULT_ORGANIZATION_ROLE", "ONBOARDING_DISABLED", "CRON_SECRET", "CUSTOM_CACHE_DISABLED",