diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index fb3eee3b..8980ab27 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -1,13 +1,5 @@ import { - Body, - Controller, - Delete, - Get, - Param, - Post, - Put, - Query, - UseFilters, + Body, Controller, Delete, Get, HttpException, Param, Post, Put, Query, UseFilters } from '@nestjs/common'; import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto'; @@ -261,7 +253,7 @@ export class IntegrationsController { throw new Error('Invalid integration'); } - let newList: any[] | {none: true} = []; + let newList: any[] | { none: true } = []; try { newList = (await this.functionIntegration(org, body)) || []; } catch (err) { @@ -298,7 +290,7 @@ export class IntegrationsController { image: p.image, label: p.name, })), - ...newList as any[], + ...(newList as any[]), ], (p) => p.id ).filter((f) => f.label && f.id); @@ -487,6 +479,18 @@ export class IntegrationsController { validName = `Channel_${String(id).slice(0, 8)}`; } } + + if ( + process.env.STRIPE_PUBLISHABLE_KEY && + org.isTrailing && + !!(await this._integrationService.checkPreviousConnections( + org.id, + String(id) + )) + ) { + throw new HttpException('', 412); + } + return this._integrationService.createOrUpdateIntegration( additionalSettings, !!integrationProvider.oneTimeToken, diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx index a72c4b1a..bf83b7ed 100644 --- a/apps/frontend/src/components/launches/add.provider.component.tsx +++ b/apps/frontend/src/components/launches/add.provider.component.tsx @@ -1,14 +1,14 @@ 'use client'; import { useModals } from '@mantine/modals'; -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { Input } from '@gitroom/react/form/input'; import { FieldValues, FormProvider, useForm } from 'react-hook-form'; import { Button } from '@gitroom/react/form/button'; import { classValidatorResolver } from '@hookform/resolvers/class-validator'; import { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useToaster } from '@gitroom/react/toaster/toaster'; @@ -42,9 +42,16 @@ export const AddProviderButton: FC<{ update?: () => void; }> = (props) => { const { update } = props; + const query = useSearchParams(); const add = useAddProvider(update); const t = useT(); + useEffect(() => { + if (query.get('onboarding')) { + add(); + } + }, []); + return ( + + + + ); +}; +export const PreConditionComponent: FC = () => { + const modal = useModals(); + const query = useSearchParams(); + useEffect(() => { + if (query.get('precondition')) { + modal.openModal({ + title: '', + withCloseButton: false, + classNames: { + modal: 'text-textColor', + }, + size: 'auto', + children: ( + + + + ), + }); + } + }, []); + return null; +}; diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx index 14063dbb..abcfe4c5 100644 --- a/apps/frontend/src/components/new-layout/layout.component.tsx +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -38,6 +38,7 @@ import { ChromeExtensionComponent } from '@gitroom/frontend/components/layout/ch import NotificationComponent from '@gitroom/frontend/components/notifications/notification.component'; import { BillingAfter } from '@gitroom/frontend/components/new-layout/billing.after'; import { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector'; +import { PreConditionComponent } from '@gitroom/frontend/components/layout/pre-condition.component'; const jakartaSans = Plus_Jakarta_Sans({ weight: ['600', '500'], @@ -79,8 +80,8 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => { + - {user.tier !== 'FREE' && }
({ @@ -68,6 +68,19 @@ export class IntegrationRepository { }); } + async checkPreviousConnections(org: string, id: string) { + const findIt = await this._integration.model.integration.findFirst({ + where: { + organizationId: { + not: org, + }, + rootInternalId: id.split('_').pop(), + }, + }); + + return findIt; + } + updateProviderSettings(org: string, id: string, settings: string) { return this._integration.model.integration.update({ where: { diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts index d7519354..a54758e6 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts @@ -73,6 +73,10 @@ export class IntegrationService { ); } + checkPreviousConnections(org: string, id: string) { + return this._integrationRepository.checkPreviousConnections(org, id); + } + async createOrUpdateIntegration( additionalSettings: | {