diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index 42d2a379..918919a1 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, 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'; @@ -30,6 +22,7 @@ import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/po import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto'; import { AuthService } from '@gitroom/helpers/auth/auth.service'; import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto'; import { NotEnoughScopes, RefreshToken, @@ -538,4 +531,35 @@ export class IntegrationsController { return this._integrationService.deleteChannel(org.id, id); } + + @Get('/plug/list') + async getPlugList() { + return { plugs: this._integrationManager.getAllPlugs() }; + } + + @Get('/:id/plugs') + async getPlugsByIntegrationId( + @Param('id') id: string, + @GetOrgFromRequest() org: Organization + ) { + return this._integrationService.getPlugsByIntegrationId(org.id, id); + } + + @Post('/:id/plugs') + async postPlugsByIntegrationId( + @Param('id') id: string, + @GetOrgFromRequest() org: Organization, + @Body() body: PlugDto + ) { + return this._integrationService.createOrUpdatePlug(org.id, id, body); + } + + @Put('/plugs/:id/activate') + async changePlugActivation( + @Param('id') id: string, + @GetOrgFromRequest() org: Organization, + @Body('status') status: boolean + ) { + return this._integrationService.changePlugActivation(org.id, id, status); + } } diff --git a/apps/frontend/src/app/(site)/plugs/page.tsx b/apps/frontend/src/app/(site)/plugs/page.tsx new file mode 100644 index 00000000..64a417e3 --- /dev/null +++ b/apps/frontend/src/app/(site)/plugs/page.tsx @@ -0,0 +1,19 @@ +import { Plugs } from '@gitroom/frontend/components/plugs/plugs'; + +export const dynamic = 'force-dynamic'; + +import { Metadata } from 'next'; +import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; + +export const metadata: Metadata = { + title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Plugs`, + description: '', +}; + +export default async function Index() { + return ( + <> + + + ); +} diff --git a/apps/frontend/src/components/layout/top.menu.tsx b/apps/frontend/src/components/layout/top.menu.tsx index e750963c..264fb2d4 100644 --- a/apps/frontend/src/components/layout/top.menu.tsx +++ b/apps/frontend/src/components/layout/top.menu.tsx @@ -44,14 +44,9 @@ export const useMenuItems = () => { ] : []), { - name: 'Marketplace', - icon: 'marketplace', - path: '/marketplace', - }, - { - name: 'Messages', - icon: 'messages', - path: '/messages', + name: 'Plugs', + icon: 'plugs', + path: '/plugs', }, { name: 'Billing', diff --git a/apps/frontend/src/components/plugs/plug.tsx b/apps/frontend/src/components/plugs/plug.tsx new file mode 100644 index 00000000..8982f3a9 --- /dev/null +++ b/apps/frontend/src/components/plugs/plug.tsx @@ -0,0 +1,316 @@ +'use client'; +import { + PlugSettings, + PlugsInterface, + usePlugs, +} from '@gitroom/frontend/components/plugs/plugs.context'; +import { Button } from '@gitroom/react/form/button'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import useSWR, { mutate } from 'swr'; +import { useModals } from '@mantine/modals'; +import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; +import { + FormProvider, + SubmitHandler, + useForm, + useFormContext, +} from 'react-hook-form'; +import { Input } from '@gitroom/react/form/input'; +import { CopilotTextarea } from '@copilotkit/react-textarea'; +import clsx from 'clsx'; +import { string, object } from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Slider } from '@gitroom/react/form/slider'; +import { useToaster } from '@gitroom/react/toaster/toaster'; + +export function convertBackRegex(s: string) { + const matches = s.match(/\/(.*)\/([a-z]*)/); + const pattern = matches?.[1] || ''; + const flags = matches?.[2] || ''; + + return new RegExp(pattern, flags); +} + +export const TextArea: FC<{ name: string; placeHolder: string }> = (props) => { + const form = useFormContext(); + const { onChange, onBlur, ...all } = form.register(props.name); + const value = form.watch(props.name); + + return ( + <> + + { + onChange({ target: { name: props.name, value: e.target.value } }); + }} + autosuggestionsConfig={{ + textareaPurpose: `Assist me in writing social media posts.`, + chatApiConfigs: {}, + }} + /> +
+ {form?.formState?.errors?.[props.name]?.message as string} +
+ + ); +}; + +export const PlugPop: FC<{ + plug: PlugsInterface; + settings: PlugSettings; + data?: { + activated: boolean; + data: string; + id: string; + integrationId: string; + organizationId: string; + plugFunction: string; + }; +}> = (props) => { + const { plug, settings, data } = props; + const { closeAll } = useModals(); + const fetch = useFetch(); + const toaster = useToaster(); + + const values = useMemo(() => { + if (!data?.data) { + return {}; + } + + return JSON.parse(data.data).reduce((acc: any, current: any) => { + return { + ...acc, + [current.name]: current.value, + }; + }, {} as any); + }, []); + + const yupSchema = useMemo(() => { + return object( + plug.fields.reduce((acc, field) => { + return { + ...acc, + [field.name]: field.validation + ? string().matches(convertBackRegex(field.validation), { + message: 'Invalid value', + }) + : null, + }; + }, {}) + ); + }, []); + + const form = useForm({ + resolver: yupResolver(yupSchema), + values, + mode: 'all', + }); + + const submit: SubmitHandler = useCallback(async (data) => { + await fetch(`/integrations/${settings.providerId}/plugs`, { + method: 'POST', + body: JSON.stringify({ + func: plug.methodName, + fields: Object.keys(data).map((key) => ({ + name: key, + value: data[key], + })), + }), + }); + + toaster.show('Plug updated', 'success'); + closeAll(); + }, []); + + return ( + +
+
+
+
+
+ +
+ +
+
{plug.description}
+
+ {plug.fields.map((field) => ( +
+ {field.type === 'richtext' ? ( +