diff --git a/apps/backend/src/api/api.module.ts b/apps/backend/src/api/api.module.ts
index 4fb94fbf..e96f13d3 100644
--- a/apps/backend/src/api/api.module.ts
+++ b/apps/backend/src/api/api.module.ts
@@ -29,6 +29,7 @@ import { TrackService } from '@gitroom/nestjs-libraries/track/track.service';
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
import { WebhookController } from '@gitroom/backend/api/routes/webhooks.controller';
+import { SignatureController } from '@gitroom/backend/api/routes/signature.controller';
const authenticatedController = [
UsersController,
@@ -44,11 +45,10 @@ const authenticatedController = [
CopilotController,
AgenciesController,
WebhookController,
+ SignatureController,
];
@Module({
- imports: [
- UploadModule,
- ],
+ imports: [UploadModule],
controllers: [
RootController,
StripeController,
diff --git a/apps/backend/src/api/routes/signature.controller.ts b/apps/backend/src/api/routes/signature.controller.ts
new file mode 100644
index 00000000..2162443b
--- /dev/null
+++ b/apps/backend/src/api/routes/signature.controller.ts
@@ -0,0 +1,39 @@
+import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
+import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
+import { Organization } from '@prisma/client';
+import { ApiTags } from '@nestjs/swagger';
+import { SignatureService } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.service';
+import { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';
+
+@ApiTags('Signatures')
+@Controller('/signatures')
+export class SignatureController {
+ constructor(private _signatureService: SignatureService) {}
+
+ @Get('/')
+ async getSignatures(@GetOrgFromRequest() org: Organization) {
+ return this._signatureService.getSignaturesByOrgId(org.id);
+ }
+
+ @Get('/default')
+ async getDefaultSignature(@GetOrgFromRequest() org: Organization) {
+ return (await this._signatureService.getDefaultSignature(org.id)) || {};
+ }
+
+ @Post('/')
+ async createSignature(
+ @GetOrgFromRequest() org: Organization,
+ @Body() body: SignatureDto
+ ) {
+ return this._signatureService.createOrUpdateSignature(org.id, body);
+ }
+
+ @Put('/:id')
+ async updateSignature(
+ @Param('id') id: string,
+ @GetOrgFromRequest() org: Organization,
+ @Body() body: SignatureDto
+ ) {
+ return this._signatureService.createOrUpdateSignature(org.id, body, id);
+ }
+}
diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx
index 9f076e80..a51caf74 100644
--- a/apps/frontend/src/components/launches/calendar.tsx
+++ b/apps/frontend/src/components/launches/calendar.tsx
@@ -490,7 +490,9 @@ export const CalendarColumn: FC<{
[integrations]
);
- const addModal = useCallback(() => {
+ const addModal = useCallback(async () => {
+ const signature = await (await fetch('/signatures/default')).json();
+
modal.openModal({
closeOnClickOutside: false,
closeOnEscape: false,
@@ -503,6 +505,11 @@ export const CalendarColumn: FC<{
allIntegrations={integrations.map((p) => ({ ...p }))}
integrations={integrations.slice(0).map((p) => ({ ...p }))}
mutate={reloadCalendarView}
+ {...(signature?.id
+ ? {
+ onlyValues: [{ content: '\n' + signature.content }],
+ }
+ : {})}
date={
randomHour ? getDate.hour(Math.floor(Math.random() * 24)) : getDate
}
diff --git a/apps/frontend/src/components/launches/editor.tsx b/apps/frontend/src/components/launches/editor.tsx
index 224ca0e3..11cf4918 100644
--- a/apps/frontend/src/components/launches/editor.tsx
+++ b/apps/frontend/src/components/launches/editor.tsx
@@ -11,6 +11,7 @@ import EmojiPicker from 'emoji-picker-react';
import { Theme } from 'emoji-picker-react';
import { BoldText } from '@gitroom/frontend/components/launches/bold.text';
import { UText } from '@gitroom/frontend/components/launches/u.text';
+import { SignatureBox } from '@gitroom/frontend/components/signature';
export const Editor = forwardRef<
RefMDEditor,
@@ -61,6 +62,7 @@ export const Editor = forwardRef<
return (
<>
+
}> = (props) => {
const { isGeneral } = useVariables();
@@ -76,6 +78,22 @@ export const SettingsPopup: FC<{ getRef?: Ref }> = (props) => {
close();
}, []);
+ const defaultValueForTabs = useMemo(() => {
+ if (user?.tier?.team_members && isGeneral) {
+ return 'teams';
+ }
+
+ if (user?.tier?.webhooks) {
+ return 'webhooks';
+ }
+
+ if (user?.tier?.public_api && isGeneral) {
+ return 'api';
+ }
+
+ return 'teams';
+ }, []);
+
useEffect(() => {
loadProfile();
}, []);
@@ -92,118 +110,139 @@ export const SettingsPopup: FC<{ getRef?: Ref }> = (props) => {
!getRef && 'rounded-[4px]'
)}
>
- {/*{!getRef && (*/}
- {/* */}
- {/*)}*/}
- {/*{!getRef && (*/}
- {/* Profile Settings
*/}
- {/*)}*/}
- {/**/}
- {/*
Profile
*/}
- {/*
*/}
- {/* Add profile information*/}
- {/*
*/}
- {/*
*/}
- {/**/}
- {/*
*/}
- {/*
*/}
- {/* */}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/* {!!picture?.path && (*/}
- {/*

*/}
- {/* )}*/}
- {/*
*/}
- {/*
*/}
- {/*
Profile Picture
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/* */}
- {/*
*/}
- {/*
*/}
- {/*{!getRef && (*/}
- {/* */}
- {/* */}
- {/*
*/}
- {/*)}*/}
- {!!user?.tier?.team_members && isGeneral && }
- {!!user?.tier?.webhooks && }
- {!!user?.tier?.public_api && isGeneral && showLogout && (
-
+
+
+ {/* Profile */}
+ {!!user?.tier?.team_members && isGeneral && (
+ Teams
+ )}
+ {!!user?.tier?.webhooks && (
+ Webhooks
+ )}
+ Signatures
+ {!!user?.tier?.public_api && isGeneral && showLogout && (
+ Public API
+ )}
+
+
+ {/*
+
+
Profile
+
+ Add profile information
+
+
+
+
+
+
+
+
+
+ {!!picture?.path && (
+

+ )}
+
+
+
Profile Picture
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+ {!!user?.tier?.team_members && isGeneral && (
+
+
+
+ )}
+
+ {!!user?.tier?.webhooks && (
+
+
+
+ )}
+
+
+
+
+
+ {!!user?.tier?.public_api && isGeneral && showLogout && (
+
+
+
+ )}
+
+
+ {showLogout && (
+
+
+
)}
- {showLogout && }
diff --git a/apps/frontend/src/components/settings/signatures.component.tsx b/apps/frontend/src/components/settings/signatures.component.tsx
new file mode 100644
index 00000000..daa36bb2
--- /dev/null
+++ b/apps/frontend/src/components/settings/signatures.component.tsx
@@ -0,0 +1,208 @@
+import React, { FC, Fragment, useCallback } from 'react';
+import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
+import useSWR from 'swr';
+import { Button } from '@gitroom/react/form/button';
+import clsx from 'clsx';
+import { useModals } from '@mantine/modals';
+import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
+import { array, boolean, object, string } from 'yup';
+import { FormProvider, useForm } from 'react-hook-form';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { CopilotTextarea } from '@copilotkit/react-textarea';
+import { Select } from '@gitroom/react/form/select';
+import { useToaster } from '@gitroom/react/toaster/toaster';
+
+export const SignaturesComponent: FC<{
+ appendSignature?: (value: string) => void;
+}> = (props) => {
+ const { appendSignature } = props;
+ const fetch = useFetch();
+ const modal = useModals();
+ const load = useCallback(async () => {
+ return (await fetch('/signatures')).json();
+ }, []);
+
+ const { data, mutate } = useSWR('signatures', load);
+
+ const addSignature = useCallback(
+ (data?: any) => () => {
+ modal.openModal({
+ title: '',
+ withCloseButton: false,
+ classNames: {
+ modal: 'bg-transparent text-textColor',
+ },
+ children: ,
+ });
+ },
+ [mutate]
+ );
+
+ return (
+
+
Signatures
+
+ You can add signatures to your account to be used in your posts.
+
+
+
+ {!!data?.length && (
+
+
Content
+
Auto Add?
+ {!!appendSignature &&
Actions
}
+
Edit
+
Delete
+ {data?.map((p: any) => (
+
+
+
+ {p.content.slice(0,15) + '...'}
+
+
+
+
+ {p.autoAdd ? 'Yes' : 'No'}
+
+
+ {!!appendSignature && (
+
+
+
+ )}
+
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+const details = object().shape({
+ content: string().required(),
+ autoAdd: boolean().required(),
+});
+
+const AddOrRemoveSignature: FC<{ data?: any; reload: () => void }> = (
+ props
+) => {
+ const { data, reload } = props;
+ const toast = useToaster();
+ const fetch = useFetch();
+
+ const form = useForm({
+ resolver: yupResolver(details),
+ values: {
+ content: data?.content || '',
+ autoAdd: data?.autoAdd || false,
+ },
+ });
+
+ const text = form.watch('content');
+ const autoAdd = form.watch('autoAdd');
+ const modal = useModals();
+
+ const callBack = useCallback(
+ async (values: any) => {
+ await fetch(data?.id ? `/signatures/${data.id}` : '/signatures', {
+ method: data?.id ? 'PUT' : 'POST',
+ body: JSON.stringify(values),
+ });
+
+ toast.show(
+ data?.id
+ ? 'Webhook updated successfully'
+ : 'Webhook added successfully',
+ 'success'
+ );
+
+ modal.closeModal(modal.modals[modal.modals.length - 1].id);
+ reload();
+ },
+ [data, modal]
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/frontend/src/components/signature.tsx b/apps/frontend/src/components/signature.tsx
new file mode 100644
index 00000000..4f5b51be
--- /dev/null
+++ b/apps/frontend/src/components/signature.tsx
@@ -0,0 +1,79 @@
+import { FC, useCallback, useState } from 'react';
+import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
+import { SignaturesComponent } from '@gitroom/frontend/components/settings/signatures.component';
+import { Transforms } from 'slate';
+
+export const SignatureBox: FC<{ editor: any }> = ({ editor }) => {
+ const [showModal, setShowModal] = useState(false);
+ const addSignature = useCallback(() => {
+ setShowModal(true);
+ }, [showModal]);
+
+ const appendValue = (val: string) => {
+ Transforms.insertText(editor, "\n" + val);
+ setShowModal(false);
+ };
+
+ return (
+ <>
+ {showModal && (
+ setShowModal(false)}
+ />
+ )}
+
+ >
+ );
+};
+
+export const SignatureModal: FC<{
+ close: () => void;
+ appendSignature: (sign: string) => void;
+}> = (props) => {
+ const { close, appendSignature } = props;
+ return (
+
+ );
+};
diff --git a/libraries/nestjs-libraries/src/database/prisma/database.module.ts b/libraries/nestjs-libraries/src/database/prisma/database.module.ts
index 610eb7db..c977211e 100644
--- a/libraries/nestjs-libraries/src/database/prisma/database.module.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/database.module.ts
@@ -31,6 +31,8 @@ import { TrackService } from '@gitroom/nestjs-libraries/track/track.service';
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
import { WebhooksRepository } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.repository';
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
+import { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';
+import { SignatureService } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.service';
@Global()
@Module({
@@ -57,6 +59,8 @@ import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webho
PostsRepository,
StripeService,
MessagesRepository,
+ SignatureRepository,
+ SignatureService,
MediaService,
MediaRepository,
ItemUserRepository,
diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
index 019c308c..71db47c0 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
@@ -193,18 +193,6 @@ export class PostsService {
...morePosts,
]);
- if (!finalPost?.postId || !finalPost?.releaseURL) {
- await this._postRepository.changeState(firstPost.id, 'ERROR');
- await this._notificationService.inAppNotification(
- firstPost.organizationId,
- `Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`,
- `An error occurred while posting on ${firstPost.integration?.providerIdentifier}`,
- true
- );
-
- return;
- }
-
if (firstPost?.intervalInDays) {
this._workerServiceProducer.emit('post', {
id,
@@ -217,6 +205,18 @@ export class PostsService {
});
}
+ if (!finalPost?.postId || !finalPost?.releaseURL) {
+ await this._postRepository.changeState(firstPost.id, 'ERROR');
+ await this._notificationService.inAppNotification(
+ firstPost.organizationId,
+ `Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`,
+ `An error occurred while posting on ${firstPost.integration?.providerIdentifier}`,
+ true
+ );
+
+ return;
+ }
+
if (firstPost.submittedForOrderId) {
this._workerServiceProducer.emit('submit', {
payload: {
@@ -610,7 +610,7 @@ export class PostsService {
: body.date,
post,
body.tags,
- body.inter,
+ body.inter
);
if (!posts?.length) {
diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma
index 9d294d0b..0136f1ec 100644
--- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma
+++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma
@@ -35,6 +35,7 @@ model Organization {
customers Customer[]
webhooks Webhooks[]
tags Tags[]
+ signatures Signatures[]
}
model Tags {
@@ -328,6 +329,21 @@ model Integration {
@@unique([organizationId, internalId])
}
+model Signatures {
+ id String @id @default(uuid())
+ organizationId String
+ organization Organization @relation(fields: [organizationId], references: [id])
+ content String
+ autoAdd Boolean
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime?
+
+ @@index([createdAt])
+ @@index([organizationId])
+ @@index([deletedAt])
+}
+
model Comments {
id String @id @default(uuid())
content String
diff --git a/libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts b/libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts
new file mode 100644
index 00000000..9ccf3f0f
--- /dev/null
+++ b/libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts
@@ -0,0 +1,48 @@
+import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
+import { Injectable } from '@nestjs/common';
+import { v4 as uuidv4 } from 'uuid';
+import { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';
+
+@Injectable()
+export class SignatureRepository {
+ constructor(private _signatures: PrismaRepository<'signatures'>) {}
+
+ getSignaturesByOrgId(orgId: string) {
+ return this._signatures.model.signatures.findMany({
+ where: { organizationId: orgId, deletedAt: null },
+ });
+ }
+
+ getDefaultSignature(orgId: string) {
+ return this._signatures.model.signatures.findFirst({
+ where: { organizationId: orgId, autoAdd: true, deletedAt: null },
+ });
+ }
+
+ async createOrUpdateSignature(
+ orgId: string,
+ signature: SignatureDto,
+ id?: string
+ ) {
+ const values = {
+ organizationId: orgId,
+ content: signature.content,
+ autoAdd: signature.autoAdd,
+ };
+
+ const { id: updatedId } = await this._signatures.model.signatures.upsert({
+ where: { id: id || uuidv4(), organizationId: orgId },
+ update: values,
+ create: values,
+ });
+
+ if (values.autoAdd) {
+ await this._signatures.model.signatures.updateMany({
+ where: { organizationId: orgId, id: { not: updatedId } },
+ data: { autoAdd: false },
+ });
+ }
+
+ return { id: updatedId };
+ }
+}
diff --git a/libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts b/libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts
new file mode 100644
index 00000000..9b7ea895
--- /dev/null
+++ b/libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@nestjs/common';
+import { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';
+import { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';
+
+@Injectable()
+export class SignatureService {
+ constructor(private _signatureRepository: SignatureRepository) {}
+
+ getSignaturesByOrgId(orgId: string) {
+ return this._signatureRepository.getSignaturesByOrgId(orgId);
+ }
+
+ getDefaultSignature(orgId: string) {
+ return this._signatureRepository.getDefaultSignature(orgId);
+ }
+
+ createOrUpdateSignature(orgId: string, signature: SignatureDto, id?: string) {
+ return this._signatureRepository.createOrUpdateSignature(orgId, signature, id);
+ }
+}
diff --git a/libraries/nestjs-libraries/src/dtos/signature/signature.dto.ts b/libraries/nestjs-libraries/src/dtos/signature/signature.dto.ts
new file mode 100644
index 00000000..3b69cf37
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/signature/signature.dto.ts
@@ -0,0 +1,11 @@
+import { IsBoolean, IsDefined, IsString } from 'class-validator';
+
+export class SignatureDto {
+ @IsString()
+ @IsDefined()
+ content: string;
+
+ @IsBoolean()
+ @IsDefined()
+ autoAdd: boolean;
+}