Merge branch 'main' into sentry-ai/mcp
This commit is contained in:
commit
dadd21b321
|
|
@ -50,6 +50,10 @@ GITHUB_CLIENT_ID=""
|
|||
GITHUB_CLIENT_SECRET=""
|
||||
BEEHIIVE_API_KEY=""
|
||||
BEEHIIVE_PUBLICATION_ID=""
|
||||
LISTMONK_DOMAIN=""
|
||||
LISTMONK_USER=""
|
||||
LISTMONK_API_KEY=""
|
||||
LISTMONK_LIST_ID=""
|
||||
THREADS_APP_ID=""
|
||||
THREADS_APP_SECRET=""
|
||||
FACEBOOK_APP_ID=""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<p align="center">
|
||||
<a href="https://affiliate.postiz.com">
|
||||
<img src="https://github.com/user-attachments/assets/af9f47b3-e20c-402b-bd11-02f39248d738" />
|
||||
<a href="https://github.com/growchief/growchief">
|
||||
<img alt="automate" src="https://github.com/user-attachments/assets/d760188d-8d56-4b05-a6c1-c57e67ef25cd" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,14 @@
|
|||
import { Body, Controller, Get, Param, Post, Req, Res } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';
|
||||
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
||||
|
|
@ -11,6 +21,10 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
|||
import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';
|
||||
import { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service';
|
||||
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
|
||||
import { Readable, pipeline } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const pump = promisify(pipeline);
|
||||
|
||||
@ApiTags('Public')
|
||||
@Controller('/public')
|
||||
|
|
@ -136,4 +150,46 @@ export class PublicController {
|
|||
console.log('cryptoPost', body, path);
|
||||
return this._nowpayments.processPayment(path, body);
|
||||
}
|
||||
|
||||
@Get('/stream')
|
||||
async streamFile(
|
||||
@Query('url') url: string,
|
||||
@Res() res: Response,
|
||||
@Req() req: Request
|
||||
) {
|
||||
if (!url.endsWith('mp4')) {
|
||||
return res.status(400).send('Invalid video URL');
|
||||
}
|
||||
|
||||
const ac = new AbortController();
|
||||
const onClose = () => ac.abort();
|
||||
req.on('aborted', onClose);
|
||||
res.on('close', onClose);
|
||||
|
||||
const r = await fetch(url, { signal: ac.signal });
|
||||
|
||||
if (!r.ok && r.status !== 206) {
|
||||
res.status(r.status);
|
||||
throw new Error(`Upstream error: ${r.statusText}`);
|
||||
}
|
||||
|
||||
const type = r.headers.get('content-type') ?? 'application/octet-stream';
|
||||
res.setHeader('Content-Type', type);
|
||||
|
||||
const contentRange = r.headers.get('content-range');
|
||||
if (contentRange) res.setHeader('Content-Range', contentRange);
|
||||
|
||||
const len = r.headers.get('content-length');
|
||||
if (len) res.setHeader('Content-Length', len);
|
||||
|
||||
const acceptRanges = r.headers.get('accept-ranges') ?? 'bytes';
|
||||
res.setHeader('Accept-Ranges', acceptRanges);
|
||||
|
||||
if (r.status === 206) res.status(206); // Partial Content for range responses
|
||||
|
||||
try {
|
||||
await pump(Readable.fromWeb(r.body as any), res);
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export class MainMcp {
|
|||
@McpTool({
|
||||
toolName: 'POSTIZ_SCHEDULE_POST',
|
||||
zod: {
|
||||
type: eenum(['draft', 'scheduled']),
|
||||
type: eenum(['draft', 'schedule']),
|
||||
configId: string(),
|
||||
generatePictures: boolean(),
|
||||
date: string().describe('UTC TIME'),
|
||||
|
|
|
|||
|
|
@ -34,3 +34,4 @@ export class PublicApiModule implements NestModule {
|
|||
consumer.apply(PublicAuthMiddleware).forRoutes(...authenticatedController);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/o
|
|||
import { AuthService as AuthChecker } from '@gitroom/helpers/auth/auth.service';
|
||||
import { ProvidersFactory } from '@gitroom/backend/services/auth/providers/providers.factory';
|
||||
import dayjs from 'dayjs';
|
||||
import { NewsletterService } from '@gitroom/nestjs-libraries/services/newsletter.service';
|
||||
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
||||
import { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';
|
||||
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
|
||||
import { NewsletterService } from '@gitroom/nestjs-libraries/newsletter/newsletter.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export class CheckMissingQueues {
|
|||
)
|
||||
).filter((p) => !p.isJob);
|
||||
|
||||
|
||||
for (const job of notExists) {
|
||||
this._workerServiceProducer.emit('post', {
|
||||
id: job.id,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@ const nextConfig = {
|
|||
experimental: {
|
||||
proxyTimeout: 90_000,
|
||||
},
|
||||
// Document-Policy header for browser profiling
|
||||
async headers() {
|
||||
return [{
|
||||
source: "/:path*",
|
||||
headers: [{
|
||||
key: "Document-Policy",
|
||||
value: "js-profiling",
|
||||
}, ],
|
||||
}, ];
|
||||
},
|
||||
reactStrictMode: false,
|
||||
transpilePackages: ['crypto-hash'],
|
||||
// Enable production sourcemaps for Sentry
|
||||
|
|
@ -60,7 +70,7 @@ export default withSentryConfig(nextConfig, {
|
|||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
|
||||
|
||||
// Sourcemap configuration optimized for monorepo
|
||||
sourcemaps: {
|
||||
disable: false,
|
||||
|
|
@ -68,7 +78,7 @@ export default withSentryConfig(nextConfig, {
|
|||
assets: [
|
||||
".next/static/**/*.js",
|
||||
".next/static/**/*.js.map",
|
||||
".next/server/**/*.js",
|
||||
".next/server/**/*.js",
|
||||
".next/server/**/*.js.map",
|
||||
],
|
||||
ignore: [
|
||||
|
|
@ -97,7 +107,7 @@ export default withSentryConfig(nextConfig, {
|
|||
telemetry: false,
|
||||
silent: process.env.NODE_ENV === 'production',
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
|
||||
|
||||
// Error handling for CI/CD
|
||||
errorHandler: (error) => {
|
||||
console.warn("Sentry build error occurred:", error.message);
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -10,6 +10,16 @@ import utc from 'dayjs/plugin/utc';
|
|||
import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
|
||||
import { CopyClient } from '@gitroom/frontend/components/preview/copy.client';
|
||||
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
|
||||
import dynamicLoad from 'next/dynamic';
|
||||
|
||||
const RenderPreviewDate = dynamicLoad(
|
||||
() =>
|
||||
import('@gitroom/frontend/components/preview/render.preview.date').then(
|
||||
(mod) => mod.RenderPreviewDate
|
||||
),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
dayjs.extend(utc);
|
||||
export const metadata: Metadata = {
|
||||
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Preview`,
|
||||
|
|
@ -91,11 +101,8 @@ export default async function Auth({
|
|||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
{t('publication_date', 'Publication Date:')}
|
||||
{dayjs
|
||||
.utc(post[0].publishDate)
|
||||
.local()
|
||||
.format('MMMM D, YYYY h:mm A')}
|
||||
{t('publication_date', 'Publication Date:')}{' '}
|
||||
<RenderPreviewDate date={post[0].publishDate} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
--new-col-color: #2c2b2b;
|
||||
--new-menu-dots: #696868;
|
||||
--new-menu-hover: #fff;
|
||||
--menu-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.50);
|
||||
--menu-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.5);
|
||||
--popup-color: rgba(65, 64, 66, 0.3);
|
||||
}
|
||||
.light {
|
||||
--new-bgColor: #f0f2f4;
|
||||
|
|
@ -33,8 +34,8 @@
|
|||
--new-boxFocused: #ebe8ff;
|
||||
--new-textColor: 14 14 14;
|
||||
--new-blockSeparator: #f2f2f4;
|
||||
--new-btn-simple: #ECEEF1;
|
||||
--new-btn-text: #0E0E0E;
|
||||
--new-btn-simple: #eceef1;
|
||||
--new-btn-text: #0e0e0e;
|
||||
--new-btn-primary: #612bd3;
|
||||
--new-ai-btn: #d82d7e;
|
||||
--new-box-hover: #f4f6f8;
|
||||
|
|
@ -43,15 +44,19 @@
|
|||
--new-table-text: #777b7f;
|
||||
--new-table-text-focused: #fc69ff;
|
||||
--new-small-strips: #191818;
|
||||
--new-big-strips: #F5F7F9;
|
||||
--new-col-color: #EFF1F3;
|
||||
--new-big-strips: #f5f7f9;
|
||||
--new-col-color: #eff1f3;
|
||||
--new-menu-dots: #696868;
|
||||
--new-menu-hover: #000;
|
||||
--menu-shadow: -22px 83px 24px 0 rgba(55, 52, 75, 0.00), -14px 53px 22px 0 rgba(55, 52, 75, 0.01), -8px 30px 19px 0 rgba(55, 52, 75, 0.05), -3px 13px 14px 0 rgba(55, 52, 75, 0.09), -1px 3px 8px 0 rgba(55, 52, 75, 0.10);
|
||||
--menu-shadow: -22px 83px 24px 0 rgba(55, 52, 75, 0),
|
||||
-14px 53px 22px 0 rgba(55, 52, 75, 0.01),
|
||||
-8px 30px 19px 0 rgba(55, 52, 75, 0.05),
|
||||
-3px 13px 14px 0 rgba(55, 52, 75, 0.09),
|
||||
-1px 3px 8px 0 rgba(55, 52, 75, 0.1);
|
||||
--popup-color: rgba(55, 37, 97, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
.dark {
|
||||
--color-primary: #0e0e0e;
|
||||
|
|
@ -193,4 +198,4 @@
|
|||
--color-custom55: #d5d7e1;
|
||||
--color-modalCustom: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -357,6 +357,10 @@ body * {
|
|||
// /*right: -5rem !important;*/
|
||||
//}
|
||||
|
||||
.trz {
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.uppy-FileInput-container {
|
||||
@apply cursor-pointer font-[500] flex justify-center items-center gap-[4px] text-[12px] rounded-[4px] w-[107px] h-[25px] text-textColor border-[2px];
|
||||
@apply bg-customColor3 border-customColor21;
|
||||
|
|
@ -672,3 +676,6 @@ html[dir='rtl'] [dir='ltr'] {
|
|||
}
|
||||
}
|
||||
}
|
||||
.blur-xs {
|
||||
filter: blur(4px);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import React, { FC, Fragment, useCallback, useMemo, useState } from 'react';
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import useSWR from 'swr';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
|
@ -28,11 +28,8 @@ export const Autopost: FC = () => {
|
|||
const addWebhook = useCallback(
|
||||
(data?: any) => () => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
title: data ? 'Edit Autopost' : 'Add Autopost',
|
||||
withCloseButton: true,
|
||||
children: <AddOrEditWebhook data={data} reload={mutate} />,
|
||||
});
|
||||
},
|
||||
|
|
@ -296,29 +293,7 @@ export const AddOrEditWebhook: FC<{
|
|||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(callBack)}>
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0 w-[500px]">
|
||||
<TopTitle title={data ? 'Edit autopost' : 'Add autopost'} />
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={modal.closeAll}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 pt-0">
|
||||
<div>
|
||||
<Input
|
||||
label="Title"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { useSWRConfig } from 'swr';
|
|||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Textarea } from '@gitroom/react/form/textarea';
|
||||
import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import React, { FC, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
|
|
@ -24,17 +24,9 @@ export const useAddProvider = (update?: () => void) => {
|
|||
return useCallback(async () => {
|
||||
const data = await (await fetch('/integrations')).json();
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'text-textColor',
|
||||
},
|
||||
size: 'auto',
|
||||
children: (
|
||||
<ModalWrapperComponent title="Add Channel">
|
||||
<AddProviderComponent update={update} {...data} />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
title: 'Add Channel',
|
||||
withCloseButton: true,
|
||||
children: <AddProviderComponent update={update} {...data} />,
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
|
@ -266,6 +258,7 @@ export const CustomVariables: FC<{
|
|||
});
|
||||
const submit = useCallback(
|
||||
async (data: FieldValues) => {
|
||||
modals.closeAll();
|
||||
gotoUrl(
|
||||
`/integrations/social/${identifier}?state=nostate&code=${Buffer.from(
|
||||
JSON.stringify(data)
|
||||
|
|
@ -349,20 +342,18 @@ export const AddProviderComponent: FC<{
|
|||
await fetch(`/integrations/social/${identifier}`)
|
||||
).json();
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'Web3 provider',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title="Web3 provider">
|
||||
<Web3Providers
|
||||
onComplete={(code, newState) => {
|
||||
window.location.href = `/integrations/social/${identifier}?code=${code}&state=${newState}`;
|
||||
}}
|
||||
nonce={url}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<Web3Providers
|
||||
onComplete={(code, newState) => {
|
||||
window.location.href = `/integrations/social/${identifier}?code=${code}&state=${newState}`;
|
||||
}}
|
||||
nonce={url}
|
||||
/>
|
||||
),
|
||||
});
|
||||
return;
|
||||
|
|
@ -388,35 +379,29 @@ export const AddProviderComponent: FC<{
|
|||
if (isExternal) {
|
||||
modal.closeAll();
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'URL',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title="URL">
|
||||
<UrlModal gotoUrl={gotoIntegration} />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
children: <UrlModal gotoUrl={gotoIntegration} />,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (customFields) {
|
||||
modal.closeAll();
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'Add Provider',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title="Add Provider">
|
||||
<CustomVariables
|
||||
identifier={identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={customFields}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<CustomVariables
|
||||
identifier={identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={customFields}
|
||||
/>
|
||||
),
|
||||
});
|
||||
return;
|
||||
|
|
@ -425,9 +410,7 @@ export const AddProviderComponent: FC<{
|
|||
},
|
||||
[]
|
||||
);
|
||||
const close = useCallback(() => {
|
||||
modal.closeAll();
|
||||
}, []);
|
||||
|
||||
const showApiButton = useCallback(
|
||||
(identifier: string, name: string) => async () => {
|
||||
modal.openModal({
|
||||
|
|
@ -449,7 +432,6 @@ export const AddProviderComponent: FC<{
|
|||
return (
|
||||
<div className="w-full flex flex-col gap-[20px] rounded-[4px] relative">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="pt-[16px] pb-[10px]">{t('social', 'Social')}</h2>
|
||||
<div className="grid grid-cols-5 gap-[10px] justify-items-center justify-center">
|
||||
{social.map((item) => (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import React, { FC, FormEventHandler, useCallback, useState } from 'react';
|
||||
import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import 'dayjs/locale/ar';
|
|||
import 'dayjs/locale/tr';
|
||||
import 'dayjs/locale/vi';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import clsx from 'clsx';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
|
||||
|
|
@ -215,7 +215,8 @@ export const WeekView = () => {
|
|||
<div
|
||||
className={clsx(
|
||||
'text-[14px] font-[600] flex items-center justify-center gap-[6px]',
|
||||
day.day === newDayjs().format('L') && 'text-newTableTextFocused'
|
||||
day.day === newDayjs().format('L') &&
|
||||
'text-newTableTextFocused'
|
||||
)}
|
||||
>
|
||||
{day.day === newDayjs().format('L') && (
|
||||
|
|
@ -391,7 +392,9 @@ export const CalendarColumn: FC<{
|
|||
|
||||
const isBeforeNow = useMemo(() => {
|
||||
const originalUtc = getDate.startOf('hour');
|
||||
return originalUtc.startOf('hour').isBefore(newDayjs().startOf('hour').utc());
|
||||
return originalUtc
|
||||
.startOf('hour')
|
||||
.isBefore(newDayjs().startOf('hour').utc());
|
||||
}, [getDate, num]);
|
||||
|
||||
const { start, stop } = useInterval(
|
||||
|
|
@ -462,8 +465,10 @@ export const CalendarColumn: FC<{
|
|||
: Fragment;
|
||||
modal.openModal({
|
||||
closeOnClickOutside: false,
|
||||
removeLayout: true,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
askClose: true,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] text-textColor',
|
||||
},
|
||||
|
|
@ -515,28 +520,24 @@ export const CalendarColumn: FC<{
|
|||
? undefined
|
||||
: await new Promise((resolve) => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: t('select_set', 'Select a Set'),
|
||||
closeOnClickOutside: true,
|
||||
askClose: true,
|
||||
closeOnEscape: true,
|
||||
withCloseButton: false,
|
||||
withCloseButton: true,
|
||||
onClose: () => resolve('exit'),
|
||||
classNames: {
|
||||
modal: 'text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title={t('select_set', 'Select a Set')}>
|
||||
<SetSelectionModal
|
||||
sets={sets}
|
||||
onSelect={(selectedSet) => {
|
||||
resolve(selectedSet);
|
||||
modal.closeAll();
|
||||
}}
|
||||
onContinueWithoutSet={() => {
|
||||
resolve(undefined);
|
||||
modal.closeAll();
|
||||
}}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<SetSelectionModal
|
||||
sets={sets}
|
||||
onSelect={(selectedSet) => {
|
||||
resolve(selectedSet);
|
||||
modal.closeAll();
|
||||
}}
|
||||
onContinueWithoutSet={() => {
|
||||
resolve(undefined);
|
||||
modal.closeAll();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
});
|
||||
|
|
@ -547,6 +548,8 @@ export const CalendarColumn: FC<{
|
|||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
removeLayout: true,
|
||||
askClose: true,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] text-textColor',
|
||||
},
|
||||
|
|
@ -586,17 +589,14 @@ export const CalendarColumn: FC<{
|
|||
const openStatistics = useCallback(
|
||||
(id: string) => () => {
|
||||
modal.openModal({
|
||||
title: t('statistics', 'Statistics'),
|
||||
closeOnClickOutside: true,
|
||||
closeOnEscape: true,
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px]',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title={t('statistics', 'Statistics')}>
|
||||
<StatisticsModal postId={id} />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
children: <StatisticsModal postId={id} />,
|
||||
size: '80%',
|
||||
// title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Textarea } from '@gitroom/react/form/textarea';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import clsx from 'clsx';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { Autocomplete } from '@mantine/core';
|
||||
import useSWR from 'swr';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { FC, useCallback, useMemo, useState } from 'react';
|
|||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
|
||||
import { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';
|
||||
|
|
@ -153,6 +153,8 @@ const FirstStep: FC = (props) => {
|
|||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
removeLayout: true,
|
||||
askClose: true,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] bg-transparent text-textColor',
|
||||
},
|
||||
|
|
@ -301,7 +303,7 @@ export const GeneratorComponent = () => {
|
|||
return;
|
||||
}
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'Generate Posts',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
|
|
@ -309,9 +311,7 @@ export const GeneratorComponent = () => {
|
|||
size: 'xl',
|
||||
children: (
|
||||
<CalendarWeekProvider {...all}>
|
||||
<ModalWrapperComponent title="Generate Posts">
|
||||
<GeneratorPopup />
|
||||
</ModalWrapperComponent>
|
||||
<GeneratorPopup />
|
||||
</CalendarWeekProvider>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
const postUrlEmitter = new EventEmitter();
|
||||
|
||||
export const MediaSettingsLayout = () => {
|
||||
|
|
@ -97,7 +98,8 @@ export const CreateThumbnail: FC<{
|
|||
altText?: string;
|
||||
onAltTextChange?: (altText: string) => void;
|
||||
}> = (props) => {
|
||||
const { onSelect, media, altText, onAltTextChange } = props;
|
||||
const { onSelect, media } = props;
|
||||
const { backendUrl } = useVariables();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
|
|
@ -211,7 +213,9 @@ export const CreateThumbnail: FC<{
|
|||
<div className="relative bg-black rounded-lg overflow-hidden">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={media.path}
|
||||
src={
|
||||
backendUrl + '/public/stream?url=' + encodeURIComponent(media.path)
|
||||
}
|
||||
className="w-full h-[200px] object-contain"
|
||||
onLoadedMetadata={handleLoadedMetadata}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
|
|
@ -358,163 +362,132 @@ export const MediaComponentInner: FC<{
|
|||
}, [altText, newThumbnail, thumbnail, thumbnailTimestamp]);
|
||||
|
||||
return (
|
||||
<div className="text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade justify-center flex bg-black/40">
|
||||
<div className="w-full h-full relative">
|
||||
<div className="w-[500px] bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] absolute left-[50%] top-[100px] -translate-x-[50%]">
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
<TopTitle title={'Media Setting'} />
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[10px] flex flex-col gap-[20px]">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<label className="text-sm text-textColor font-medium">
|
||||
Alt Text (for accessibility)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={altText}
|
||||
onChange={(e) => setAltText(e.target.value)}
|
||||
placeholder="Describe the image/video content..."
|
||||
className="w-full px-3 py-2 bg-fifth border border-tableBorder rounded-lg text-textColor placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-forth focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
{media?.path.indexOf('mp4') > -1 && (
|
||||
<>
|
||||
{/* Alt Text Input */}
|
||||
<div>
|
||||
{!isEditingThumbnail ? (
|
||||
<div className="flex flex-col">
|
||||
{/* Show existing thumbnail if it exists */}
|
||||
{(newThumbnail || thumbnail) && (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<span className="text-sm text-textColor">
|
||||
Current Thumbnail:
|
||||
</span>
|
||||
<img
|
||||
src={newThumbnail || thumbnail}
|
||||
alt="Current thumbnail"
|
||||
className="max-w-full max-h-[500px] object-contain rounded-lg border border-tableBorder"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-[10px] flex flex-col gap-[20px]">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<label className="text-sm text-textColor font-medium">
|
||||
Alt Text (for accessibility)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={altText}
|
||||
onChange={(e) => setAltText(e.target.value)}
|
||||
placeholder="Describe the image/video content..."
|
||||
className="w-full px-3 py-2 bg-fifth border border-tableBorder rounded-lg text-textColor placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-forth focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
{media?.path.indexOf('mp4') > -1 && (
|
||||
<>
|
||||
{/* Alt Text Input */}
|
||||
<div>
|
||||
{!isEditingThumbnail ? (
|
||||
<div className="flex flex-col">
|
||||
{/* Show existing thumbnail if it exists */}
|
||||
{(newThumbnail || thumbnail) && (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<span className="text-sm text-textColor">
|
||||
Current Thumbnail:
|
||||
</span>
|
||||
<img
|
||||
src={newThumbnail || thumbnail}
|
||||
alt="Current thumbnail"
|
||||
className="max-w-full max-h-[500px] object-contain rounded-lg border border-tableBorder"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={() => setIsEditingThumbnail(true)}
|
||||
className="bg-third text-textColor px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-tableBorder"
|
||||
>
|
||||
{media.thumbnail || newThumbnail
|
||||
? 'Edit Thumbnail'
|
||||
: 'Create Thumbnail'}
|
||||
</button>
|
||||
{(thumbnail || newThumbnail) && (
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
setNewThumbnail(null);
|
||||
setThumbnail(null);
|
||||
}}
|
||||
className="bg-red-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-red-700"
|
||||
>
|
||||
Clear Thumbnail
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{/* Back button */}
|
||||
<div className="flex justify-start">
|
||||
<button
|
||||
onClick={() => setIsEditingThumbnail(false)}
|
||||
className="text-textColor hover:text-white transition-colors flex items-center space-x-2"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 12H5M12 19L5 12L12 5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Thumbnail Editor */}
|
||||
<CreateThumbnail
|
||||
onSelect={(blob: Blob, timestampMs: number) => {
|
||||
// Convert blob to base64 or handle as needed
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// You can handle the result here - for now just call onSelect with the blob URL
|
||||
const url = URL.createObjectURL(blob);
|
||||
setNewThumbnail(url);
|
||||
setThumbnailTimestamp(timestampMs);
|
||||
setIsEditingThumbnail(false);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}}
|
||||
media={media}
|
||||
altText={altText}
|
||||
onAltTextChange={setAltText}
|
||||
/>
|
||||
</div>
|
||||
{/* Action Buttons */}
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={() => setIsEditingThumbnail(true)}
|
||||
className="bg-third text-textColor px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-tableBorder"
|
||||
>
|
||||
{media.thumbnail || newThumbnail
|
||||
? 'Edit Thumbnail'
|
||||
: 'Create Thumbnail'}
|
||||
</button>
|
||||
{(thumbnail || newThumbnail) && (
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
setNewThumbnail(null);
|
||||
setThumbnail(null);
|
||||
}}
|
||||
className="bg-red-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-red-700"
|
||||
>
|
||||
Clear Thumbnail
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{/* Back button */}
|
||||
<div className="flex justify-start">
|
||||
<button
|
||||
onClick={() => setIsEditingThumbnail(false)}
|
||||
className="text-textColor hover:text-white transition-colors flex items-center space-x-2"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 12H5M12 19L5 12L12 5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!isEditingThumbnail && (
|
||||
<div className="flex space-x-2 !mt-[20px]">
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={onClose}
|
||||
className="flex-1 bg-gray-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={save}
|
||||
className="flex-1 bg-forth text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
{/* Thumbnail Editor */}
|
||||
<CreateThumbnail
|
||||
onSelect={(blob: Blob, timestampMs: number) => {
|
||||
// Convert blob to base64 or handle as needed
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// You can handle the result here - for now just call onSelect with the blob URL
|
||||
const url = URL.createObjectURL(blob);
|
||||
setNewThumbnail(url);
|
||||
setThumbnailTimestamp(timestampMs);
|
||||
setIsEditingThumbnail(false);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}}
|
||||
media={media}
|
||||
altText={altText}
|
||||
onAltTextChange={setAltText}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isEditingThumbnail && (
|
||||
<div className="flex space-x-2 !mt-[20px]">
|
||||
<button
|
||||
disabled={loading}
|
||||
onClick={onClose}
|
||||
className="flex-1 bg-gray-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={save}
|
||||
className="flex-1 bg-forth text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { useClickOutside } from '@mantine/hooks';
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TimeTable } from '@gitroom/frontend/components/launches/time.table';
|
||||
import {
|
||||
Integrations,
|
||||
|
|
@ -137,18 +137,12 @@ export const Menu: FC<{
|
|||
(integration) => integration.id === id
|
||||
);
|
||||
modal.openModal({
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[600px] bg-transparent text-textColor',
|
||||
},
|
||||
size: '100%',
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
closeOnClickOutside: false,
|
||||
children: (
|
||||
<ModalWrapperComponent title="Time Table Slots" ask={true}>
|
||||
<TimeTable integration={findIntegration!} mutate={mutate} />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
askClose: true,
|
||||
title: 'Time Table Slots',
|
||||
children: <TimeTable integration={findIntegration!} mutate={mutate} />,
|
||||
});
|
||||
setShow(false);
|
||||
}, [integrations]);
|
||||
|
|
@ -175,6 +169,8 @@ export const Menu: FC<{
|
|||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
removeLayout: true,
|
||||
askClose: true,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] bg-transparent text-textColor',
|
||||
},
|
||||
|
|
@ -254,40 +250,36 @@ export const Menu: FC<{
|
|||
classNames: {
|
||||
modal: 'md',
|
||||
},
|
||||
title: '',
|
||||
title: 'Move / Add to customer',
|
||||
withCloseButton: false,
|
||||
closeOnEscape: true,
|
||||
closeOnClickOutside: true,
|
||||
children: (
|
||||
<ModalWrapperComponent title="Move / Add to customer">
|
||||
<CustomerModal
|
||||
// @ts-ignore
|
||||
integration={findIntegration}
|
||||
onClose={() => {
|
||||
mutate();
|
||||
toast.show('Customer Updated', 'success');
|
||||
}}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<CustomerModal
|
||||
// @ts-ignore
|
||||
integration={findIntegration}
|
||||
onClose={() => {
|
||||
mutate();
|
||||
toast.show('Customer Updated', 'success');
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
setShow(false);
|
||||
}, [integrations]);
|
||||
const updateCredentials = useCallback(() => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'Custom URL',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'md',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title="Custom URL">
|
||||
<CustomVariables
|
||||
identifier={findIntegration.identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={findIntegration.customFields}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<CustomVariables
|
||||
identifier={findIntegration.identifier}
|
||||
gotoUrl={(url: string) => router.push(url)}
|
||||
variables={findIntegration.customFields}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
|
|
@ -21,7 +21,7 @@ export const NewPost = () => {
|
|||
? undefined
|
||||
: await new Promise((resolve) => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: t('select_set', 'Select a Set'),
|
||||
closeOnClickOutside: true,
|
||||
closeOnEscape: true,
|
||||
withCloseButton: false,
|
||||
|
|
@ -30,19 +30,17 @@ export const NewPost = () => {
|
|||
modal: 'text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ModalWrapperComponent title={t('select_set', 'Select a Set')}>
|
||||
<SetSelectionModal
|
||||
sets={sets}
|
||||
onSelect={(selectedSet) => {
|
||||
resolve(selectedSet);
|
||||
modal.closeAll();
|
||||
}}
|
||||
onContinueWithoutSet={() => {
|
||||
resolve(undefined);
|
||||
modal.closeAll();
|
||||
}}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<SetSelectionModal
|
||||
sets={sets}
|
||||
onSelect={(selectedSet) => {
|
||||
resolve(selectedSet);
|
||||
modal.closeAll();
|
||||
}}
|
||||
onContinueWithoutSet={() => {
|
||||
resolve(undefined);
|
||||
modal.closeAll();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
});
|
||||
|
|
@ -53,6 +51,8 @@ export const NewPost = () => {
|
|||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
removeLayout: true,
|
||||
askClose: true,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] bg-transparent text-textColor',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,10 +56,12 @@ const ActionControls = ({ store }: any) => {
|
|||
body: formData,
|
||||
})
|
||||
).json();
|
||||
close.setMedia([{
|
||||
id: data.id,
|
||||
path: data.path,
|
||||
}]);
|
||||
close.setMedia([
|
||||
{
|
||||
id: data.id,
|
||||
path: data.path,
|
||||
},
|
||||
]);
|
||||
close.close();
|
||||
}}
|
||||
>
|
||||
|
|
@ -102,63 +104,34 @@ const Polonto: FC<{
|
|||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className="fixed start-0 top-0 bg-primary/80 z-[300] w-full min-h-full px-[60px] animate-fade">
|
||||
<div className="w-full h-full bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative">
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
<TopTitle title="Design Media" />
|
||||
</div>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-white text-black relative z-[400] polonto">
|
||||
<CloseContext.Provider
|
||||
value={{
|
||||
close: () => closeModal(),
|
||||
setMedia,
|
||||
}}
|
||||
>
|
||||
<PolotnoContainer
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '700px',
|
||||
<div className="bg-white text-black relative z-[400] polonto">
|
||||
<CloseContext.Provider
|
||||
value={{
|
||||
close: () => closeModal(),
|
||||
setMedia,
|
||||
}}
|
||||
>
|
||||
<PolotnoContainer
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '700px',
|
||||
}}
|
||||
>
|
||||
<SidePanelWrap>
|
||||
<SidePanel store={store} sections={features} />
|
||||
</SidePanelWrap>
|
||||
<WorkspaceWrap>
|
||||
<Toolbar
|
||||
store={store}
|
||||
components={{
|
||||
ActionControls,
|
||||
}}
|
||||
>
|
||||
<SidePanelWrap>
|
||||
<SidePanel store={store} sections={features} />
|
||||
</SidePanelWrap>
|
||||
<WorkspaceWrap>
|
||||
<Toolbar
|
||||
store={store}
|
||||
components={{
|
||||
ActionControls,
|
||||
}}
|
||||
/>
|
||||
<Workspace store={store} />
|
||||
<ZoomButtons store={store} />
|
||||
</WorkspaceWrap>
|
||||
</PolotnoContainer>
|
||||
</CloseContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
/>
|
||||
<Workspace store={store} />
|
||||
<ZoomButtons store={store} />
|
||||
</WorkspaceWrap>
|
||||
</PolotnoContainer>
|
||||
</CloseContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { FC, Fragment, useCallback } from 'react';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import useSWR from 'swr';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import timezone from 'dayjs/plugin/timezone';
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
// @ts-ignore
|
||||
import useKeypress from 'react-use-keypress';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { sortBy } from 'lodash';
|
||||
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React, { FC, useMemo, useState, useCallback, useEffect } from 'react';
|
|||
import { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import { ButtonCaster } from '@gitroom/frontend/components/auth/providers/farcaster.provider';
|
||||
export const WrapcasterProvider: FC<Web3ProviderInterface> = (props) => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import '@neynar/react/dist/style.css';
|
|||
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React, { FC, useMemo, useState, useCallback, useEffect } from 'react';
|
|||
import { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import {
|
||||
NeynarAuthButton,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import {
|
||||
cookieName,
|
||||
fallbackLng,
|
||||
|
|
@ -70,7 +70,7 @@ export const ChangeLanguageComponent = () => {
|
|||
const handleLanguageChange = (language: string) => {
|
||||
setCookie(language);
|
||||
i18next.changeLanguage(language);
|
||||
modals.closeModal('change-language');
|
||||
modals.closeCurrent();
|
||||
};
|
||||
|
||||
// Function to get language name in its native script
|
||||
|
|
@ -123,16 +123,9 @@ export const LanguageComponent = () => {
|
|||
const t = useT();
|
||||
const openModal = () => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
modalId: 'change-language',
|
||||
children: (
|
||||
<ModalWrapperComponent title={t('change_language', 'Change Language')}>
|
||||
<ChangeLanguageComponent />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
size: 'lg',
|
||||
centered: true,
|
||||
title: t('change_language', 'Change Language'),
|
||||
withCloseButton: true,
|
||||
children: <ChangeLanguageComponent />,
|
||||
});
|
||||
};
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
<CopilotKit
|
||||
credentials="include"
|
||||
runtimeUrl={backendUrl + '/copilot/chat'}
|
||||
showDevConsole={false}
|
||||
>
|
||||
<MantineWrapper>
|
||||
{user.tier === 'FREE' && searchParams.get('check') && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,371 @@
|
|||
import { create } from 'zustand';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import React, {
|
||||
createContext,
|
||||
FC,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import clsx from 'clsx';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
interface OpenModalInterface {
|
||||
title?: string;
|
||||
closeOnClickOutside?: boolean;
|
||||
removeLayout?: boolean;
|
||||
closeOnEscape?: boolean;
|
||||
withCloseButton?: boolean;
|
||||
askClose?: boolean;
|
||||
onClose?: () => void;
|
||||
children: ReactNode | ((close: () => void) => ReactNode);
|
||||
classNames?: {
|
||||
modal?: string;
|
||||
};
|
||||
size?: string | number;
|
||||
height?: string | number;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface ModalManagerStoreInterface {
|
||||
closeById(id: string): void;
|
||||
openModal(params: OpenModalInterface): void;
|
||||
closeAll(): void;
|
||||
}
|
||||
|
||||
interface State extends ModalManagerStoreInterface {
|
||||
modalManager: Array<{ id: string } & OpenModalInterface>;
|
||||
}
|
||||
|
||||
const useModalStore = create<State>((set) => ({
|
||||
modalManager: [],
|
||||
openModal: (params) =>
|
||||
set((state) => ({
|
||||
modalManager: [
|
||||
...state.modalManager,
|
||||
{ id: params.id || makeId(20), ...params },
|
||||
],
|
||||
})),
|
||||
closeById: (id) =>
|
||||
set((state) => ({
|
||||
modalManager: state.modalManager.filter((modal) => modal.id !== id),
|
||||
})),
|
||||
closeAll: () => set({ modalManager: [] }),
|
||||
}));
|
||||
|
||||
const CurrentModalContext = createContext({ id: '' });
|
||||
|
||||
interface ModalManagerInterface extends ModalManagerStoreInterface {
|
||||
closeCurrent(): void;
|
||||
}
|
||||
|
||||
export const useModals = () => {
|
||||
const { closeAll, openModal, closeById } = useModalStore(
|
||||
useShallow((state) => ({
|
||||
openModal: state.openModal,
|
||||
closeById: state.closeById,
|
||||
closeAll: state.closeAll,
|
||||
}))
|
||||
);
|
||||
|
||||
const modalContext = useContext(CurrentModalContext);
|
||||
|
||||
return {
|
||||
openModal,
|
||||
closeAll,
|
||||
closeById,
|
||||
closeCurrent: () => {
|
||||
if (modalContext.id) {
|
||||
closeById(modalContext.id);
|
||||
}
|
||||
},
|
||||
} satisfies ModalManagerInterface;
|
||||
};
|
||||
|
||||
export const Component: FC<{
|
||||
closeModal: (id: string) => void;
|
||||
zIndex: number;
|
||||
isLast: boolean;
|
||||
modal: { id: string } & OpenModalInterface;
|
||||
}> = memo(({ isLast, modal, closeModal, zIndex }) => {
|
||||
const decision = useDecisionModal();
|
||||
const closeModalFunction = useCallback(async () => {
|
||||
if (modal.askClose) {
|
||||
const open = await decision.open();
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
modal?.onClose?.();
|
||||
closeModal(modal.id);
|
||||
}, [modal.id, closeModal]);
|
||||
|
||||
const RenderComponent = useMemo(() => {
|
||||
return typeof modal.children === 'function'
|
||||
? modal.children(closeModalFunction)
|
||||
: modal.children;
|
||||
}, [modal, closeModalFunction]);
|
||||
|
||||
useHotkeys(
|
||||
'Escape',
|
||||
() => {
|
||||
if (isLast) {
|
||||
closeModalFunction();
|
||||
}
|
||||
},
|
||||
[isLast, closeModalFunction]
|
||||
);
|
||||
|
||||
if (modal.removeLayout) {
|
||||
return (
|
||||
<div
|
||||
style={{ zIndex }}
|
||||
className={clsx(
|
||||
'fixed flex left-0 top-0 min-w-full min-h-full bg-popup transition-all animate-fadeIn overflow-y-auto pb-[50px] text-newTextColor',
|
||||
!isLast && '!overflow-hidden'
|
||||
)}
|
||||
>
|
||||
<div className="relative flex-1">
|
||||
<div className="absolute top-0 left-0 min-w-full min-h-full">
|
||||
<div
|
||||
className="mx-auto py-[48px]"
|
||||
{...(modal.size && { style: { width: modal.size } })}
|
||||
>
|
||||
{typeof modal.children === 'function'
|
||||
? modal.children(closeModalFunction)
|
||||
: modal.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CurrentModalContext.Provider value={{ id: modal.id }}>
|
||||
<div
|
||||
onClick={closeModalFunction}
|
||||
style={{ zIndex }}
|
||||
className="fixed flex left-0 top-0 min-w-full min-h-full bg-popup transition-all animate-fadeIn overflow-y-auto pb-[50px] text-newTextColor"
|
||||
>
|
||||
<div className="relative flex-1">
|
||||
<div className="absolute top-0 left-0 min-w-full min-h-full pt-[100px] pb-[100px]">
|
||||
<div
|
||||
className={clsx(
|
||||
!modal.removeLayout && 'gap-[40px] p-[32px]',
|
||||
'bg-newBgColorInner mx-auto flex flex-col w-fit rounded-[24px] relative',
|
||||
modal.size ? '' : 'min-w-[600px]'
|
||||
)}
|
||||
{...(modal.size && { style: { width: modal.size } })}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="text-[24px] font-[600] flex-1">
|
||||
{modal.title}
|
||||
</div>
|
||||
{typeof modal.withCloseButton === 'undefined' ||
|
||||
modal.withCloseButton ? (
|
||||
<div className="cursor-pointer">
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={closeModalFunction}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="whitespace-pre-line">{RenderComponent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CurrentModalContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export const ModalManagerInner: FC = () => {
|
||||
const { closeModal, modalManager } = useModalStore(
|
||||
useShallow((state) => ({
|
||||
closeModal: state.closeById,
|
||||
modalManager: state.modalManager,
|
||||
}))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalManager.length > 0) {
|
||||
document.querySelector('body')?.classList.add('overflow-hidden');
|
||||
Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>
|
||||
p.classList.add('blur-xs', 'pointer-events-none')
|
||||
);
|
||||
} else {
|
||||
document.querySelector('body')?.classList.remove('overflow-hidden');
|
||||
Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>
|
||||
p.classList.remove('blur-xs', 'pointer-events-none')
|
||||
);
|
||||
}
|
||||
}, [modalManager]);
|
||||
|
||||
if (modalManager.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`body, html { overflow: hidden !important; }`}</style>
|
||||
{modalManager.map((modal, index) => (
|
||||
<Component
|
||||
isLast={modalManager.length - 1 === index}
|
||||
key={modal.id}
|
||||
modal={modal}
|
||||
zIndex={200 + index}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const ModalManager: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<div>
|
||||
<ModalManagerEmitter />
|
||||
<ModalManagerInner />
|
||||
<div className="transition-all w-full">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
export const showModalEmitter = (params: ModalManagerInterface) => {
|
||||
emitter.emit('show', params);
|
||||
};
|
||||
|
||||
export const ModalManagerEmitter: FC = () => {
|
||||
const { showModal } = useModalStore(
|
||||
useShallow((state) => ({
|
||||
showModal: state.openModal,
|
||||
}))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
emitter.on('show', (params: OpenModalInterface) => {
|
||||
showModal(params);
|
||||
});
|
||||
|
||||
return () => {
|
||||
emitter.removeAllListeners('show');
|
||||
};
|
||||
}, []);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const DecisionModal: FC<{
|
||||
description: string;
|
||||
approveLabel: string;
|
||||
cancelLabel: string;
|
||||
resolution: (value: boolean) => void;
|
||||
}> = ({ description, cancelLabel, approveLabel, resolution }) => {
|
||||
const { closeCurrent } = useModals();
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div>{description}</div>
|
||||
<div className="flex gap-[12px] mt-[16px]">
|
||||
<Button
|
||||
onClick={() => {
|
||||
resolution(true);
|
||||
closeCurrent();
|
||||
}}
|
||||
>
|
||||
{approveLabel}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
resolution(false);
|
||||
closeCurrent();
|
||||
}}
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const decisionModalEmitter = new EventEmitter();
|
||||
|
||||
export const areYouSure = ({
|
||||
title = 'Are you sure?',
|
||||
description = 'Are you sure you want to close this modal?' as any,
|
||||
approveLabel = 'Yes',
|
||||
cancelLabel = 'No',
|
||||
} = {}): Promise<boolean> => {
|
||||
return new Promise<boolean>((newRes) => {
|
||||
decisionModalEmitter.emit('open', {
|
||||
title,
|
||||
description,
|
||||
approveLabel,
|
||||
cancelLabel,
|
||||
newRes,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const DecisionEverywhere: FC = () => {
|
||||
const decision = useDecisionModal();
|
||||
useEffect(() => {
|
||||
decisionModalEmitter.on('open', decision.open);
|
||||
}, []);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useDecisionModal = () => {
|
||||
const modals = useModals();
|
||||
const open = useCallback(
|
||||
({
|
||||
title = 'Are you sure?',
|
||||
description = 'Are you sure you want to close this modal?' as any,
|
||||
approveLabel = 'Yes',
|
||||
cancelLabel = 'No',
|
||||
newRes = undefined as any,
|
||||
} = {}) => {
|
||||
return new Promise<boolean>((res) => {
|
||||
modals.openModal({
|
||||
title,
|
||||
askClose: false,
|
||||
onClose: () => res(false),
|
||||
children: (
|
||||
<DecisionModal
|
||||
resolution={(value) => (newRes ? newRes(value) : res(value))}
|
||||
description={description}
|
||||
approveLabel={approveLabel}
|
||||
cancelLabel={cancelLabel}
|
||||
/>
|
||||
),
|
||||
});
|
||||
});
|
||||
},
|
||||
[modals]
|
||||
);
|
||||
|
||||
return { open };
|
||||
};
|
||||
|
|
@ -1,19 +1,26 @@
|
|||
import React, { FC, useEffect } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
|
||||
export const PreConditionComponentModal: FC = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-[16px]">
|
||||
<div className="whitespace-pre-line">
|
||||
This social channel was connected previously to another Postiz account.{'\n'}
|
||||
To continue, please fast-track your trial for an immediate charge.{'\n'}{'\n'}
|
||||
** Please be advised that the account will not eligible for a refund, and the charge is final.
|
||||
This social channel was connected previously to another Postiz account.
|
||||
{'\n'}
|
||||
To continue, please fast-track your trial for an immediate charge.{'\n'}
|
||||
{'\n'}
|
||||
** Please be advised that the account will not eligible for a refund,
|
||||
and the charge is final.
|
||||
</div>
|
||||
<div className="flex gap-[2px] justify-center">
|
||||
<Button onClick={() => window.location.href='/billing?finishTrial=true'}>Fast track - Charge me now</Button>
|
||||
<Button
|
||||
onClick={() => (window.location.href = '/billing?finishTrial=true')}
|
||||
>
|
||||
Fast track - Charge me now
|
||||
</Button>
|
||||
<Button secondary={true}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -25,17 +32,13 @@ export const PreConditionComponent: FC = () => {
|
|||
useEffect(() => {
|
||||
if (query.get('precondition')) {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: 'Suspicious activity detected',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'text-textColor',
|
||||
},
|
||||
size: 'auto',
|
||||
children: (
|
||||
<ModalWrapperComponent title="Suspicious activity detected">
|
||||
<PreConditionComponentModal />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
children: <PreConditionComponentModal />,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import React, {
|
||||
FC,
|
||||
Ref,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
|
@ -129,6 +128,39 @@ export const useMenuItem = () => {
|
|||
] satisfies MenuItemInterface[] as MenuItemInterface[];
|
||||
|
||||
const secondMenu = [
|
||||
// {
|
||||
// name: 'GrowChief',
|
||||
// icon: (
|
||||
// <svg
|
||||
// width="20"
|
||||
// height="21"
|
||||
// viewBox="0 0 50 28"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// data-tooltip-id="tooltip"
|
||||
// data-tooltip-content="New! Automate your X and LinkedIn outreach with GrowChief"
|
||||
// >
|
||||
// <path
|
||||
// d="M24.8789 0.191772C39.9967 0.198463 49.621 14.0845 49.6514 14.1283C49.6514 14.1283 40.0206 27.8931 24.8789 27.8998C9.73703 27.9062 0.189453 14.1283 0.189453 14.1283C0.235704 14.0609 9.77381 0.185332 24.8789 0.191772Z"
|
||||
// fill="none"
|
||||
// stroke="currentColor"
|
||||
// strokeWidth="3"
|
||||
// />
|
||||
//
|
||||
// <circle
|
||||
// cx="24.9189"
|
||||
// cy="14.2621"
|
||||
// r="9.1328"
|
||||
// fill="none"
|
||||
// stroke="currentColor"
|
||||
// strokeWidth="3"
|
||||
// />
|
||||
// </svg>
|
||||
// ),
|
||||
// path: 'https://growchief.com',
|
||||
// role: ['ADMIN', 'SUPERADMIN', 'USER'],
|
||||
// requireBilling: true,
|
||||
// },
|
||||
{
|
||||
name: t('affiliate', 'Affiliate'),
|
||||
icon: (
|
||||
|
|
@ -237,7 +269,7 @@ export const useMenuItem = () => {
|
|||
</svg>
|
||||
),
|
||||
path: '/settings',
|
||||
role: ['ADMIN', "USER", 'SUPERADMIN'],
|
||||
role: ['ADMIN', 'USER', 'SUPERADMIN'],
|
||||
},
|
||||
] satisfies MenuItemInterface[] as MenuItemInterface[];
|
||||
|
||||
|
|
@ -254,7 +286,7 @@ export const TopMenu: FC = () => {
|
|||
const { isGeneral, billingEnabled } = useVariables();
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-1 flex-col gap-[16px]">
|
||||
<div className="flex flex-1 flex-col gap-[16px] blurMe">
|
||||
{
|
||||
// @ts-ignore
|
||||
user?.orgId &&
|
||||
|
|
@ -286,7 +318,7 @@ export const TopMenu: FC = () => {
|
|||
))
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-col gap-[16px]">
|
||||
<div className="flex flex-col gap-[16px] blurMe">
|
||||
{secondMenu
|
||||
.filter((f) => {
|
||||
if (f.hide) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { capitalize, chunk, fill } from 'lodash';
|
||||
import useSWR from 'swr';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Textarea } from '@gitroom/react/form/textarea';
|
||||
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FC, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { MarketplaceProvider } from '@gitroom/frontend/components/marketplace/marketplace.provider';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { CustomSelect } from '@gitroom/react/form/custom.select';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import useSWR from 'swr';
|
|||
import { Input } from '@gitroom/react/form/input';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { OrderList } from '@gitroom/frontend/components/marketplace/order.list';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { countries } from '@gitroom/nestjs-libraries/services/stripe.country.list';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import useSWR from 'swr';
|
|||
import { capitalize } from 'lodash';
|
||||
import removeMd from 'remove-markdown';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Post as PrismaPost } from '@prisma/client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';
|
||||
|
|
|
|||
|
|
@ -30,9 +30,13 @@ import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
|||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { ThirdPartyMedia } from '@gitroom/frontend/components/third-parties/third-party.media';
|
||||
import { ReactSortable } from 'react-sortablejs';
|
||||
import { useMediaSettings } from '@gitroom/frontend/components/launches/helpers/media.settings.component';
|
||||
import {
|
||||
MediaComponentInner,
|
||||
useMediaSettings,
|
||||
} from '@gitroom/frontend/components/launches/helpers/media.settings.component';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
import { AiVideo } from '@gitroom/frontend/components/launches/ai.video';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
const Polonto = dynamic(
|
||||
() => import('@gitroom/frontend/components/launches/polonto')
|
||||
);
|
||||
|
|
@ -246,7 +250,7 @@ export const MediaBox: FC<{
|
|||
const dragAndDrop = useCallback(
|
||||
async (event: ClipboardEvent<HTMLDivElement> | File[]) => {
|
||||
if (!ref?.current?.setOptions) {
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
|
@ -564,12 +568,12 @@ export const MultiMediaComponent: FC<{
|
|||
dummy,
|
||||
} = props;
|
||||
const user = useUser();
|
||||
const modals = useModals();
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setCurrentMedia(value);
|
||||
}
|
||||
}, [value]);
|
||||
const [modal, setShowModal] = useState(false);
|
||||
const [mediaModal, setMediaModal] = useState(false);
|
||||
const [currentMedia, setCurrentMedia] = useState(value);
|
||||
const mediaDirectory = useMediaDirectory();
|
||||
|
|
@ -598,17 +602,14 @@ export const MultiMediaComponent: FC<{
|
|||
[currentMedia]
|
||||
);
|
||||
const showModal = useCallback(() => {
|
||||
if (!modal) {
|
||||
onOpen?.();
|
||||
} else {
|
||||
onClose?.();
|
||||
}
|
||||
setShowModal(!modal);
|
||||
}, [modal, onOpen, onClose]);
|
||||
const closeDesignModal = useCallback(() => {
|
||||
onClose?.();
|
||||
setMediaModal(false);
|
||||
}, [modal]);
|
||||
modals.openModal({
|
||||
askClose: false,
|
||||
children: (close) => (
|
||||
<MediaBox setMedia={changeMedia} closeModal={close} />
|
||||
),
|
||||
});
|
||||
}, [changeMedia]);
|
||||
|
||||
const clearMedia = useCallback(
|
||||
(topIndex: number) => () => {
|
||||
const newMedia = currentMedia?.filter((f, index) => index !== topIndex);
|
||||
|
|
@ -622,10 +623,19 @@ export const MultiMediaComponent: FC<{
|
|||
},
|
||||
[currentMedia]
|
||||
);
|
||||
|
||||
const designMedia = useCallback(() => {
|
||||
onOpen?.();
|
||||
setMediaModal(true);
|
||||
}, []);
|
||||
if (!!user?.tier?.ai && !dummy) {
|
||||
modals.openModal({
|
||||
askClose: false,
|
||||
title: 'Design Media',
|
||||
size: '80%',
|
||||
children: (close) => (
|
||||
<Polonto setMedia={changeMedia} closeModal={close} />
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [changeMedia]);
|
||||
|
||||
const mediaSettings = useMediaSettings();
|
||||
|
||||
|
|
@ -634,11 +644,6 @@ export const MultiMediaComponent: FC<{
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-[8px] bg-bigStrip rounded-bl-[8px] select-none w-full">
|
||||
{modal && <MediaBox setMedia={changeMedia} closeModal={showModal} />}
|
||||
{mediaModal && !!user?.tier?.ai && !dummy && (
|
||||
<Polonto setMedia={changeMedia} closeModal={closeDesignModal} />
|
||||
)}
|
||||
|
||||
<div className="flex gap-[10px]">
|
||||
<Button
|
||||
onClick={showModal}
|
||||
|
|
@ -687,17 +692,31 @@ export const MultiMediaComponent: FC<{
|
|||
<div className="w-full h-full relative group">
|
||||
<div
|
||||
onClick={async () => {
|
||||
const data: any = await mediaSettings(media);
|
||||
console.log(
|
||||
value?.map((p) => (p.id === data.id ? data : p))
|
||||
);
|
||||
onChange({
|
||||
target: {
|
||||
name: 'upload',
|
||||
value: value?.map((p) =>
|
||||
p.id === data.id ? data : p
|
||||
),
|
||||
},
|
||||
modals.openModal({
|
||||
title: 'Media Settings',
|
||||
children: (close) => (
|
||||
<MediaComponentInner
|
||||
media={media as any}
|
||||
onClose={close}
|
||||
onSelect={(value: any) => {
|
||||
console.log(value);
|
||||
onChange({
|
||||
target: {
|
||||
name: 'upload',
|
||||
value: currentMedia.map((p) => {
|
||||
if (p.id === media.id) {
|
||||
return {
|
||||
...p,
|
||||
...value,
|
||||
};
|
||||
}
|
||||
return p;
|
||||
}),
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}}
|
||||
className="absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] bg-black/80 rounded-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-[100]"
|
||||
|
|
|
|||
|
|
@ -88,18 +88,10 @@ export function useUppyUploader(props: {
|
|||
// Expand generic types to specific ones
|
||||
const expandedTypes = allowedTypes.flatMap((type) => {
|
||||
if (type === 'image/*') {
|
||||
return [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
];
|
||||
return ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
|
||||
}
|
||||
if (type === 'video/*') {
|
||||
return [
|
||||
'video/mp4',
|
||||
'video/mpeg',
|
||||
];
|
||||
return ['video/mp4', 'video/mpeg'];
|
||||
}
|
||||
return [type];
|
||||
});
|
||||
|
|
@ -214,12 +206,11 @@ export function useUppyUploader(props: {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log(result);
|
||||
if (transloadit.length > 0) {
|
||||
// @ts-ignore
|
||||
const allRes = result.transloadit[0].results;
|
||||
const toSave = uniq<string>(
|
||||
allRes[Object.keys(allRes)[0]].flatMap((item: any) =>
|
||||
(allRes[Object.keys(allRes)[0]] || []).flatMap((item: any) =>
|
||||
item.url.split('/').pop()
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import React, { FC } from 'react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
|
|
|||
|
|
@ -20,11 +20,8 @@ import { weightedLength } from '@gitroom/helpers/utils/count.length';
|
|||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { capitalize } from 'lodash';
|
||||
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
|
||||
// @ts-ignore
|
||||
import useKeypress from 'react-use-keypress';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer';
|
||||
import { CopilotPopup } from '@copilotkit/react-ui';
|
||||
|
|
@ -46,7 +43,6 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const toaster = useToaster();
|
||||
const modal = useModals();
|
||||
usePreventWindowUnload(true);
|
||||
|
||||
const { addEditSets, mutate, customClose, dummy } = props;
|
||||
|
||||
|
|
@ -136,8 +132,6 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
[integrations]
|
||||
);
|
||||
|
||||
useKeypress('Escape', askClose);
|
||||
|
||||
const schedule = useCallback(
|
||||
(type: 'draft' | 'now' | 'schedule') => async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -166,6 +160,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log(checkAllValid);
|
||||
for (const item of checkAllValid) {
|
||||
if (item.valid === false) {
|
||||
toaster.show('Some fields are not valid', 'warning');
|
||||
|
|
@ -312,7 +307,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
<>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-col md:flex-row bg-newBgLineColor gap-[1px] rounded-[24px]'
|
||||
'flex flex-col md:flex-row bg-newBgLineColor gap-[1px] rounded-[24px] trz'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
|
@ -496,11 +491,10 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
<ShowAllProviders ref={ref} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CopilotPopup
|
||||
hitEscapeToClose={false}
|
||||
clickOutsideToClose={true}
|
||||
instructions={`
|
||||
<CopilotPopup
|
||||
hitEscapeToClose={false}
|
||||
clickOutsideToClose={true}
|
||||
instructions={`
|
||||
You are an assistant that help the user to schedule their social media posts,
|
||||
Here are the things you can do:
|
||||
- Add a new comment / post to the list of posts
|
||||
|
|
@ -511,11 +505,12 @@ Here are the things you can do:
|
|||
Post content can be added using the addPostContentFor{num} function.
|
||||
After using the addPostFor{num} it will create a new addPostContentFor{num+ 1} function.
|
||||
`}
|
||||
labels={{
|
||||
title: 'Your Assistant',
|
||||
initial: 'Hi! 👋 How can I assist you today?',
|
||||
}}
|
||||
/>
|
||||
labels={{
|
||||
title: 'Your Assistant',
|
||||
initial: 'Hi! 👋 How can I assist you today?',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, ReactNode, useEffect, useRef } from 'react';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
PostComment,
|
||||
withProvider,
|
||||
} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';
|
||||
import { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
|
||||
import { SelectList } from '@gitroom/frontend/components/new-launch/providers/listmonk/select.list';
|
||||
import { SelectTemplates } from '@gitroom/frontend/components/new-launch/providers/listmonk/select.templates';
|
||||
|
||||
const SettingsComponent = () => {
|
||||
const form = useSettings();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input label="Subject" {...form.register('subject')} />
|
||||
<Input label="Preview" {...form.register('preview')} />
|
||||
<SelectList {...form.register('list')} />
|
||||
<SelectTemplates {...form.register('templates')} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withProvider({
|
||||
postComment: PostComment.POST,
|
||||
minimumCharacters: [],
|
||||
SettingsComponent: SettingsComponent,
|
||||
CustomPreviewComponent: undefined,
|
||||
dto: ListmonkDto,
|
||||
checkValidity: async (posts) => {
|
||||
if (
|
||||
posts.some(
|
||||
(p) => p.some((a) => a.path.indexOf('mp4') > -1) && p.length > 1
|
||||
)
|
||||
) {
|
||||
return 'You can only upload one video per post.';
|
||||
}
|
||||
|
||||
if (posts.some((p) => p.length > 4)) {
|
||||
return 'There can be maximum 4 pictures in a post.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
maximumCharacters: 300000,
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
'use client';
|
||||
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
|
||||
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
|
||||
export const SelectList: FC<{
|
||||
name: string;
|
||||
onChange: (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => void;
|
||||
}> = (props) => {
|
||||
const { onChange, name } = props;
|
||||
const t = useT();
|
||||
const customFunc = useCustomProviderFunction();
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const { getValues } = useSettings();
|
||||
const [currentMedia, setCurrentMedia] = useState<string | undefined>();
|
||||
const onChangeInner = (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => {
|
||||
setCurrentMedia(event.target.value);
|
||||
onChange(event);
|
||||
};
|
||||
useEffect(() => {
|
||||
customFunc.get('list').then((data) => setOrgs(data));
|
||||
const settings = getValues()[props.name];
|
||||
if (settings) {
|
||||
setCurrentMedia(settings);
|
||||
}
|
||||
}, []);
|
||||
if (!orgs.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
name={name}
|
||||
label="Select List"
|
||||
onChange={onChangeInner}
|
||||
value={currentMedia}
|
||||
>
|
||||
<option value="">{t('select_1', '--Select--')}</option>
|
||||
{orgs.map((org: any) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
'use client';
|
||||
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
|
||||
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
|
||||
export const SelectTemplates: FC<{
|
||||
name: string;
|
||||
onChange: (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => void;
|
||||
}> = (props) => {
|
||||
const { onChange, name } = props;
|
||||
const t = useT();
|
||||
const customFunc = useCustomProviderFunction();
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const { getValues } = useSettings();
|
||||
const [currentMedia, setCurrentMedia] = useState<string | undefined>();
|
||||
const onChangeInner = (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => {
|
||||
setCurrentMedia(event.target.value);
|
||||
onChange(event);
|
||||
};
|
||||
useEffect(() => {
|
||||
customFunc.get('templates').then((data) => setOrgs(data));
|
||||
const settings = getValues()[props.name];
|
||||
if (settings) {
|
||||
setCurrentMedia(settings);
|
||||
}
|
||||
}, []);
|
||||
if (!orgs.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
name={name}
|
||||
label="Select Template"
|
||||
onChange={onChangeInner}
|
||||
value={currentMedia}
|
||||
>
|
||||
<option value="">{t('select_1', '--Select--')}</option>
|
||||
{orgs.map((org: any) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
|
@ -31,6 +31,7 @@ import { Button } from '@gitroom/react/form/button';
|
|||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';
|
||||
import WordpressProvider from '@gitroom/frontend/components/new-launch/providers/wordpress/wordpress.provider';
|
||||
import ListmonkProvider from '@gitroom/frontend/components/new-launch/providers/listmonk/listmonk.provider';
|
||||
|
||||
export const Providers = [
|
||||
{
|
||||
|
|
@ -133,6 +134,10 @@ export const Providers = [
|
|||
identifier: 'wordpress',
|
||||
component: WordpressProvider,
|
||||
},
|
||||
{
|
||||
identifier: 'listmonk',
|
||||
component: ListmonkProvider,
|
||||
},
|
||||
];
|
||||
export const ShowAllProviders = forwardRef((props, ref) => {
|
||||
const { date, current, global, selectedIntegrations, allIntegrations } =
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';
|
|||
interface Values {
|
||||
id: string;
|
||||
content: string;
|
||||
media: { id: string; path: string, thumbnail?: string }[];
|
||||
media: { id: string; path: string; thumbnail?: string }[];
|
||||
}
|
||||
|
||||
interface Internal {
|
||||
|
|
@ -504,7 +504,9 @@ export const useLaunchStore = create<StoreState>()((set) => ({
|
|||
? {
|
||||
...item,
|
||||
integrationValue: item.integrationValue.map((v, i) =>
|
||||
i === index ? { ...v, media: [...v.media, ...media] } : v
|
||||
i === index
|
||||
? { ...v, media: [...(v?.media || []), ...media] }
|
||||
: v
|
||||
),
|
||||
}
|
||||
: item
|
||||
|
|
@ -516,7 +518,9 @@ export const useLaunchStore = create<StoreState>()((set) => ({
|
|||
) =>
|
||||
set((state) => ({
|
||||
global: state.global.map((item, i) =>
|
||||
i === index ? { ...item, media: [...item.media, ...media] } : item
|
||||
i === index
|
||||
? { ...item, media: [...(item?.media || []), ...media] }
|
||||
: item
|
||||
),
|
||||
})),
|
||||
setPostComment: (postComment: PostComment) =>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => {
|
|||
<CopilotKit
|
||||
credentials="include"
|
||||
runtimeUrl={backendUrl + '/copilot/chat'}
|
||||
showDevConsole={false}
|
||||
>
|
||||
<MantineWrapper>
|
||||
{user.tier === 'FREE' && searchParams.get('check') && (
|
||||
|
|
@ -103,7 +104,7 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 bg-newBgLineColor rounded-[12px] overflow-hidden flex flex-col gap-[1px]">
|
||||
<div className="flex-1 bg-newBgLineColor rounded-[12px] overflow-hidden flex flex-col gap-[1px] blurMe">
|
||||
<div className="flex bg-newBgColorInner h-[80px] px-[20px] items-center">
|
||||
<div className="text-[24px] font-[600] flex flex-1">
|
||||
<Title />
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const Logo = () => {
|
|||
<path
|
||||
d="M47.0122 2.5752C47.9217 2.57909 48.5299 2.67533 49.0386 2.88672C50.0099 3.29052 50.829 3.99206 51.3774 4.88965C51.6647 5.35982 51.8537 5.94554 51.9976 6.84375C52.1427 7.7505 52.2327 8.91127 52.3569 10.5166L54.564 39.0283C54.6882 40.6337 54.7769 41.7946 54.7729 42.7129C54.7691 43.6226 54.6729 44.2305 54.4614 44.7393C54.0576 45.7105 53.3561 46.5287 52.4585 47.0771C51.9883 47.3644 51.4027 47.5534 50.5044 47.6973C49.5977 47.8424 48.4376 47.9334 46.8325 48.0576L24.9937 49.748C23.3886 49.8723 22.2282 49.961 21.3101 49.957C20.4003 49.9531 19.7925 49.8561 19.2837 49.6445C18.3124 49.2407 17.4933 48.5402 16.9448 47.6426C16.6576 47.1724 16.4685 46.5867 16.3247 45.6885C16.1795 44.7817 16.0896 43.621 15.9653 42.0156L13.7583 13.5029C13.6341 11.8979 13.5454 10.7375 13.5493 9.81934C13.5532 8.90971 13.6494 8.30172 13.8608 7.79297C14.2646 6.82169 14.9662 6.00253 15.8638 5.4541C16.3339 5.16692 16.9197 4.97778 17.8179 4.83398C18.7246 4.68882 19.8854 4.59884 21.4907 4.47461L43.3286 2.78418C44.9336 2.65997 46.094 2.57129 47.0122 2.5752Z"
|
||||
stroke="#131019"
|
||||
stroke-width="1.45254"
|
||||
strokeWidth="1.45254"
|
||||
/>
|
||||
<path
|
||||
d="M21.5681 5.49237L43.4061 3.80233C45.0283 3.67679 46.1402 3.59211 47.007 3.59582C47.8534 3.59945 48.3108 3.69053 48.6454 3.82963C49.4173 4.15056 50.0679 4.70763 50.5037 5.421C50.6927 5.7302 50.853 6.16816 50.9868 7.00389C51.1239 7.85984 51.2113 8.97152 51.3368 10.5937L53.5434 39.1062C53.6689 40.7284 53.7536 41.8403 53.7499 42.7071C53.7463 43.5535 53.6552 44.0109 53.5161 44.3455C53.1952 45.1174 52.6381 45.7679 51.9247 46.2038C51.6155 46.3927 51.1776 46.5531 50.3418 46.6869C49.4859 46.824 48.3742 46.9114 46.752 47.0369L24.914 48.7269C23.2918 48.8525 22.1799 48.9372 21.3131 48.9335C20.4667 48.9298 20.0093 48.8387 19.6747 48.6996C18.9028 48.3787 18.2522 47.8216 17.8164 47.1083C17.6274 46.7991 17.4671 46.3611 17.3333 45.5254C17.1962 44.6694 17.1088 43.5578 16.9833 41.9356L14.7767 13.4231C14.6512 11.8009 14.5665 10.689 14.5702 9.82217C14.5738 8.97581 14.6649 8.51838 14.804 8.1838C15.1249 7.41186 15.682 6.76133 16.3954 6.32545C16.7046 6.13653 17.1425 5.97616 17.9783 5.84235C18.8342 5.70531 19.9459 5.61791 21.5681 5.49237Z"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { ConnectChannels } from '@gitroom/frontend/components/onboarding/connect.channels';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
|
|
@ -46,15 +46,11 @@ export const Onboarding: FC = () => {
|
|||
}
|
||||
modalOpen.current = true;
|
||||
modal.openModal({
|
||||
title: '',
|
||||
title: t('onboarding', 'Onboarding'),
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
size: '900px',
|
||||
children: (
|
||||
<ModalWrapperComponent title={t('onboarding', 'Onboarding')}>
|
||||
<Welcome />
|
||||
</ModalWrapperComponent>
|
||||
),
|
||||
children: <Welcome />,
|
||||
});
|
||||
}, [query]);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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 { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import {
|
||||
FormProvider,
|
||||
|
|
@ -243,18 +243,17 @@ export const Plug = () => {
|
|||
mutate();
|
||||
},
|
||||
size: '500px',
|
||||
title: `Auto Plug: ${p.title}`,
|
||||
children: (
|
||||
<ModalWrapperComponent title={`Auto Plug: ${p.title}`}>
|
||||
<PlugPop
|
||||
plug={p}
|
||||
data={data}
|
||||
settings={{
|
||||
identifier: plug.identifier,
|
||||
providerId: plug.providerId,
|
||||
name: plug.name,
|
||||
}}
|
||||
/>
|
||||
</ModalWrapperComponent>
|
||||
<PlugPop
|
||||
plug={p}
|
||||
data={data}
|
||||
settings={{
|
||||
identifier: plug.identifier,
|
||||
providerId: plug.providerId,
|
||||
name: plug.name,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const PreviewWrapper = ({ children }: { children: ReactNode }) => {
|
|||
<CopilotKit
|
||||
credentials="include"
|
||||
runtimeUrl={backendUrl + '/copilot/chat'}
|
||||
showDevConsole={false}
|
||||
>
|
||||
<MantineWrapper>
|
||||
<Toaster />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { FC } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
dayjs.extend(utc);
|
||||
|
||||
export const RenderPreviewDate: FC<{ date: string }> = ({ date }) => {
|
||||
console.log(date);
|
||||
return <>{dayjs.utc(date).local().format('MMMM D, YYYY h:mm A')}</>;
|
||||
};
|
||||
|
|
@ -6,15 +6,14 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
|||
import useSWR from 'swr';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import clsx from 'clsx';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import dayjs from 'dayjs';
|
||||
import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';
|
||||
import { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
|
||||
const SaveSetModal: FC<{
|
||||
postData: any;
|
||||
|
|
@ -97,9 +96,8 @@ export const Sets: FC = () => {
|
|||
closeOnClickOutside: false,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] bg-transparent text-textColor',
|
||||
},
|
||||
removeLayout: true,
|
||||
askClose: true,
|
||||
children: (
|
||||
<AddEditModal
|
||||
allIntegrations={integrations.map((p: any) => ({
|
||||
|
|
@ -109,10 +107,6 @@ export const Sets: FC = () => {
|
|||
addEditSets={(data) => {
|
||||
modal.openModal({
|
||||
title: 'Save as Set',
|
||||
classNames: {
|
||||
modal: 'bg-sixth text-textColor',
|
||||
title: 'text-textColor',
|
||||
},
|
||||
children: (
|
||||
<SaveSetModal
|
||||
initialValue={params?.name || ''}
|
||||
|
|
@ -137,7 +131,6 @@ export const Sets: FC = () => {
|
|||
onCancel={() => modal.closeAll()}
|
||||
/>
|
||||
),
|
||||
size: 'md',
|
||||
});
|
||||
}}
|
||||
reopenModal={() => {}}
|
||||
|
|
@ -146,7 +139,6 @@ export const Sets: FC = () => {
|
|||
date={newDayjs()}
|
||||
/>
|
||||
),
|
||||
size: '80%',
|
||||
title: ``,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ 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 { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
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';
|
||||
|
|
@ -27,11 +27,8 @@ export const SignaturesComponent: FC<{
|
|||
const addSignature = useCallback(
|
||||
(data?: any) => () => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
title: data ? 'Edit Signature' : 'Add Signature',
|
||||
withCloseButton: true,
|
||||
children: <AddOrRemoveSignature data={data} reload={mutate} />,
|
||||
});
|
||||
},
|
||||
|
|
@ -170,7 +167,7 @@ const AddOrRemoveSignature: FC<{
|
|||
: 'Signature added successfully',
|
||||
'success'
|
||||
);
|
||||
modal.closeModal(modal.modals[modal.modals.length - 1].id);
|
||||
modal.closeCurrent();
|
||||
reload();
|
||||
},
|
||||
[data, modal]
|
||||
|
|
@ -181,14 +178,11 @@ const AddOrRemoveSignature: FC<{
|
|||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(callBack)}>
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0 w-[500px]">
|
||||
<TopTitle title={data ? 'Edit Signature' : 'Add Signature'} />
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] pt-0">
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
modal.closeModal(modal.modals[modal.modals.length - 1].id)
|
||||
}
|
||||
onClick={() => modal.closeCurrent()}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import useSWR from 'swr';
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { capitalize } from 'lodash';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { useForm, FormProvider, useWatch } from 'react-hook-form';
|
||||
|
|
@ -65,38 +65,13 @@ export const AddMember = () => {
|
|||
},
|
||||
[]
|
||||
);
|
||||
const closeModal = useCallback(() => {
|
||||
return modals.closeAll();
|
||||
}, []);
|
||||
|
||||
const t = useT();
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(submit)}>
|
||||
<div className="relative flex gap-[10px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0">
|
||||
<TopTitle title="Add Member" />
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="relative flex gap-[10px] flex-col flex-1 p-[16px] pt-0">
|
||||
{sendEmail && (
|
||||
<Input
|
||||
label="Email"
|
||||
|
|
@ -153,7 +128,8 @@ export const TeamsComponent = () => {
|
|||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
withCloseButton: false,
|
||||
title: 'Add Team Member',
|
||||
withCloseButton: true,
|
||||
children: <AddMember />,
|
||||
});
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
import { FC, useCallback, useState } from 'react';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { SignaturesComponent } from '@gitroom/frontend/components/settings/signatures.component';
|
||||
import { Transforms } from 'slate';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
export const SignatureBox: FC<{
|
||||
editor: any;
|
||||
}> = ({ editor }) => {
|
||||
const [showModal, setShowModal] = useState<any>(false);
|
||||
const addSignature = useCallback(() => {
|
||||
setShowModal(true);
|
||||
}, [showModal]);
|
||||
const modals = useModals();
|
||||
const appendValue = (val: string) => {
|
||||
editor?.commands?.insertContent("\n\n" + val);
|
||||
editor?.commands?.insertContent('\n\n' + val);
|
||||
editor?.commands?.focus();
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const addSignature = useCallback(() => {
|
||||
modals.openModal({
|
||||
title: 'Add Signature',
|
||||
children: (close) => (
|
||||
<SignatureModal appendSignature={appendValue} close={close} />
|
||||
),
|
||||
});
|
||||
}, [appendValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && (
|
||||
<SignatureModal
|
||||
appendSignature={appendValue}
|
||||
close={() => setShowModal(false)}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
onClick={addSignature}
|
||||
className="select-none cursor-pointer w-[40px] p-[5px] text-center"
|
||||
|
|
@ -45,32 +44,10 @@ export const SignatureModal: FC<{
|
|||
close: () => void;
|
||||
appendSignature: (sign: string) => void;
|
||||
}> = (props) => {
|
||||
const { close, appendSignature } = props;
|
||||
const { appendSignature } = props;
|
||||
return (
|
||||
<div className="bg-black/40 fixed start-0 top-0 w-full h-full z-[500]">
|
||||
<div className="relative w-[900px] mx-auto flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0">
|
||||
<TopTitle title={`Add signature`} />
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
onClick={close}
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="relative w-[900px] mx-auto flex gap-[20px] flex-col flex-1 rounded-[4px] pt-0">
|
||||
<SignaturesComponent appendSignature={appendSignature} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import useSWR from 'swr';
|
|||
import React, { FC, useCallback, useState } from 'react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { FieldValues, FormProvider, useForm } from 'react-hook-form';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
|
|
@ -115,12 +115,10 @@ export const ThirdPartyListComponent: FC<{ reload: () => void }> = (props) => {
|
|||
const addApiKey = useCallback(
|
||||
(title: string, identifier: string) => () => {
|
||||
modals.openModal({
|
||||
title: '',
|
||||
title: `Add API key for ${title}`,
|
||||
withCloseButton: false,
|
||||
children: (
|
||||
<ModalWrapperComponent title={`Add API key for ${title}`}>
|
||||
<ApiModal identifier={identifier} title={title} update={reload} />
|
||||
</ModalWrapperComponent>
|
||||
<ApiModal identifier={identifier} title={title} update={reload} />
|
||||
),
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.titl
|
|||
import './providers/heygen.provider';
|
||||
import { thirdPartyList } from '@gitroom/frontend/components/third-parties/third-party.wrapper';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
|
||||
const ThirdPartyContext = createContext({
|
||||
id: '',
|
||||
|
|
@ -92,93 +93,53 @@ export const ThirdPartyPopup: FC<{
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="removeEditor fixed start-0 top-0 bg-primary/80 z-[300] w-full min-h-full animate-fade bg-black/30 rounded-[24px]"
|
||||
onClick={closeModal}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="absolute -top-[50px] left-0" ref={refNew} />
|
||||
</div>
|
||||
<div
|
||||
className="max-w-[1000px] w-full h-full bg-sixth border-tableBorder border-2 rounded-xl relative mx-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="pb-[20px] px-[20px] w-full h-full">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex-1">
|
||||
<TopTitle title="Integrations" />
|
||||
</div>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="outline-none z-[300] absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
<div className={clsx('flex flex-wrap flex-col gap-[10px] pt-[20px]')}>
|
||||
{!thirdParty && (
|
||||
<div className="grid grid-cols-4 gap-[10px] justify-items-center justify-center">
|
||||
{thirdParties.map((p: any) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
setThirdParty(p);
|
||||
}}
|
||||
key={p.identifier}
|
||||
className="w-full h-full p-[20px] min-h-[100px] text-[14px] bg-third hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className={clsx('flex flex-wrap flex-col gap-[10px] pt-[20px]')}>
|
||||
{!thirdParty && (
|
||||
<div className="grid grid-cols-4 gap-[10px] justify-items-center justify-center">
|
||||
{thirdParties.map((p: any) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
setThirdParty(p);
|
||||
}}
|
||||
key={p.identifier}
|
||||
className="w-full h-full p-[20px] min-h-[100px] text-[14px] bg-third hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer"
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
className="w-[32px] h-[32px]"
|
||||
src={`/icons/third-party/${p.identifier}.png`}
|
||||
/>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap text-left text-lg">
|
||||
{p.title}: {p.name}
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap text-left">
|
||||
{p.description}
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<Button className="w-full">Use</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<img
|
||||
className="w-[32px] h-[32px]"
|
||||
src={`/icons/third-party/${p.identifier}.png`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{thirdParty && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className="cursor-pointer float-left"
|
||||
onClick={() => setThirdParty(null)}
|
||||
>
|
||||
{'<'} Back
|
||||
</div>
|
||||
</div>
|
||||
<ThirdPartyContext.Provider
|
||||
value={{ ...thirdParty, data: allData, close, onChange }}
|
||||
>
|
||||
<Component />
|
||||
</ThirdPartyContext.Provider>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap text-left text-lg">
|
||||
{p.title}: {p.name}
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap text-left">
|
||||
{p.description}
|
||||
</div>
|
||||
<div className="w-full flex">
|
||||
<Button className="w-full">Use</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{thirdParty && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className="cursor-pointer float-left"
|
||||
onClick={() => setThirdParty(null)}
|
||||
>
|
||||
{'<'} Back
|
||||
</div>
|
||||
</div>
|
||||
<ThirdPartyContext.Provider
|
||||
value={{ ...thirdParty, data: allData, close, onChange }}
|
||||
>
|
||||
<Component />
|
||||
</ThirdPartyContext.Provider>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -197,7 +158,7 @@ export const ThirdPartyMedia: FC<{
|
|||
const { allData, onChange } = props;
|
||||
const t = useT();
|
||||
const fetch = useFetch();
|
||||
const [popup, setPopup] = useState(false);
|
||||
const modals = useModals();
|
||||
|
||||
const thirdParties = useCallback(async () => {
|
||||
return (await (await fetch('/third-party')).json()).filter(
|
||||
|
|
@ -220,20 +181,25 @@ export const ThirdPartyMedia: FC<{
|
|||
|
||||
return (
|
||||
<>
|
||||
{popup && (
|
||||
<ThirdPartyPopup
|
||||
thirdParties={data}
|
||||
closeModal={() => setPopup(false)}
|
||||
allData={allData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
<div className="relative group">
|
||||
<Button
|
||||
className={clsx(
|
||||
'relative ms-[10px] !px-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-newBgLineColor bg-newColColor'
|
||||
)}
|
||||
onClick={() => setPopup(true)}
|
||||
onClick={() => {
|
||||
modals.openModal({
|
||||
title: t('integrations', 'Integrations'),
|
||||
size: '80%',
|
||||
children: (close) => (
|
||||
<ThirdPartyPopup
|
||||
thirdParties={data}
|
||||
closeModal={close}
|
||||
allData={allData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={clsx('flex gap-[5px] items-center')}>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
|||
import useSWR from 'swr';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
|
@ -27,11 +27,8 @@ export const Webhooks: FC = () => {
|
|||
const addWebhook = useCallback(
|
||||
(data?: any) => () => {
|
||||
modal.openModal({
|
||||
title: '',
|
||||
withCloseButton: false,
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
title: data ? 'Update webhook' : 'Add webhook',
|
||||
withCloseButton: true,
|
||||
children: <AddOrEditWebhook data={data} reload={mutate} />,
|
||||
});
|
||||
},
|
||||
|
|
@ -246,29 +243,7 @@ export const AddOrEditWebhook: FC<{
|
|||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(callBack)}>
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0 w-[500px]">
|
||||
<TopTitle title={data ? 'Edit webhook' : 'Add webhook'} />
|
||||
<button
|
||||
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
onClick={modal.closeAll}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] pt-0">
|
||||
<div>
|
||||
<Input
|
||||
label="Name"
|
||||
|
|
|
|||
|
|
@ -73,8 +73,6 @@ module.exports = {
|
|||
customColor55: 'var(--color-custom55)',
|
||||
modalCustom: 'var(--color-modalCustom)',
|
||||
|
||||
|
||||
|
||||
newBgColor: 'var(--new-bgColor)',
|
||||
newBgColorInner: 'var(--new-bgColorInner)',
|
||||
newBgLineColor: 'var(--new-bgLineColor)',
|
||||
|
|
@ -96,6 +94,7 @@ module.exports = {
|
|||
menuDots: 'var(--new-menu-dots)',
|
||||
menuDotsHover: 'var(--new-menu-hover)',
|
||||
bigStrip: 'var(--new-big-strips)',
|
||||
popup: 'var(--popup-color)',
|
||||
},
|
||||
gridTemplateColumns: {
|
||||
13: 'repeat(13, minmax(0, 1fr));',
|
||||
|
|
@ -110,6 +109,7 @@ module.exports = {
|
|||
animation: {
|
||||
fade: 'fadeOut 0.5s ease-in-out',
|
||||
normalFadeIn: 'normalFadeIn 0.5s ease-in-out',
|
||||
fadeIn: 'normalFadeIn 0.2s ease-in-out forwards',
|
||||
normalFadeOut: 'normalFadeOut 0.5s linear 5s forwards',
|
||||
overflow: 'overFlow 0.5s ease-in-out forwards',
|
||||
overflowReverse: 'overFlowReverse 0.5s ease-in-out forwards',
|
||||
|
|
@ -121,7 +121,7 @@ module.exports = {
|
|||
yellow: '0 0 60px 20px #6b6237',
|
||||
yellowToast: '0px 0px 50px rgba(252, 186, 3, 0.3)',
|
||||
greenToast: '0px 0px 50px rgba(60, 124, 90, 0.3)',
|
||||
menu: 'var(--menu-shadow)'
|
||||
menu: 'var(--menu-shadow)',
|
||||
},
|
||||
// that is actual animation
|
||||
keyframes: (theme) => ({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export class PlugsController {
|
|||
return await this._integrationService.processPlugs(data);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
"Unhandled error, let's avoid crashing the plugs worker",
|
||||
"Unhandled error, let's avoid crashing the plug worker",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,9 @@ export const stripHtmlValidation = (
|
|||
.replace(/ /gi, ' ')
|
||||
.replace(/^<p[^>]*>/i, '')
|
||||
.replace(/<p[^>]*>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '');
|
||||
.replace(/<\/p>/gi, '')
|
||||
.replace(/>/gi, '>')
|
||||
.replace(/</gi, '<')
|
||||
|
||||
if (none) {
|
||||
return striptags(html);
|
||||
|
|
|
|||
|
|
@ -30,3 +30,4 @@ export const agentCategories = [
|
|||
'Update',
|
||||
'Trend',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
constructor() {
|
||||
super({
|
||||
log: [
|
||||
|
|
@ -16,6 +16,10 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
|||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -26,7 +30,6 @@ export class PrismaRepository<T extends keyof PrismaService> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class PrismaTransaction {
|
||||
public model: Pick<PrismaService, '$transaction'>;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
runtime = "nodejs"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/provider
|
|||
import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto';
|
||||
import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';
|
||||
import { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';
|
||||
import { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';
|
||||
|
||||
export type ProviderExtension<T extends string, M> = { __type: T } & M;
|
||||
export type AllProvidersSettings =
|
||||
|
|
@ -34,6 +35,7 @@ export type AllProvidersSettings =
|
|||
| ProviderExtension<'devto', DevToSettingsDto>
|
||||
| ProviderExtension<'hashnode', HashnodeSettingsDto>
|
||||
| ProviderExtension<'wordpress', WordpressDto>
|
||||
| ProviderExtension<'listmonk', ListmonkDto>
|
||||
| ProviderExtension<'facebook', None>
|
||||
| ProviderExtension<'threads', None>
|
||||
| ProviderExtension<'mastodon', None>
|
||||
|
|
@ -64,6 +66,7 @@ export const allProviders = (setEmpty?: any) => {
|
|||
{ value: DevToSettingsDto, name: 'devto' },
|
||||
{ value: WordpressDto, name: 'wordpress' },
|
||||
{ value: HashnodeSettingsDto, name: 'hashnode' },
|
||||
{ value: ListmonkDto, name: 'listmonk' },
|
||||
{ value: setEmpty, name: 'facebook' },
|
||||
{ value: setEmpty, name: 'threads' },
|
||||
{ value: setEmpty, name: 'mastodon' },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { IsOptional, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class ListmonkDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
preview: string;
|
||||
|
||||
@IsString()
|
||||
list: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
template: string;
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ export class RedditSettingsDtoInner {
|
|||
@ValidateIf((e) => e.is_flair_required)
|
||||
@IsDefined()
|
||||
@ValidateNested()
|
||||
@Type(() => RedditFlairDto)
|
||||
flair: RedditFlairDto;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/
|
|||
import { NostrProvider } from '@gitroom/nestjs-libraries/integrations/social/nostr.provider';
|
||||
import { VkProvider } from '@gitroom/nestjs-libraries/integrations/social/vk.provider';
|
||||
import { WordpressProvider } from '@gitroom/nestjs-libraries/integrations/social/wordpress.provider';
|
||||
import { ListmonkProvider } from '@gitroom/nestjs-libraries/integrations/social/listmonk.provider';
|
||||
|
||||
export const socialIntegrationList: SocialProvider[] = [
|
||||
new XProvider(),
|
||||
|
|
@ -54,6 +55,7 @@ export const socialIntegrationList: SocialProvider[] = [
|
|||
new DevToProvider(),
|
||||
new HashnodeProvider(),
|
||||
new WordpressProvider(),
|
||||
new ListmonkProvider(),
|
||||
// new MastodonCustomProvider(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ export abstract class SocialAbstract {
|
|||
|
||||
public handleErrors(
|
||||
body: string
|
||||
): { type: 'refresh-token' | 'bad-body'; value: string } | undefined {
|
||||
):
|
||||
| { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }
|
||||
| undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -38,11 +40,17 @@ export abstract class SocialAbstract {
|
|||
d: { query: string },
|
||||
id: string,
|
||||
integration: Integration
|
||||
): Promise<{ id: string; label: string; image: string, doNotCache?: boolean }[] | { none: true }> {
|
||||
): Promise<
|
||||
| { id: string; label: string; image: string; doNotCache?: boolean }[]
|
||||
| { none: true }
|
||||
> {
|
||||
return { none: true };
|
||||
}
|
||||
|
||||
async runInConcurrent<T>(func: (...args: any[]) => Promise<T>) {
|
||||
async runInConcurrent<T>(
|
||||
func: (...args: any[]) => Promise<T>,
|
||||
ignoreConcurrency?: boolean
|
||||
) {
|
||||
const value = await concurrency<any>(
|
||||
this.identifier,
|
||||
this.maxConcurrentJob,
|
||||
|
|
@ -54,7 +62,8 @@ export abstract class SocialAbstract {
|
|||
const handle = this.handleErrors(JSON.stringify(err));
|
||||
return { err: true, ...(handle || {}) };
|
||||
}
|
||||
}
|
||||
},
|
||||
ignoreConcurrency
|
||||
);
|
||||
|
||||
if (value && value?.err && value?.value) {
|
||||
|
|
@ -100,11 +109,16 @@ export abstract class SocialAbstract {
|
|||
json.includes('Rate limit')
|
||||
) {
|
||||
await timer(5000);
|
||||
return this.fetch(url, options, identifier, totalRetries + 1);
|
||||
return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency);
|
||||
}
|
||||
|
||||
const handleError = this.handleErrors(json || '{}');
|
||||
|
||||
if (handleError?.type === 'retry') {
|
||||
await timer(5000);
|
||||
return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency);
|
||||
}
|
||||
|
||||
if (
|
||||
request.status === 401 &&
|
||||
(handleError?.type === 'refresh-token' || !handleError)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import {
|
||||
BadBody,
|
||||
RefreshToken,
|
||||
SocialAbstract,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
|
|
@ -119,8 +120,17 @@ async function uploadVideo(
|
|||
if (status.jobStatus.blob) {
|
||||
blob = status.jobStatus.blob;
|
||||
}
|
||||
// wait a second
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
if (status.jobStatus.state === 'JOB_STATE_FAILED') {
|
||||
throw new BadBody(
|
||||
'bluesky',
|
||||
JSON.stringify({}),
|
||||
{} as any,
|
||||
'Could not upload video, job failed'
|
||||
);
|
||||
}
|
||||
|
||||
await timer(30000);
|
||||
}
|
||||
|
||||
console.log('posting video...');
|
||||
|
|
@ -295,7 +305,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
|
|||
aspectRatio: {
|
||||
width: p.width,
|
||||
height: p.height,
|
||||
}
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,18 @@ export class InstagramProvider
|
|||
|
||||
public override handleErrors(body: string):
|
||||
| {
|
||||
type: 'refresh-token' | 'bad-body';
|
||||
type: 'refresh-token' | 'bad-body' | 'retry';
|
||||
value: string;
|
||||
}
|
||||
| undefined {
|
||||
|
||||
if (body.indexOf('An unknown error occurred') > -1) {
|
||||
return {
|
||||
type: 'retry' as const,
|
||||
value: 'An unknown error occurred, please try again later',
|
||||
};
|
||||
}
|
||||
|
||||
if (body.indexOf('REVOKED_ACCESS_TOKEN') > -1) {
|
||||
return {
|
||||
type: 'refresh-token' as const,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class InstagramStandaloneProvider
|
|||
|
||||
editor = 'normal' as const;
|
||||
|
||||
public override handleErrors(body: string): { type: "refresh-token" | "bad-body"; value: string } | undefined {
|
||||
public override handleErrors(body: string): { type: "refresh-token" | "bad-body" | "retry"; value: string } | undefined {
|
||||
return instagramProvider.handleErrors(body);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -254,20 +254,26 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
|
|||
|
||||
const etags = [];
|
||||
for (let i = 0; i < picture.length; i += 1024 * 1024 * 2) {
|
||||
const upload = await this.fetch(sendUrlRequest, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-Restli-Protocol-Version': '2.0.0',
|
||||
'LinkedIn-Version': '202501',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
...(isVideo
|
||||
? { 'Content-Type': 'application/octet-stream' }
|
||||
: isPdf
|
||||
? { 'Content-Type': 'application/pdf' }
|
||||
: {}),
|
||||
const upload = await this.fetch(
|
||||
sendUrlRequest,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-Restli-Protocol-Version': '2.0.0',
|
||||
'LinkedIn-Version': '202501',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
...(isVideo
|
||||
? { 'Content-Type': 'application/octet-stream' }
|
||||
: isPdf
|
||||
? { 'Content-Type': 'application/pdf' }
|
||||
: {}),
|
||||
},
|
||||
body: picture.slice(i, i + 1024 * 1024 * 2),
|
||||
},
|
||||
body: picture.slice(i, i + 1024 * 1024 * 2),
|
||||
});
|
||||
'linkedin',
|
||||
0,
|
||||
true
|
||||
);
|
||||
|
||||
etags.push(upload.headers.get('etag'));
|
||||
}
|
||||
|
|
@ -737,7 +743,9 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
|
|||
return elements.map((p: any) => ({
|
||||
id: String(p.id),
|
||||
label: p.localizedName,
|
||||
image: p.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0]?.identifier || '',
|
||||
image:
|
||||
p.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0]?.identifier ||
|
||||
'',
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { SocialAbstract } from '../social.abstract';
|
||||
import {
|
||||
AuthTokenDetails,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
} from './social.integrations.interface';
|
||||
import dayjs from 'dayjs';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';
|
||||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import slugify from 'slugify';
|
||||
|
||||
export class ListmonkProvider extends SocialAbstract implements SocialProvider {
|
||||
override maxConcurrentJob = 100; // Bluesky has moderate rate limits
|
||||
identifier = 'listmonk';
|
||||
name = 'ListMonk';
|
||||
isBetweenSteps = false;
|
||||
scopes = [] as string[];
|
||||
editor = 'html' as const;
|
||||
|
||||
async customFields() {
|
||||
return [
|
||||
{
|
||||
key: 'url',
|
||||
label: 'URL',
|
||||
defaultValue: '',
|
||||
validation: `/^(https?:\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?:localhost)|(?:\\d{1,3}(?:\\.\\d{1,3}){3})|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63})(?::\\d{2,5})?(?:\\/[^\\s?#]*)?(?:\\?[^\\s#]*)?(?:#[^\\s]*)?$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username',
|
||||
validation: `/^.+$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'password' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: 0,
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthUrl() {
|
||||
const state = makeId(6);
|
||||
return {
|
||||
url: '',
|
||||
codeVerifier: makeId(10),
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
async authenticate(params: {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
refresh?: string;
|
||||
}) {
|
||||
const body: { url: string; username: string; password: string } =
|
||||
JSON.parse(Buffer.from(params.code, 'base64').toString());
|
||||
|
||||
console.log(body);
|
||||
try {
|
||||
const basic = Buffer.from(body.username + ':' + body.password).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const { data } = await (
|
||||
await this.fetch(body.url + '/api/settings', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: 'Basic ' + basic,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
return {
|
||||
refreshToken: basic,
|
||||
expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),
|
||||
accessToken: basic,
|
||||
id: Buffer.from(body.url).toString('base64'),
|
||||
name: data['app.site_name'],
|
||||
picture: data['app.logo_url'] || '',
|
||||
username: data['app.site_name'],
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
}
|
||||
|
||||
async list(
|
||||
token: string,
|
||||
data: any,
|
||||
internalId: string,
|
||||
integration: Integration
|
||||
) {
|
||||
const body: { url: string; username: string; password: string } =
|
||||
JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const postTypes = await (
|
||||
await this.fetch(`${body.url}/api/lists`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
return postTypes.data.results.map((p: any) => ({ id: p.id, name: p.name }));
|
||||
}
|
||||
|
||||
async templates(
|
||||
token: string,
|
||||
data: any,
|
||||
internalId: string,
|
||||
integration: Integration
|
||||
) {
|
||||
const body: { url: string; username: string; password: string } =
|
||||
JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const postTypes = await (
|
||||
await this.fetch(`${body.url}/api/templates`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
return [
|
||||
{ id: 0, name: 'Default' },
|
||||
...postTypes.data.map((p: any) => ({ id: p.id, name: p.name })),
|
||||
];
|
||||
}
|
||||
|
||||
async post(
|
||||
id: string,
|
||||
accessToken: string,
|
||||
postDetails: PostDetails<ListmonkDto>[],
|
||||
integration: Integration
|
||||
): Promise<PostResponse[]> {
|
||||
const body: { url: string; username: string; password: string } =
|
||||
JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const sendBody = `
|
||||
<style>
|
||||
.content {
|
||||
padding: 20px;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
<div class="hidden-preheader"
|
||||
style="display:none !important; visibility:hidden; opacity:0; overflow:hidden;
|
||||
max-height:0; max-width:0; line-height:1px; font-size:1px; color:transparent;
|
||||
mso-hide:all;">
|
||||
<!-- A short visible decoy (optional): shows as "." or short text in preview -->
|
||||
${postDetails?.[0]?.settings?.preview || ''}
|
||||
<!-- Then invisible padding to eat up preview characters -->
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
<!-- Repeat the trio (zero-width space, zero-width non-joiner, nbsp, BOM) a bunch of times -->
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
​‌ ​‌ ​‌ ​‌ 
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
${postDetails[0].message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const {
|
||||
data: { uuid: postId, id: campaignId },
|
||||
} = await (
|
||||
await this.fetch(body.url + '/api/campaigns', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: slugify(postDetails[0].settings.subject, {
|
||||
lower: true,
|
||||
strict: true,
|
||||
trim: true,
|
||||
}),
|
||||
type: 'regular',
|
||||
content_type: 'html',
|
||||
subject: postDetails[0].settings.subject,
|
||||
lists: [+postDetails[0].settings.list],
|
||||
body: sendBody,
|
||||
...(+postDetails?.[0]?.settings?.template
|
||||
? { template_id: +postDetails[0].settings.template }
|
||||
: {}),
|
||||
}),
|
||||
})
|
||||
).json();
|
||||
|
||||
await this.fetch(body.url + `/api/campaigns/${campaignId}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status: 'running',
|
||||
}),
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id,
|
||||
status: 'completed',
|
||||
releaseURL: `${body.url}/api/campaigns/${campaignId}/preview`,
|
||||
postId,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +38,7 @@ export class XProvider extends SocialAbstract implements SocialProvider {
|
|||
if (body.includes('usage-capped')) {
|
||||
return {
|
||||
type: 'refresh-token',
|
||||
value:
|
||||
'Posting failed - capped reached. Please try again later',
|
||||
value: 'Posting failed - capped reached. Please try again later',
|
||||
};
|
||||
}
|
||||
if (body.includes('duplicate-rules')) {
|
||||
|
|
@ -55,6 +54,17 @@ export class XProvider extends SocialAbstract implements SocialProvider {
|
|||
value: 'The Tweet contains a URL that is not allowed on X',
|
||||
};
|
||||
}
|
||||
if (
|
||||
body.includes(
|
||||
'This user is not allowed to post a video longer than 2 minutes'
|
||||
)
|
||||
) {
|
||||
return {
|
||||
type: 'bad-body',
|
||||
value:
|
||||
'The video you are trying to post is longer than 2 minutes, which is not allowed for this account',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -307,22 +317,24 @@ export class XProvider extends SocialAbstract implements SocialProvider {
|
|||
postDetails.flatMap((p) =>
|
||||
p?.media?.flatMap(async (m) => {
|
||||
return {
|
||||
id: await this.runInConcurrent(async () =>
|
||||
client.v1.uploadMedia(
|
||||
m.path.indexOf('mp4') > -1
|
||||
? Buffer.from(await readOrFetch(m.path))
|
||||
: await sharp(await readOrFetch(m.path), {
|
||||
animated: lookup(m.path) === 'image/gif',
|
||||
})
|
||||
.resize({
|
||||
width: 1000,
|
||||
id: await this.runInConcurrent(
|
||||
async () =>
|
||||
client.v1.uploadMedia(
|
||||
m.path.indexOf('mp4') > -1
|
||||
? Buffer.from(await readOrFetch(m.path))
|
||||
: await sharp(await readOrFetch(m.path), {
|
||||
animated: lookup(m.path) === 'image/gif',
|
||||
})
|
||||
.gif()
|
||||
.toBuffer(),
|
||||
{
|
||||
mimeType: lookup(m.path) || '',
|
||||
}
|
||||
)
|
||||
.resize({
|
||||
width: 1000,
|
||||
})
|
||||
.gif()
|
||||
.toBuffer(),
|
||||
{
|
||||
mimeType: lookup(m.path) || '',
|
||||
}
|
||||
),
|
||||
true
|
||||
),
|
||||
postId: p.id,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -222,7 +222,8 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
|
|||
media: {
|
||||
body: response.data,
|
||||
},
|
||||
})
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
if (settings?.thumbnail?.path) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
export interface NewsletterInterface {
|
||||
name: string;
|
||||
register(email: string): Promise<void>;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { newsletterProviders } from '@gitroom/nestjs-libraries/newsletter/providers';
|
||||
|
||||
export class NewsletterService {
|
||||
static getProvider() {
|
||||
if (process.env.BEEHIIVE_API_KEY) {
|
||||
return newsletterProviders.find((p) => p.name === 'beehiiv')!;
|
||||
}
|
||||
if (process.env.LISTMONK_API_KEY) {
|
||||
return newsletterProviders.find((p) => p.name === 'listmonk')!;
|
||||
}
|
||||
|
||||
return newsletterProviders.find((p) => p.name === 'empty')!;
|
||||
}
|
||||
static async register(email: string) {
|
||||
if (email.indexOf('@') === -1) {
|
||||
return;
|
||||
}
|
||||
return NewsletterService.getProvider().register(email);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { BeehiivProvider } from '@gitroom/nestjs-libraries/newsletter/providers/beehiiv.provider';
|
||||
import { EmailEmptyProvider } from '@gitroom/nestjs-libraries/newsletter/providers/email-empty.provider';
|
||||
import { ListmonkProvider } from '@gitroom/nestjs-libraries/newsletter/providers/listmonk.provider';
|
||||
|
||||
export const newsletterProviders = [
|
||||
new BeehiivProvider(),
|
||||
new ListmonkProvider(),
|
||||
new EmailEmptyProvider(),
|
||||
];
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
export class NewsletterService {
|
||||
static async register(email: string) {
|
||||
if (
|
||||
!process.env.BEEHIIVE_API_KEY ||
|
||||
!process.env.BEEHIIVE_PUBLICATION_ID ||
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
email.indexOf('@') === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';
|
||||
|
||||
export class BeehiivProvider implements NewsletterInterface {
|
||||
name = 'beehiiv';
|
||||
async register(email: string) {
|
||||
const body = {
|
||||
email,
|
||||
reactivate_existing: false,
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';
|
||||
|
||||
export class EmailEmptyProvider implements NewsletterInterface {
|
||||
name = 'empty';
|
||||
async register(email: string) {
|
||||
console.log('Could have registered to newsletter:', email);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';
|
||||
|
||||
export class ListmonkProvider implements NewsletterInterface {
|
||||
name = 'listmonk';
|
||||
async register(email: string) {
|
||||
const body = {
|
||||
email,
|
||||
status: 'enabled',
|
||||
lists: [+process.env.LISTMONK_LIST_ID].filter((f) => f),
|
||||
};
|
||||
|
||||
const authString = `${process.env.LISTMONK_USER}:${process.env.LISTMONK_API_KEY}`;
|
||||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'application/json');
|
||||
headers.set('Accept', 'application/json');
|
||||
headers.set(
|
||||
'Authorization',
|
||||
'Basic ' + Buffer.from(authString).toString('base64')
|
||||
);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { id },
|
||||
} = await (
|
||||
await fetch(`${process.env.LISTMONK_DOMAIN}/api/subscribers`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
).json();
|
||||
|
||||
const welcomeEmail = {
|
||||
subscriber_id: id,
|
||||
template_id: +process.env.LISTMONK_WELCOME_TEMPLATE_ID,
|
||||
subject: 'Welcome to Postiz 🚀',
|
||||
};
|
||||
|
||||
await fetch(`${process.env.LISTMONK_DOMAIN}/api/tx`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(welcomeEmail),
|
||||
});
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,8 +31,12 @@ export const initializeSentry = (appName: string, allowLogs = false) => {
|
|||
recordOutputs: true,
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.3,
|
||||
tracesSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.25,
|
||||
enableLogs: true,
|
||||
|
||||
// Profiling
|
||||
profileSessionSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.35,
|
||||
profileLifecycle: 'trace',
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
|
|
|||
|
|
@ -96,16 +96,18 @@ export class EmailService {
|
|||
</div>
|
||||
`;
|
||||
|
||||
const sends = await concurrency('send-email', 1, () =>
|
||||
this.emailService.sendEmail(
|
||||
try {
|
||||
const sends = await this.emailService.sendEmail(
|
||||
to,
|
||||
subject,
|
||||
modifiedHtml,
|
||||
process.env.EMAIL_FROM_NAME,
|
||||
process.env.EMAIL_FROM_ADDRESS,
|
||||
replyTo
|
||||
)
|
||||
);
|
||||
console.log(sends);
|
||||
);
|
||||
console.log(sends);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,31 +80,36 @@ class CloudflareStorage implements IUploadProvider {
|
|||
}
|
||||
|
||||
async uploadFile(file: Express.Multer.File): Promise<any> {
|
||||
const id = makeId(10);
|
||||
const extension = mime.extension(file.mimetype) || '';
|
||||
try {
|
||||
const id = makeId(10);
|
||||
const extension = mime.extension(file.mimetype) || '';
|
||||
|
||||
// Create the PutObjectCommand to upload the file to Cloudflare R2
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this._bucketName,
|
||||
ACL: 'public-read',
|
||||
Key: `${id}.${extension}`,
|
||||
Body: file.buffer,
|
||||
});
|
||||
// Create the PutObjectCommand to upload the file to Cloudflare R2
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this._bucketName,
|
||||
ACL: 'public-read',
|
||||
Key: `${id}.${extension}`,
|
||||
Body: file.buffer,
|
||||
});
|
||||
|
||||
await this._client.send(command);
|
||||
await this._client.send(command);
|
||||
|
||||
return {
|
||||
filename: `${id}.${extension}`,
|
||||
mimetype: file.mimetype,
|
||||
size: file.size,
|
||||
buffer: file.buffer,
|
||||
originalname: `${id}.${extension}`,
|
||||
fieldname: 'file',
|
||||
path: `${this._uploadUrl}/${id}.${extension}`,
|
||||
destination: `${this._uploadUrl}/${id}.${extension}`,
|
||||
encoding: '7bit',
|
||||
stream: file.buffer as any,
|
||||
};
|
||||
return {
|
||||
filename: `${id}.${extension}`,
|
||||
mimetype: file.mimetype,
|
||||
size: file.size,
|
||||
buffer: file.buffer,
|
||||
originalname: `${id}.${extension}`,
|
||||
fieldname: 'file',
|
||||
path: `${this._uploadUrl}/${id}.${extension}`,
|
||||
destination: `${this._uploadUrl}/${id}.${extension}`,
|
||||
encoding: '7bit',
|
||||
stream: file.buffer as any,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error uploading file to Cloudflare R2:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the removeFile method from IUploadProvider
|
||||
|
|
|
|||
|
|
@ -38,34 +38,39 @@ export class LocalStorage implements IUploadProvider {
|
|||
}
|
||||
|
||||
async uploadFile(file: Express.Multer.File): Promise<any> {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
try {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
|
||||
const innerPath = `/${year}/${month}/${day}`;
|
||||
const dir = `${this.uploadDirectory}${innerPath}`;
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const innerPath = `/${year}/${month}/${day}`;
|
||||
const dir = `${this.uploadDirectory}${innerPath}`;
|
||||
mkdirSync(dir, { recursive: true });
|
||||
|
||||
const randomName = Array(32)
|
||||
.fill(null)
|
||||
.map(() => Math.round(Math.random() * 16).toString(16))
|
||||
.join('');
|
||||
const randomName = Array(32)
|
||||
.fill(null)
|
||||
.map(() => Math.round(Math.random() * 16).toString(16))
|
||||
.join('');
|
||||
|
||||
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
|
||||
const publicPath = `${innerPath}/${randomName}${extname(
|
||||
file.originalname
|
||||
)}`;
|
||||
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
|
||||
const publicPath = `${innerPath}/${randomName}${extname(
|
||||
file.originalname
|
||||
)}`;
|
||||
|
||||
// Logic to save the file to the filesystem goes here
|
||||
writeFileSync(filePath, file.buffer);
|
||||
// Logic to save the file to the filesystem goes here
|
||||
writeFileSync(filePath, file.buffer);
|
||||
|
||||
return {
|
||||
filename: `${randomName}${extname(file.originalname)}`,
|
||||
path: process.env.FRONTEND_URL + '/uploads' + publicPath,
|
||||
mimetype: file.mimetype,
|
||||
originalname: file.originalname,
|
||||
};
|
||||
return {
|
||||
filename: `${randomName}${extname(file.originalname)}`,
|
||||
path: process.env.FRONTEND_URL + '/uploads' + publicPath,
|
||||
mimetype: file.mimetype,
|
||||
originalname: file.originalname,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error uploading file to Local Storage:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async removeFile(filePath: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const Checkbox = forwardRef<
|
|||
{...disableForm ? {} : form.register(props.name!)}
|
||||
onClick={changeStatus}
|
||||
className={clsx(
|
||||
'cursor-pointer rounded-[4px] select-none w-[24px] h-[24px] justify-center items-center flex',
|
||||
'cursor-pointer rounded-[4px] select-none w-[24px] h-[24px] justify-center items-center flex text-white',
|
||||
variant === 'default' || !variant
|
||||
? 'bg-forth'
|
||||
: 'border-customColor1 border-2 bg-customColor2',
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import { Slider } from '@mantine/core';
|
||||
import { FC } from 'react';
|
||||
export const Track: FC<{
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
onChange: (value: number) => void;
|
||||
}> = (props) => {
|
||||
const { value, onChange, min, max } = props;
|
||||
return (
|
||||
<Slider
|
||||
color="violet"
|
||||
labelAlwaysOn={true}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
size="xl"
|
||||
classNames={{
|
||||
track:
|
||||
'before:bg-customColor3 before:border before:border-customColor6',
|
||||
mark: 'border-customColor6',
|
||||
markFilled: 'border-customColor7',
|
||||
}}
|
||||
min={min}
|
||||
max={max}
|
||||
// classNames={{
|
||||
// track: 'h-[15px]',
|
||||
// thumb: 'w-[24px] h-[24px]',
|
||||
// }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Swal from 'sweetalert2';
|
||||
import i18next from '@gitroom/react/translation/i18next';
|
||||
import { areYouSure } from '@gitroom/frontend/components/layout/new-modal';
|
||||
|
||||
export const deleteDialog = async (
|
||||
message: string,
|
||||
|
|
@ -7,14 +7,11 @@ export const deleteDialog = async (
|
|||
title?: string,
|
||||
cancelButton?: string
|
||||
) => {
|
||||
const fire = await Swal.fire({
|
||||
return areYouSure({
|
||||
title: title || i18next.t('are_you_sure', 'Are you sure?'),
|
||||
text: message,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText:
|
||||
description: message,
|
||||
approveLabel:
|
||||
confirmButton || i18next.t('yes_delete_it', 'Yes, delete it!'),
|
||||
cancelButtonText: cancelButton || i18next.t('no_cancel', 'No, cancel!'),
|
||||
cancelLabel: cancelButton || i18next.t('no_cancel', 'No, cancel!'),
|
||||
});
|
||||
return fire.isConfirmed;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,26 +1,15 @@
|
|||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import i18next from '@gitroom/react/translation/i18next';
|
||||
import {
|
||||
DecisionEverywhere,
|
||||
ModalManager,
|
||||
} from '@gitroom/frontend/components/layout/new-modal';
|
||||
export const MantineWrapper = (props: { children: ReactNode }) => {
|
||||
const dir = i18next.dir();
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<MantineProvider>
|
||||
<ModalsProvider
|
||||
modalProps={{
|
||||
dir,
|
||||
classNames: {
|
||||
modal: 'bg-primary text-white',
|
||||
close: 'bg-black hover:bg-black cursor-pointer',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ModalsProvider>
|
||||
</MantineProvider>
|
||||
<ModalManager>
|
||||
<DecisionEverywhere />
|
||||
{props.children}
|
||||
</ModalManager>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const initializeSentryClient = (environment: string, dsn: string) =>
|
|||
integrations: [
|
||||
// Add default integrations back
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.browserProfilingIntegration(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: true,
|
||||
maskAllInputs: true,
|
||||
|
|
@ -15,6 +16,8 @@ export const initializeSentryClient = (environment: string, dsn: string) =>
|
|||
autoInject: false,
|
||||
}),
|
||||
],
|
||||
replaysSessionSampleRate: environment === 'development' ? 1.0 : 0.1,
|
||||
replaysOnErrorSampleRate: environment === 'development' ? 1.0 : 0.1,
|
||||
replaysSessionSampleRate: environment === 'development' ? 1.0 : 0.5,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
profilesSampleRate: environment === 'development' ? 1.0 : 0.2,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,505 @@
|
|||
{
|
||||
"calendar": "კალენდარი",
|
||||
"webhooks": "ვებჰუქები",
|
||||
"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request": "ვებჰუქები საშუალებას გაძლევთ მიიღოთ შეტყობინება, როცა Postiz-ში რაიმე ხდება HTTP მოთხოვნის მეშვეობით.",
|
||||
"name": "სახელი",
|
||||
"url": "ბმული",
|
||||
"edit": "რედაქტირება",
|
||||
"delete": "წაშლა",
|
||||
"add_a_webhook": "ვებჰუქის დამატება",
|
||||
"save": "შენახვა",
|
||||
"send_test": "ტესტის გაგზავნა",
|
||||
"select_role": "როლის არჩევა",
|
||||
"video_made_with_ai": "ვიდეო შექმნილია AI-ის მეშვეობით",
|
||||
"please_add_at_least": "გთხოვთ დაამატოთ მინიმუმ 20 სიმბოლო",
|
||||
"send_invitation_via_email": "მოწვევა გადაიგზავნოს ელფოსტით?",
|
||||
"global_settings": "გლობალური პარამეტრები",
|
||||
"copy_id": "არხის ID-ის კოპირება",
|
||||
"team_members": "გუნდის წევრები",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "მოიწვიეთ ასისტენტი ან გუნდის წევრი თქვენი ანგარიშის სამართავად",
|
||||
"remove": "წაშლა",
|
||||
"add_another_member": "სხვა წევრის დამატება",
|
||||
"signatures": "ხელმოწერები",
|
||||
"you_can_add_signatures_to_your_account_to_be_used_in_your_posts": "შეგიძლიათ დაამატოთ ხელმოწერები, რომლებიც გამოყენებული იქნება თქვენს პოსტებში.",
|
||||
"content": "კონტენტი",
|
||||
"auto_add": "ავტომატურად დამატება?",
|
||||
"actions": "ქმედებები",
|
||||
"use_signature": "ხელმოწერის გამოყენება",
|
||||
"add_a_signature": "ხელმოწერის დამატება",
|
||||
"no": "არა",
|
||||
"yes": "კი",
|
||||
"your_git_repository": "თქვენი Git რეპოზიტორია",
|
||||
"connect_your_github_repository_to_receive_updates_and_analytics": "დააკავშირეთ თქვენი GitHub რეპოზიტორია განახლებებისა და ანალიტიკის მისაღებად",
|
||||
"connected": "დაკავშირებულია:",
|
||||
"disconnect": "გათიშვა",
|
||||
"connect_your_repository": "რეპოზიტორიის დაკავშირება",
|
||||
"cancel": "გაუქმება",
|
||||
"connect": "დაკავშირება",
|
||||
"public_api": "საჯარო API",
|
||||
"check_n8n": "იხილეთ ჩვენი N8N მორგებული node Postiz-ისთვის.",
|
||||
"use_postiz_api_to_integrate_with_your_tools": "გამოიყენეთ Postiz API თქვენი ინსტრუმენტების ინტეგრაციისთვის.",
|
||||
"read_how_to_use_it_over_the_documentation": "იხილეთ დეტალები დოკუმენტაციაში.",
|
||||
"reveal": "ჩვენება",
|
||||
"copy_key": "გასაღების კოპირება",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "დააკავშირეთ MCP-კლიენტი Postiz-სთან პოსტების უფრო სწრაფად დასაგეგმად!",
|
||||
"share_with_a_client": "კლიენტთან გაზიარება",
|
||||
"post": "პოსტი",
|
||||
"comments": "კომენტარები",
|
||||
"user": "მომხმარებელი",
|
||||
"login_register_to_add_comments": "კომენტარის დასამატებლად გაიარეთ ავტორიზაცია ან რეგისტრაცია",
|
||||
"status": "სტატუსი:",
|
||||
"there_are_not_plugs_matching_your_channels": "თქვენს არხებთან შესაბამისი მოდულები ვერ მოიძებნა",
|
||||
"you_have_to_add_x_or_linkedin_or_threads": "უნდა დაამატოთ: X, LinkedIn ან Threads",
|
||||
"go_to_the_calendar_to_add_channels": "გადადით კალენდარში არხების დასამატებლად",
|
||||
"channels": "არხები",
|
||||
"activate": "აქტივაცია",
|
||||
"this_channel_needs_to_be_refreshed": "ამ არხს განახლება სჭირდება,",
|
||||
"click_here_to_refresh": "დააჭირეთ აქ განახლებისთვის",
|
||||
"can_t_show_analytics_yet": "ჯერ ანალიტიკა მიუწვდომელია",
|
||||
"you_have_to_add_social_media_channels": "დაამატეთ სოციალური მედიის არხები",
|
||||
"supported": "მხარდაჭერილია:",
|
||||
"step": "ნაბიჯი",
|
||||
"skip_onboarding": "გაშვების გამოტოვება",
|
||||
"onboarding": "გაშვება",
|
||||
"next": "შემდეგი",
|
||||
"you_are_done_from_here_you_can": "მზადაა! აქედან შეგიძლიათ:",
|
||||
"view_analytics": "ანალიტიკის ნახვა",
|
||||
"schedule_a_new_post": "ახალი პოსტის დაგეგმვა",
|
||||
"to_sell_posts_you_would_have_to": "პოსტების გასაყიდად საჭიროა:",
|
||||
"1_connect_at_least_one_channel": "1. მინიმუმ ერთი არხის დაკავშირება",
|
||||
"2_connect_you_bank_account": "2. საბანკო ანგარიშის დაკავშირება",
|
||||
"go_back_to_connect_channels": "დაბრუნდით არხების დასაკავშირებლად",
|
||||
"move_to_the_seller_page_to_connect_you_bank": "გადადით გამყიდველის გვერდზე საბანკო ანგარიშის დასაკავშირებლად",
|
||||
"connect_channels": "არხების დაკავშირება",
|
||||
"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later": "დააკავშირეთ სოციალური მედიისა და გამოქვეყნების არხები, რომ შემდეგ დაგეგმოთ პოსტები",
|
||||
"social": "სოციალური",
|
||||
"publishing_platforms": "გამოქვეყნების პლატფორმები",
|
||||
"no_channels": "არხები ჯერ არ არის",
|
||||
"connect_your_accounts": "დააბმითეთ თქვენი ანგარიშები — დაგეგმვა, გამოქვეყნება და ანალიზი ერთ სივრცეში.",
|
||||
"notifications": "შეტყობინებები",
|
||||
"no_notifications": "შეტყობინებები არ არის",
|
||||
"send_message": "შეტყობინების გაგზავნა",
|
||||
"mar_28": "მარტი 28",
|
||||
"there_are_no_messages_yet": "ჯერ არ არის შეტყობინებები.",
|
||||
"checkout_the_marketplace": "ეწვიეთ მარკეტპლეისს",
|
||||
"go_to_marketplace": "გადასვლა მარკეტპლეისში",
|
||||
"all_messages": "ყველა შეტყობინება",
|
||||
"previous": "წინა",
|
||||
"select_or_upload_pictures_maximum_5_at_a_time": "აირჩიეთ ან ატვირთეთ სურათები (მაქს. 5 ერთდროულად)",
|
||||
"you_can_also_drag_drop_pictures": "ასევე შეგიძლიათ გადაათრიოთ და ჩააგდოთ სურათები",
|
||||
"you_don_t_have_any_assets_yet": "ჯერ არ გაქვთ ასეტები",
|
||||
"click_the_button_below_to_upload_one": "დააჭირეთ ქვედა ღილაკს ატვირთვისთვის",
|
||||
"add_selected_media": "არჩეული მედიის დამატება",
|
||||
"insert_media": "მედიის ჩასმა",
|
||||
"design_media": "მედიის დიზაინი",
|
||||
"select": "არჩევა",
|
||||
"editor": "რედაქტორი",
|
||||
"clear": "გასუფთავება",
|
||||
"order_completed": "შეკვეთა დასრულდა",
|
||||
"the_order_has_been_completed": "შეკვეთა დასრულებულია",
|
||||
"post_has_been_published": "პოსტი გამოქვეყნდა",
|
||||
"url_1": "ბმული:",
|
||||
"new_offer": "ახალი შეთავაზება",
|
||||
"platform": "პლატფორმა",
|
||||
"posts": "პოსტები",
|
||||
"pay_accept_offer": "გადახდა და შეთავაზების მიღება",
|
||||
"accepted": "მიღებულია",
|
||||
"post_draft": "პოსტის მონახაზი",
|
||||
"revision_needed": "საჭიროა გადახედვა",
|
||||
"approve": "დამტკიცება",
|
||||
"preview": "გადახედვა",
|
||||
"revision_requested": "მოთხოვნილია გადახედვა",
|
||||
"accepted_1": "მიღებულია",
|
||||
"cancelled_by_the_seller": "გაუქმდა გამყიდველის მიერ",
|
||||
"please_select_your_country_where_your_business_is": "აირჩიეთ თქვენი ბიზნესის ქვეყანა",
|
||||
"select_country": "--აირჩიეთ ქვეყანა--",
|
||||
"connect_bank_account": "საბანკო ანგარიშის დაკავშირება",
|
||||
"seller_mode": "გამყიდველის რეჟიმი",
|
||||
"active": "აქტიური",
|
||||
"details": "დეტალები",
|
||||
"audience_size": "აუდიტორიის ზომა",
|
||||
"add_another_platform": "სხვა პლატფორმის დამატება",
|
||||
"send_an_offer_for": "შეთავაზების გაგზავნა $",
|
||||
"complete_order_and_pay_early": "შეკვეთის დასრულება და გადახდა",
|
||||
"order_in_progress": "შეკვეთა პროცესშია",
|
||||
"create_a_new_offer": "ახალი შეთავაზების შექმნა",
|
||||
"orders": "შეკვეთები",
|
||||
"price": "ფასი",
|
||||
"state": "სტატუსი",
|
||||
"showing": "ნაჩვენებია",
|
||||
"to": "მდე",
|
||||
"from": "დან",
|
||||
"results": "შედეგები",
|
||||
"content_writer": "კონტენტის ავტორი",
|
||||
"influencer": "ინფლუენსერი",
|
||||
"request_service": "მომსახურების მოთხოვნა",
|
||||
"the_marketplace_is_not_opened_yet": "მარკეტპლეისი ჯერ არ არის გახსნილი",
|
||||
"check_again_soon": "შეამოწმეთ მალე!",
|
||||
"filter": "ფილტრი",
|
||||
"result": "შედეგი",
|
||||
"seller": "გამყიდველი",
|
||||
"buyer": "მყიდველი",
|
||||
"discord_support": "Discord მხარდაჭერა",
|
||||
"teams": "გუნდები",
|
||||
"webhooks_1": "ვებჰუქები",
|
||||
"auto_post": "ავტო-პოსტი",
|
||||
"logout_from": "გასვლა ანგარიშიდან",
|
||||
"join_10000_entrepreneurs_who_use_postiz": "შეუერთდით 10,000+ მეწარმეს, ვინც იყენებს Postiz-ს",
|
||||
"to_manage_all_your_social_media_channels": "ყველა სოციალური არხის სამართავად",
|
||||
"100_no_risk_trial": "100% რისკის გარეშე საცდელი პერიოდი",
|
||||
"pay_nothing_for_the_first_7_days": "პირველი 7 დღე უფასოა",
|
||||
"cancel_anytime_hassle_free": "გაუქმება ნებისმიერ დროს, უპრობლემოდ",
|
||||
"add_free_subscription": "-- უფასო გამოწერის დამატება --",
|
||||
"currently_impersonating": "ამჟამად იმპერსონაციაა",
|
||||
"user_1": "მომხმარებელი:",
|
||||
"drag_n_drop_some_files_here": "გადაათრიეთ ფაილები აქ",
|
||||
"add_time_slot": "დროის სლოტის დამატება",
|
||||
"add_slot": "სლოტის დამატება",
|
||||
"cancel_publication": "გამოქვეყნების გაუქმება",
|
||||
"statistics": "სტატისტიკა",
|
||||
"loading": "იტვირთება",
|
||||
"short_link": "მოკლე ბმული",
|
||||
"original_link": "ორიგინალი ბმული",
|
||||
"clicks": "კლიკები",
|
||||
"selected_customer": "არჩეული კლიენტი",
|
||||
"customer": "კლიენტი:",
|
||||
"repeat_post_every": "პოსტის გამეორება ყოველ...",
|
||||
"use_this_media": "ამ მედიის გამოყენება",
|
||||
"create_new_post": "პოსტის შექმნა",
|
||||
"update_post": "არსებული პოსტის განახლება",
|
||||
"merge_comments_into_one_post": "კომენტარების გაერთიანება ერთ პოსტად",
|
||||
"accounts_that_will_engage": "ანგარიშები, რომლებიც ჩაერთვებიან:",
|
||||
"day": "დღე",
|
||||
"week": "კვირა",
|
||||
"month": "თვე",
|
||||
"remove_from_customer": "კლიენტიდან მოხსნა",
|
||||
"show_more": "+ მეტი",
|
||||
"show_less": "- ნაკლები",
|
||||
"upload": "ატვირთვა",
|
||||
"ai": "AI",
|
||||
"add_channel": "არხის დამატება",
|
||||
"add_platform": "პლატფორმის დამატება",
|
||||
"articles": "სტატიები",
|
||||
"add_comment": "კომენტარის დამატება",
|
||||
"add_post": "პოსტის დამატება თრედში",
|
||||
"add_comment_or_post": "კომენტარის / პოსტის დამატება",
|
||||
"you_are_in_global_editing_mode": "გლობალური რედაქტირების რეჟიმში ხართ",
|
||||
"the_post_should_be_at_least_6_characters_long": "პოსტი უნდა შეიცავდეს მინიმუმ 6 სიმბოლოს",
|
||||
"are_you_sure_you_want_to_delete_post": "დარწმუნებული ხართ, რომ წაშალოთ ეს პოსტი?",
|
||||
"post_deleted_successfully": "პოსტი წარმატებით წაიშალა",
|
||||
"delete_post": "პოსტს წაშლა",
|
||||
"save_as_draft": "მონახაზად შენახვა",
|
||||
"post_now": "გამოქვეყნება ახლა",
|
||||
"please_add": "დაამატეთ, გთხოვთ",
|
||||
"to_your_telegram_group_channel_and_click_here": "თქვენს Telegram ჯგუფში/არხში და შემდეგ დააჭირეთ აქ:",
|
||||
"connect_telegram": "Telegram-ის დაკავშირება",
|
||||
"please_add_the_following_command_in_your_chat": "დაამატეთ ჩატში შემდეგი ბრძანება:",
|
||||
"copy": "კოპირება",
|
||||
"settings": "პარამეტრები",
|
||||
"integrations": "ინტეგრაციები",
|
||||
"add_integration": "ინტეგრაციის დამატება",
|
||||
"you_are_now_editing_only": "ახლა რედაქტირებ მხოლოდ",
|
||||
"tag_a_company": "მიანიჭე ტეგი კომპანიას",
|
||||
"video_length_is_invalid_must_be_up_to": "ვიდეოს სიგრძე არასწორია, უნდა იყოს მაქსიმუმ",
|
||||
"seconds": "წამი",
|
||||
"this_feature_available_only_for_photos": "ეს ფუნქცია ხელმისაწვდომია მხოლოდ ფოტოებისთვის — დაემატება ნაგულისხმევი მუსიკა, რომელსაც შემდეგ შეცვლით.",
|
||||
"allow_user_to": "ნება მიეცით მომხმარებელს:",
|
||||
"your_video_will_be_labeled_promotional": "თქვენი ვიდეო მოინათლება როგორც \"რეკლამიური კონტენტი\".",
|
||||
"this_cannot_be_changed_once_posted": "ეს ვერ შეიცვლება პოსტის გამოქვეყნების შემდეგ.",
|
||||
"turn_on_to_disclose_video_promotes": "ჩართეთ, რათა მიუთითოთ, რომ ვიდეო ხელს უწყობს საქონელს ან მომსახურებას ღირებულების სანაცვლოდ. ვიდეო შესაძლოა ასახავდეს თქვენ, მესამე პირს ან ორივეს.",
|
||||
"you_are_promoting_yourself": "აქვეყნებთ თქვენი ბრენდის რეკლამას.",
|
||||
"this_video_will_be_classified_brand_organic": "ვიდეო კლასიფიცირდება როგორც Brand Organic.",
|
||||
"you_are_promoting_another_brand": "აქვეყნებთ სხვა ბრენდის/მესამე მხარის რეკლამას.",
|
||||
"this_video_will_be_classified_branded_content": "ვიდეო კლასიფიცირდება როგორც Branded Content.",
|
||||
"by_posting_you_agree_to_tiktoks": "პოსტის გამოქვეყნებით ეთანხმებით TikTok-ის",
|
||||
"music_usage_confirmation": "მუსიკის გამოყენების დადასტურებას",
|
||||
"branded_content_policy": "ბრენდირებული კონტენტის პოლიტიკას",
|
||||
"select_1": "--აირჩიეთ--",
|
||||
"select_flair": "--აირჩიეთ ფლეარი--",
|
||||
"link": "ბმული",
|
||||
"add_subreddit": "Subreddit-ის დამატება",
|
||||
"please_add_at_least_one_subreddit": "დაამატეთ მინიმუმ ერთი Subreddit",
|
||||
"add_community": "Community-ის დამატება",
|
||||
"select_post_type": "აირჩიეთ პოსტის ტიპი...",
|
||||
"we_couldn_t_find_any_business_connected_to_your_linkedin_page": "LinkedIn გვერდს მიერთებული ბიზნესი ვერ ვიპოვეთ.",
|
||||
"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again": "დახურეთ ეს ფანჯარა, შექმენით ახალი გვერდი და თავიდან დაამატეთ არხი.",
|
||||
"select_linkedin_page": "აირჩიეთ LinkedIn გვერდი:",
|
||||
"we_couldn_t_find_any_business_connected_to_the_selected_pages": "არჩეულ გვერდებს მიერთებული ბიზნესი ვერ ვიპოვეთ.",
|
||||
"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses": "გირჩევთ დააკავშიროთ ყველა გვერდი და ყველა ბიზნესი.",
|
||||
"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again": "დახურეთ ეს ფანჯარა, წაშალეთ ინტეგრაცია და თავიდან დაამატეთ არხი.",
|
||||
"select_instagram_account": "აირჩიეთ Instagram ანგარიში:",
|
||||
"select_page": "გვერდის არჩევა:",
|
||||
"generate_image_with_ai": "სურათის გენერირება AI-ით",
|
||||
"reconnect_channel": "არხის ხელახლა დაკავშირება",
|
||||
"update_credentials": "მომართვების განახლება",
|
||||
"additional_settings": "დამატებითი პარამეტრები",
|
||||
"change_bot": "ბოტის შეცვლა",
|
||||
"move_add_to_customer": "გადატანა / დამატება კლიენტზე",
|
||||
"edit_time_slots": "დროის სლოტების რედაქტირება",
|
||||
"enable_channel": "არხის ჩართვა",
|
||||
"disable_channel": "არხის გამორთვა",
|
||||
"add": "დამატება",
|
||||
"short_post": "მოკლე პოსტი",
|
||||
"long_post": "გრძელი პოსტი",
|
||||
"a_thread_with_short_posts": "თრედი მოკლე პოსტებით",
|
||||
"a_thread_with_long_posts": "თრედი გრძელი პოსტებით",
|
||||
"personal_voice_i_am_happy_to_announce": "პირადი ხმით (\"მიხარია განცხადება...\")",
|
||||
"company_voice_we_are_happy_to_announce": "კომპანიის ხმით (\"მოხარულები ვართ, რომ ვაცხადებთ...\")",
|
||||
"generate": "გენერირება",
|
||||
"generate_posts": "პოსტების გენერირება",
|
||||
"purchase_a_life_time_pro_account_with_sol_199": "შეიძინეთ სამუდამო PRO ანგარიში SOL-ით ($199). გთხოვთ გაითვალისწინოთ, რომ ანაზღაურება არ ხდება.",
|
||||
"purchase_now": "ყიდვა ახლა",
|
||||
"pay_today": "გადახდა დღეს",
|
||||
"we_are_sorry_to_see_you_go": "ანანთაით ხართ, რომ მიდიხართ :(",
|
||||
"would_you_mind_shortly_tell_us_what_we_could_have_done_better": "გვისურვებდით მოკლედ გვეცნობრებინათ, რა შეგვეძლო გაგვეკეთებინა უკეთ?",
|
||||
"cancel_subscription": "გამოწერის გაუქმება",
|
||||
"plans": "გეგმები",
|
||||
"monthly": "თვიური",
|
||||
"yearly": "წლიური",
|
||||
"reactivate_subscription": "გამოწერის ხელახლა აქტივაცია",
|
||||
"update_payment_method_invoices_history": "გადახდის მეთოდის განახლება / ინვოისების ისტორია",
|
||||
"cancel_subscription_1": "გამოწერის გაუქმება",
|
||||
"your_subscription_will_be_canceled_at": "თქვენი გამოწერა გაუქმდება თარიღზე:",
|
||||
"you_will_never_be_charged_again": "სხვა აღარ ჩამოგეჭრებათ თანხა",
|
||||
"current_package": "მიმდინარე პაკეტი:",
|
||||
"next_package": "შემდეგი პაკეტი:",
|
||||
"claim": "მოთხოვნა",
|
||||
"frequently_asked_questions": "ხშირად დასმული კითხვები",
|
||||
"autopost": "ავტოპოსტი",
|
||||
"autopost_can_automatically_posts_your_rss_new_items_to_social_media": "ავტოპოსტი ავტომატურად გამოაქვეყნებს თქვენს RSS-ის ახალ ჩანაწერებს სოციალურ მედია არხებზე",
|
||||
"title": "სათაური",
|
||||
"add_an_autopost": "ავტოპოსტის დამატება",
|
||||
"post_content": "პოსტის შინაარსი",
|
||||
"sign_up": "რეგისტრაცია",
|
||||
"or": "ან",
|
||||
"by_registering_you_agree_to_our": "რეგისტრაციით ეთანხმებით ჩვენს",
|
||||
"and": "და",
|
||||
"terms_of_service": "გამოყენების პირობებს",
|
||||
"privacy_policy": "კონფიდენციალურობის პოლიტიკას",
|
||||
"create_account": "ანგარიშის შექმნა",
|
||||
"already_have_an_account": "უკვე გაქვთ ანგარიში?",
|
||||
"sign_in": "შესვლა",
|
||||
"sign_in_1": "შესვლა",
|
||||
"don_t_have_an_account": "არ გაქვთ ანგარიში?",
|
||||
"forgot_password": "დაგავიწყდათ პაროლი",
|
||||
"forgot_password_1": "პაროლის აღდგენა",
|
||||
"send_password_reset_email": "პაროლის აღდგენის წერილის გაგზავნა",
|
||||
"go_back_to_login": "დაბრუნება შესვლაზე",
|
||||
"we_have_send_you_an_email_with_a_link_to_reset_your_password": "გაგიგზავნეთ ელფოსტა პაროლის აღდგენის ბმულით.",
|
||||
"change_password": "პაროლის შეცვლა",
|
||||
"we_successfully_reset_your_password_you_can_now_login_with_your": "პაროლი წარმატებით აღდგა. შეგიძლიათ შეხვიდეთ თქვენი",
|
||||
"click_here_to_go_back_to_login": "დააჭირეთ აქ შესვლაზე დასაბრუნებლად",
|
||||
"activate_your_account": "ანგარიშის გააქტიურება",
|
||||
"thank_you_for_registering": "გმადლობთ რეგისტრაციისთვის!",
|
||||
"please_check_your_email_to_activate_your_account": "გთხოვთ, გადაამოწმოთ ელფოსტა ანგარიშის გასააქტიურებლად.",
|
||||
"sign_in_with": "შესვლა შემდეგით",
|
||||
"continue_with_google": "გაგრძელება Google-ით",
|
||||
"sign_in_with_github": "შესვლა GitHub-ით",
|
||||
"continue_with_farcaster": "გაგრძელება Farcaster-ით",
|
||||
"continue_with_your_wallet": "გაგრძელება საფულით",
|
||||
"stars_per_day": "ვარსკვლავები დღეში",
|
||||
"media": "მედია",
|
||||
"check_launch": "გაშვების გადამოწმება",
|
||||
"load_your_github_repository_from_settings_to_see_analytics": "ჩატვირთეთ GitHub რეპოზიტორია პარამეტრებიდან ანალიტიკის სანახავად",
|
||||
"stars": "ვარსკვლავები",
|
||||
"processing_stars": "ვარსკვლავების დამუშავება...",
|
||||
"forks": "ფორკები",
|
||||
"registration_is_disabled": "რეგისტრაცია გათიშულია",
|
||||
"login_instead": "შემოსვლა",
|
||||
"gitroom": "Gitroom",
|
||||
"select_a_conversation_and_chat_away": "აირჩიეთ საუბარი და დაიწყეთ ჩათი",
|
||||
"adding_channel_redirecting_you": "არხის დამატება... გადამისამართება",
|
||||
"could_not_add_provider": "პროვაიდერის დამატება ვერ მოხერხდა.",
|
||||
"you_are_being_redirected_back": "ბრუნდებით უკან",
|
||||
"we_are_experiencing_some_difficulty_try_to_refresh_the_page": "ამჟამად ჭირს მუშაობა, სცადეთ გვერდის განახლება",
|
||||
"post_not_found": "პოსტი ვერ მოიძებნა",
|
||||
"publication_date": "გამოქვეყნების თარიღი:",
|
||||
"analytics": "ანალიტიკა",
|
||||
"launches": "გაშვებები",
|
||||
"plugs": "მოდულები",
|
||||
"billing": "ბილინგი",
|
||||
"affiliate": "აფილியேიტი",
|
||||
"monday": "ორშაბათი",
|
||||
"tuesday": "სამშაბათი",
|
||||
"wednesday": "ოთხშაბათი",
|
||||
"thursday": "ხუთშაბათი",
|
||||
"friday": "პარასკევი",
|
||||
"saturday": "შაბათი",
|
||||
"sunday": "კვირა",
|
||||
"can_t_change_date_remove_post_from_publication": "თარიღის შეცვლა შეუძლებელია — მოხსენით პოსტი გამოქვეყნებიდან",
|
||||
"predicted_github_trending_change": "პროგნოზირებული ცვლილება GitHub Trending-ში",
|
||||
"duplicate_post": "პოსტის დუბლირება",
|
||||
"preview_post": "პოსტზე წინასწარი გადახედვა",
|
||||
"post_statistics": "პოსტის სტატისტიკა",
|
||||
"draft": "მონახაზი",
|
||||
"week_number": "კვირა {{number}}",
|
||||
"top_title_edit_webhook": "ვებჰუქის რედაქტირება",
|
||||
"top_title_add_webhook": "ვებჰუქის დამატება",
|
||||
"top_title_oh_no": "ო, არა",
|
||||
"top_title_auto_plug": "ავტომოდული: {{title}}",
|
||||
"top_title_edit_autopost": "ავტოპოსტის რედაქტირება",
|
||||
"top_title_add_autopost": "ავტოპოსტის დამატება",
|
||||
"top_title_send_a_new_offer": "ახალი შეთავაზების გაგზავნა",
|
||||
"top_title_media_library": "მედიალაიბრარი",
|
||||
"top_title_add_signature": "ხელმოწერის დამატება",
|
||||
"top_title_send_a_message_to": "შეტყობინების გაგზავნა: {{name}}",
|
||||
"top_title_configure_provider": "პროვაიდერის კონფიგურაცია",
|
||||
"top_title_add_member": "წევრის დამატება",
|
||||
"top_title_change_bot_picture": "ბოტის სურათის შეცვლა",
|
||||
"top_title_create_a_new_tag": "ახალი ტეგის შექმნა",
|
||||
"top_title_select_company": "კომპანიის არჩევა",
|
||||
"top_title_additional_settings": "დამატებითი პარამეტრები",
|
||||
"top_title_time_table_slots": "დროის ცხრილი / სლოტები",
|
||||
"top_title_design_media": "მედიის დიზაინი",
|
||||
"top_title_edit_post": "პოსტის რედაქტირება",
|
||||
"top_title_create_post": "ახალი პოსტის შექმნა",
|
||||
"top_title_move__add_to_customer": "გადატანა / დამატება კლიენტზე",
|
||||
"top_title_add_api_key_for": "API გასაღები — {{name}}",
|
||||
"top_title_instance_url": "ინსტანციის URL",
|
||||
"top_title_custom_url": "მორგებული URL",
|
||||
"top_title_add_channel": "არხის დამატება",
|
||||
"top_title_add_telegram": "Telegram-ის დამატება",
|
||||
"top_title_add_wrapcast": "Wrapcast-ის დამატება",
|
||||
"top_title_comments_for": "კომენტარები — {{date}}",
|
||||
"top_title_edit_signature": "ხელმოწერის რედაქტირება",
|
||||
"label_name": "სახელი",
|
||||
"label_url": "ბმული",
|
||||
"label_title": "სათაური",
|
||||
"label_subtitle": "ქვესათაური",
|
||||
"label_email": "ელ.ფოსტა",
|
||||
"label_full_name": "სრული სახელი",
|
||||
"label_password": "პაროლი",
|
||||
"label_confirm_password": "პაროლის დადასტურება",
|
||||
"label_api_key": "API გასაღები",
|
||||
"label_instance_url": "ინსტანციის URL",
|
||||
"label_custom_url": "მორგებული URL",
|
||||
"label_feedback": "უკუკავშირი",
|
||||
"label_bio": "ბიო",
|
||||
"label_role": "როლი",
|
||||
"label_country": "ქვეყანა",
|
||||
"label_audience_size": "აუდიტორიის ზომა ყველა პლატფორმაზე",
|
||||
"label_pick_time": "დროის არჩევა",
|
||||
"label_nickname": "მეტსახელი",
|
||||
"label_write_anything": "დაწერეთ რაც გსურთ",
|
||||
"label_output_format": "გამოტანის ფორმატი",
|
||||
"label_add_pictures": "დავამატოთ სურათები?",
|
||||
"label_hour": "საათი",
|
||||
"label_minutes": "წუთი",
|
||||
"label_select_publication": "აირჩიეთ პუბლიკაცია",
|
||||
"label_canonical_link": "კანონიკური ბმული",
|
||||
"label_cover_picture": "ქოვერის სურათი",
|
||||
"label_tags": "ტეგები",
|
||||
"label_topics": "თემები",
|
||||
"label_tags_maximum_4": "ტეგები (მაქს. 4)",
|
||||
"label_attachments": "დანართები",
|
||||
"label_type": "ტიპი",
|
||||
"label_thumbnail": "თამბნეილი",
|
||||
"label_who_can_see_this_video": "ვის შეუძლია ამ ვიდეოს ნახვა?",
|
||||
"label_content_posting_method": "კონტენტის გამოქვეყნების მეთოდი",
|
||||
"label_auto_add_music": "მუსიკის ავტომატური დამატება",
|
||||
"label_duet": "დუეტი",
|
||||
"label_stitch": "Stitch",
|
||||
"label_comments": "კომენტარები",
|
||||
"label_disclose_video_content": "ვიდეოს შინაარსის გამჟღავნება",
|
||||
"label_your_brand": "თქვენი ბრენდი",
|
||||
"label_branded_content": "ბრენდირებული კონტენტი",
|
||||
"label_subreddit": "Subreddit",
|
||||
"label_flair": "Flair",
|
||||
"label_media": "მედია",
|
||||
"label_search_subreddit": "Subreddit-ის ძიება",
|
||||
"label_delay": "დაყოვნება",
|
||||
"label_post_type": "პოსტის ტიპი",
|
||||
"label_collaborators": "თანაავტორები (მაქს. 3) — ანგარიშები არ უნდა იყოს პრაივეტი",
|
||||
"label_community": "Community",
|
||||
"label_search_community": "Community-ის ძიება",
|
||||
"label_channel": "არხი",
|
||||
"label_search_channel": "არხის ძიება",
|
||||
"label_select_channel": "აირჩიეთ არხი",
|
||||
"label_new_password": "ახალი პაროლი",
|
||||
"label_repeat_password": "პაროლის გამეორება",
|
||||
"label_platform": "პლატფორმა",
|
||||
"label_price_per_post": "ფასი ერთ პოსტზე",
|
||||
"label_integrations": "ინტეგრაციები",
|
||||
"label_code": "კოდი",
|
||||
"label_should_sync_last_post": "გავათანაბროთ თუ არა მიმდინარე ბოლო პოსტი?",
|
||||
"label_when_post": "როდის დავპოსტოთ?",
|
||||
"label_autogenerate_content": "კონტენტის ავტომატური გენერირება",
|
||||
"label_generate_picture": "სურათის გენერირება?",
|
||||
"label_company": "კომპანია",
|
||||
"label_tag_color": "ტეგის ფერი",
|
||||
"label_select_board": "ბორდის არჩევა",
|
||||
"label_select_organization": "ორგანიზაციის არჩევა",
|
||||
"label_auto_add_signature": "ავტომატურად დავამატოთ ხელმოწერა?",
|
||||
"enable_color_picker": "ფერების ამრჩევის ჩართვა",
|
||||
"cancel_the_color_picker": "ფერის ამრჩევის გაუქმება",
|
||||
"no_content_yet": "კონტენტი ჯერჯერობით არ არის",
|
||||
"write_your_reply": "დაწერეთ თქვენი პოსტი...",
|
||||
"add_a_tag": "ტეგის დამატება",
|
||||
"add_to_calendar": "კალენდარში დამატება",
|
||||
"select_channels_from_circles": "აირჩიეთ არხები ზემოთ არსებულ წრეებიდან",
|
||||
"not_matching_order": "შეკვეთას არ ემთხვევა",
|
||||
"submit_for_order": "გაგზავნა შეკვეთაზე",
|
||||
"schedule": "დაგეგმვა",
|
||||
"update": "განახლება",
|
||||
"attachments": "დანართები",
|
||||
"tags": "ტეგები",
|
||||
"public_to_everyone": "ხილულია ყველასთვის",
|
||||
"mutual_follow_friends": "ურთიერთჩამომყოლები",
|
||||
"follower_of_creator": "შემქმნელის გამომწერები",
|
||||
"self_only": "მხოლოდ მე",
|
||||
"post_content_directly_to_tiktok": "კონტენტის პირდაპირ გამოქვეყნება TikTok-ში",
|
||||
"upload_content_to_tiktok_without_posting": "კონტენტის ატვირთვა TikTok-ში გამოქვეყნების გარეშე",
|
||||
"choose_upload_without_posting_description": "აირჩიეთ ატვირთვა გამოქვეყნების გარეშე, თუ გსურთ მასალის გადამოწმება/რედაქტირება TikTok-ის აპში გამოქვეყნებამდე.",
|
||||
"faq_am_i_going_to_be_charged_by_postiz": "Postiz დამაკისრებს თანხას?",
|
||||
"faq_to_confirm_credit_card_information_postiz_will_hold": "საკრედიტო ბარათის დადასტურებისთვის Postiz დროებით დაბლოკავს $2-ს და მაშინვე გაათავისუფლებს",
|
||||
"faq_can_i_trust_postiz": "შემიძლია ვენდო Postiz-ს?",
|
||||
"faq_postiz_gitroom_is_proudly_open_source": "Postiz არის ღია კოდის! გამჭვირვალე კულტურა — შეგიძლიათ ნახოთ მთელი კოდი ან გამოიყენოთ პირადი პროექტებისთვის. ღია რეპოზიტორიის სანახავად <a href=\"https://github.com/gitroomhq/postiz-app\" target=\"_blank\" style=\"text-decoration: underline;\">დააჭირეთ აქ</a>.",
|
||||
"faq_what_are_channels": "რა არის არხები?",
|
||||
"faq_postiz_gitroom_allows_you_to_schedule_posts": "Postiz გაძლევთ პოსტების დაგეგმვის საშუალებას სხვადასხვა არხებზე — მაგალითად X, Facebook, Instagram, TikTok, YouTube, Reddit, LinkedIn, Dribbble, Threads და Pinterest.",
|
||||
"faq_what_are_team_members": "რა არის გუნდის წევრები?",
|
||||
"faq_if_you_have_a_team_with_multiple_members": "თუ თქვენ ჰყავთ გუნდი რამდენიმე წევრით, შეგიძლიათ მოიწვიოთ ისინი workspace-ში, ითანამშრომლოთ პოსტებზე და დაამატონ თავიანთი არხები",
|
||||
"faq_what_is_ai_auto_complete": "რა არის AI auto-complete?",
|
||||
"faq_we_automate_chatgpt_to_help_you_write": "ვავტომატებთ ChatGPT-ს, რომ დაგეხმაროთ სოციალური პოსტებისა და სტატიათა წერაში.",
|
||||
"enter_email": "შეიტანეთ ელფოსტა",
|
||||
"are_you_sure": "დარწმუნებული ხართ?",
|
||||
"yes_delete_it": "კი, წაშალე!",
|
||||
"no_cancel": "არა, გაუქმება!",
|
||||
"are_you_sure_you_want_to_delete": "ნამდვილად გსურთ წაშალოთ {{name}}?",
|
||||
"are_you_sure_you_want_to_delete_the_image": "ნამდვილად გსურთ სურათის წაშლა?",
|
||||
"are_you_sure_you_want_to_logout": "ნამდვილად გსურთ გასვლა?",
|
||||
"yes_logout": "კი, გამოსვლა",
|
||||
"are_you_sure_you_want_to_delete_this_slot": "წავშალოთ ეს სლოტი?",
|
||||
"are_you_sure_you_want_to_delete_this_subreddit": "წავშალოთ ეს Subreddit?",
|
||||
"are_you_sure_you_want_to_close_the_window": "დავხუროთ ფანჯარა?",
|
||||
"yes_close": "კი, დახურე",
|
||||
"link_copied_to_clipboard": "ბმული დაკოპირდა",
|
||||
"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost": "დავხუროთ ეს ფანჯარა? (ყველა მონაცემი დაიკარგება)",
|
||||
"yes_close_it": "კი, დახურე!",
|
||||
"uploading_pictures": "სურათების ატვირთვა...",
|
||||
"agent_starting": "აგენტის გაშვება",
|
||||
"researching_your_content": "თქვენი კონტენტის კვლევა...",
|
||||
"understanding_the_category": "კატეგორიის გააზრება...",
|
||||
"finding_the_topic": "თემის ძიება...",
|
||||
"finding_popular_posts_to_match_with": "პოპულარული პოსტების მოპოვება შესატყვისად...",
|
||||
"generating_hook": "ჰუკის გენერირება...",
|
||||
"generating_content": "კონტენტის გენერირება...",
|
||||
"generating_pictures": "სურათების გენერირება...",
|
||||
"finding_time_to_post": "საუკეთესო დროის პოვნა...",
|
||||
"write_anything": "დაწერეთ რაც გსურთ",
|
||||
"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you": "დაწერეთ რაც გინდათ და დაამატეთ ბმულები — კვლევას ჩვენ მოვახდენთ",
|
||||
"output_format": "გამოტანის ფორმატი",
|
||||
"add_pictures": "სურათების დამატება?",
|
||||
"7_days": "7 დღე",
|
||||
"30_days": "30 დღე",
|
||||
"90_days": "90 დღე",
|
||||
"start_7_days_free_trial": "დაიწყე 7-დღიანი უფასო პერიოდი",
|
||||
"change_language": "ენის შეცვლა",
|
||||
"that_a_wrap": "დასრულებულია!\n\nთუ მოგეწონა ეს თრედი:\n\n1. გამომყევი @{{username}}\n2. გააზიარეთ ქვემოთ არსებული პოსტი",
|
||||
"post_as_images_carousel": "გამოქვეყნება სურათების კარუსელად",
|
||||
"save_set": "სეტის შენახვა",
|
||||
"separate_post": "თრედის დაყოფა რამდენიმე პოსტად",
|
||||
"label_who_can_reply_to_this_post": "ვის შეუძლია პასუხის გაცემა ამ პოსტზე?",
|
||||
"delete_integration": "ინტეგრაციის წაშლა",
|
||||
"start_writing_your_post": "დაიწყეთ თქვენი პოსტის წერა წინასწარი ხედვისთვის"
|
||||
}
|
||||
|
|
@ -73,9 +73,9 @@
|
|||
"@neynar/react": "^0.9.7",
|
||||
"@postiz/wallets": "^0.0.1",
|
||||
"@prisma/client": "^6.5.0",
|
||||
"@sentry/nestjs": "^9.43.0",
|
||||
"@sentry/nextjs": "^9.43.0",
|
||||
"@sentry/profiling-node": "^9.43.0",
|
||||
"@sentry/nestjs": "^10.12.0",
|
||||
"@sentry/nextjs": "^10.12.0",
|
||||
"@sentry/profiling-node": "^10.12.0",
|
||||
"@solana/wallet-adapter-react": "^0.15.35",
|
||||
"@solana/wallet-adapter-react-ui": "^0.9.35",
|
||||
"@swc/helpers": "0.5.13",
|
||||
|
|
@ -186,6 +186,7 @@
|
|||
"react-dom": "18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-hook-form": "^7.58.1",
|
||||
"react-hotkeys-hook": "^5.1.0",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-loading": "^2.0.3",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
|
|
|
|||
10849
pnpm-lock.yaml
10849
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue