diff --git a/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx b/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx
new file mode 100644
index 00000000..42739668
--- /dev/null
+++ b/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx
@@ -0,0 +1,160 @@
+import { FC } from 'react';
+import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
+import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';
+import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting';
+import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';
+import {
+ afterLinkedinCompanyPreventRemove,
+ linkedinCompanyPreventRemove,
+} from '@gitroom/helpers/utils/linkedin.company.prevent.remove';
+import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
+import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
+import { Input } from '@gitroom/react/form/input';
+import { DribbbleTeams } from '@gitroom/frontend/components/launches/providers/dribbble/dribbble.teams';
+import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';
+
+const DribbbleSettings: FC = () => {
+ const { register, control } = useSettings();
+ return (
+
+
+
+
+ );
+};
+const DribbblePreview: FC = (props) => {
+ const { value: topValue, integration } = useIntegration();
+ const mediaDir = useMediaDirectory();
+ const newValues = useFormatting(topValue, {
+ removeMarkdown: true,
+ saveBreaklines: true,
+ beforeSpecialFunc: (text: string) => {
+ return linkedinCompanyPreventRemove(text);
+ },
+ specialFunc: (text: string) => {
+ return afterLinkedinCompanyPreventRemove(text.slice(0, 280));
+ },
+ });
+
+ const [firstPost, ...morePosts] = newValues;
+ if (!firstPost) {
+ return null;
+ }
+
+ return (
+
+
+
+

+
+
+
{integration?.name}
+
+ CEO @ Gitroom
+
+
1m
+
+
+
+
+
+ {!!firstPost?.images?.length && (
+
+ {firstPost.images.map((image, index) => (
+
+
+
+ ))}
+
+ )}
+
+ {morePosts.map((p, index) => (
+
+
+

+
+
+
{integration?.name}
+
+ CEO @ Gitroom
+
+
+ {p.text}
+
+
+ {!!p?.images?.length && (
+
+ {p.images.map((image, index) => (
+
+
+
+
+
+ ))}
+
+ )}
+
+
+ ))}
+
+ );
+};
+
+export default withProvider(
+ DribbbleSettings,
+ DribbblePreview,
+ DribbbleDto,
+ async ([firstItem, ...otherItems]) => {
+ const isMp4 = firstItem?.find((item) => item.path.indexOf('mp4') > -1);
+
+ if (firstItem.length !== 1) {
+ return 'Dribbble requires one item';
+ }
+
+ if (isMp4) {
+ return 'Dribbble does not support mp4 files';
+ }
+
+ const details = await new Promise<{width: number, height: number}>((resolve, reject) => {
+ const url = new Image();
+ url.onload = function() {
+ // @ts-ignore
+ resolve({width: this.width, height: this.height});
+ }
+ url.src = firstItem[0].path;
+ });
+
+
+ if (
+ (details?.width === 400 && details?.height === 300) ||
+ (details?.width === 800 && details?.height === 600)
+ ) {
+ return true;
+ }
+
+ return 'Invalid image size. Dribbble requires 400x300 or 800x600 px images.';
+ }
+);
diff --git a/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx b/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx
new file mode 100644
index 00000000..22d79be2
--- /dev/null
+++ b/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx
@@ -0,0 +1,47 @@
+import { FC, useEffect, useState } from 'react';
+import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
+import { Select } from '@gitroom/react/form/select';
+import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
+
+export const DribbbleTeams: FC<{
+ name: string;
+ onChange: (event: { target: { value: string; name: string } }) => void;
+}> = (props) => {
+ const { onChange, name } = props;
+ const customFunc = useCustomProviderFunction();
+ const [orgs, setOrgs] = useState();
+ const { getValues } = useSettings();
+ const [currentMedia, setCurrentMedia] = useState();
+
+ const onChangeInner = (event: { target: { value: string, name: string } }) => {
+ setCurrentMedia(event.target.value);
+ onChange(event);
+ };
+
+ useEffect(() => {
+ customFunc.get('teams').then((data) => setOrgs(data));
+ const settings = getValues()[props.name];
+ if (settings) {
+ setCurrentMedia(settings);
+ }
+ }, []);
+
+ if (!orgs) {
+ return null;
+ }
+
+ if (!orgs.length) {
+ return <>>;
+ }
+
+ return (
+
+ );
+};
diff --git a/apps/frontend/src/components/launches/providers/show.all.providers.tsx b/apps/frontend/src/components/launches/providers/show.all.providers.tsx
index 2ee9aa87..cfb7997e 100644
--- a/apps/frontend/src/components/launches/providers/show.all.providers.tsx
+++ b/apps/frontend/src/components/launches/providers/show.all.providers.tsx
@@ -11,6 +11,7 @@ import InstagramProvider from '@gitroom/frontend/components/launches/providers/i
import YoutubeProvider from '@gitroom/frontend/components/launches/providers/youtube/youtube.provider';
import TiktokProvider from '@gitroom/frontend/components/launches/providers/tiktok/tiktok.provider';
import PinterestProvider from '@gitroom/frontend/components/launches/providers/pinterest/pinterest.provider';
+import DribbbleProvider from '@gitroom/frontend/components/launches/providers/dribbble/dribbble.provider';
export const Providers = [
{identifier: 'devto', component: DevtoProvider},
@@ -25,6 +26,7 @@ export const Providers = [
{identifier: 'youtube', component: YoutubeProvider},
{identifier: 'tiktok', component: TiktokProvider},
{identifier: 'pinterest', component: PinterestProvider},
+ {identifier: 'dribbble', component: DribbbleProvider},
];
diff --git a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts
index c314c473..563b4c65 100644
--- a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts
+++ b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts
@@ -10,6 +10,7 @@ import {HashnodeSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/provider
import {RedditSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto";
import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';
import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';
+import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';
export class EmptySettings {}
export class Integration {
@@ -64,6 +65,7 @@ export class Post {
{ value: RedditSettingsDto, name: 'reddit' },
{ value: YoutubeSettingsDto, name: 'youtube' },
{ value: PinterestSettingsDto, name: 'pinterest' },
+ { value: DribbbleDto, name: 'dribbble' },
],
},
})
diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dribbble.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dribbble.dto.ts
new file mode 100644
index 00000000..882429a4
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dribbble.dto.ts
@@ -0,0 +1,12 @@
+import { IsDefined, IsOptional, IsString, IsUrl } from 'class-validator';
+
+export class DribbbleDto {
+ @IsString()
+ @IsDefined()
+ title: string;
+
+ @IsString()
+ @IsOptional()
+ @IsUrl()
+ team: string;
+}
diff --git a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts
index 0d76c8b2..f95fafeb 100644
--- a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts
+++ b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts
@@ -12,6 +12,8 @@ import FormData from 'form-data';
import { timer } from '@gitroom/helpers/utils/timer';
import dayjs from 'dayjs';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
+import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';
+import mime from 'mime-types';
export class DribbbleProvider extends SocialAbstract implements SocialProvider {
identifier = 'dribbble';
@@ -125,151 +127,51 @@ export class DribbbleProvider extends SocialAbstract implements SocialProvider {
};
}
- async boards(accessToken: string) {
- const { items } = await (
- await this.fetch('https://api-sandbox.pinterest.com/v5/boards', {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- })
- ).json();
-
- return (
- items?.map((item: any) => ({
- name: item.name,
- id: item.id,
- })) || []
- );
- }
-
async post(
id: string,
accessToken: string,
- postDetails: PostDetails[]
+ postDetails: PostDetails[]
): Promise {
- let mediaId = '';
- const findMp4 = postDetails?.[0]?.media?.find(
- (p) => (p.path?.indexOf('mp4') || -1) > -1
- );
- const picture = postDetails?.[0]?.media?.find(
- (p) => (p.path?.indexOf('mp4') || -1) === -1
- );
-
- if (findMp4) {
- const { upload_url, media_id, upload_parameters } = await (
- await this.fetch('https://api-sandbox.pinterest.com/v5/media', {
- method: 'POST',
- body: JSON.stringify({
- media_type: 'video',
- }),
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${accessToken}`,
- },
- })
- ).json();
-
- const { data, status } = await axios.get(
- postDetails?.[0]?.media?.[0]?.url!,
- {
- responseType: 'stream',
- }
- );
-
- const formData = Object.keys(upload_parameters)
- .filter((f) => f)
- .reduce((acc, key) => {
- acc.append(key, upload_parameters[key]);
- return acc;
- }, new FormData());
-
- formData.append('file', data);
- await axios.post(upload_url, formData);
-
- let statusCode = '';
- while (statusCode !== 'succeeded') {
- console.log('trying');
- const mediafile = await (
- await this.fetch(
- 'https://api-sandbox.pinterest.com/v5/media/' + media_id,
- {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- }
- )
- ).json();
-
- await timer(3000);
- statusCode = mediafile.status;
+ const { data, status } = await axios.get(
+ postDetails?.[0]?.media?.[0]?.url!,
+ {
+ responseType: 'stream',
}
+ );
- mediaId = media_id;
- }
+ const slash = postDetails?.[0]?.media?.[0]?.url.split('/').at(-1);
- const mapImages = postDetails?.[0]?.media?.map((m) => ({
- url: m.url,
- }));
+ const formData = new FormData();
+ formData.append('image', data, {
+ filename: slash,
+ contentType: mime.lookup(slash!) || '',
+ });
- try {
- const {
- id: pId,
- link,
- ...all
- } = await (
- await this.fetch('https://api-sandbox.pinterest.com/v5/pins', {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${accessToken}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- ...(postDetails?.[0]?.settings.link
- ? { link: postDetails?.[0]?.settings.link }
- : {}),
- ...(postDetails?.[0]?.settings.title
- ? { title: postDetails?.[0]?.settings.title }
- : {}),
- ...(postDetails?.[0]?.settings.description
- ? { title: postDetails?.[0]?.settings.description }
- : {}),
- ...(postDetails?.[0]?.settings.dominant_color
- ? { title: postDetails?.[0]?.settings.dominant_color }
- : {}),
- board_id: postDetails?.[0]?.settings.board,
- media_source: mediaId
- ? {
- source_type: 'video_id',
- media_id: mediaId,
- cover_image_url: picture?.url,
- }
- : mapImages?.length === 1
- ? {
- source_type: 'image_url',
- url: mapImages?.[0]?.url,
- }
- : {
- source_type: 'multiple_image_urls',
- items: mapImages,
- },
- }),
- })
- ).json();
+ formData.append('title', postDetails[0].settings.title);
+ formData.append('description', postDetails[0].message);
- return [
- {
- id: postDetails?.[0]?.id,
- postId: pId,
- releaseURL: `https://www.pinterest.com/pin/${pId}`,
- status: 'success',
+ const data2 = await axios.post(
+ 'https://api.dribbble.com/v2/shots',
+ formData,
+ {
+ headers: {
+ ...formData.getHeaders(),
+ Authorization: `Bearer ${accessToken}`,
},
- ];
- } catch (err) {
- console.log(err);
- return [];
- }
+ }
+ );
+
+ const location = data2.headers['location'];
+ const newId = location.split('/').at(-1);
+
+ return [
+ {
+ id: postDetails?.[0]?.id,
+ status: 'completed',
+ postId: newId,
+ releaseURL: `https://dribbble.com/shots/${newId}`,
+ },
+ ];
}
analytics(
diff --git a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts
index 9ba02834..ad9bcb2b 100644
--- a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts
+++ b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts
@@ -38,7 +38,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider {
}`
)}` +
`&state=${state}` +
- '&scope=pages_show_list,business_management,pages_manage_posts,pages_manage_engagement,pages_read_engagement',
+ '&scope=pages_show_list,business_management,pages_manage_posts,pages_manage_engagement,pages_read_engagement,read_insights',
codeVerifier: makeId(10),
state,
};