diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx index a88f1e88..c3de52f1 100644 --- a/apps/frontend/src/components/launches/add.edit.model.tsx +++ b/apps/frontend/src/components/launches/add.edit.model.tsx @@ -250,44 +250,16 @@ export const AddEditModal: FC<{ group: existingData?.group, trigger: values[v].trigger, settings: values[v].settings(), - firstCommentRequirements: values[v].firstCommentRequirements, - maximumMediaRequirements: values[v].maximumMediaRequirements, - minimumMediaRequirements: values[v].minimumMediaRequirements, + checkValidity: values[v].checkValidity })); for (const key of allKeys) { - // @ts-ignore - const images = key?.value[0].image; - if ( - (images?.length || 0) > (key.maximumMediaRequirements || 100000) || - (images?.length || 0) < (key.minimumMediaRequirements || 0) - ) { - toaster.show( - `The amount of ${capitalize(key?.integration?.identifier)} media attached supposed to be ${ - (key.maximumMediaRequirements === key.minimumMediaRequirements) || !key.maximumMediaRequirements - ? key.minimumMediaRequirements - : `between ${key.minimumMediaRequirements} to ${key.maximumMediaRequirements}` - }`, - 'warning' - ); - return; - } - - if ( - key.firstCommentRequirements && - !images?.every((p: any) => - key.firstCommentRequirements === 'video' - ? p.name.includes('mp4') - : !p.name.includes('mp4') - ) - ) { - toaster.show( - `${capitalize(key?.integration?.identifier?.toUpperCase())} media should be a ${ - key.firstCommentRequirements === 'video' ? 'video' : 'image' - }`, - 'warning' - ); - return; + if (key.checkValidity) { + const check = await key.checkValidity(key?.value.map((p: any) => p.image || {path: ''})); + if (typeof check === 'string') { + toaster.show(check, 'warning'); + return; + } } if (key.value.some((p) => !p.content || p.content.length < 6)) { @@ -322,7 +294,7 @@ export const AddEditModal: FC<{ ); modal.closeAll(); }, - [postFor, dateState, value, integrations, existingData] + [postFor, dateState, value, integrations, existingData, selectedIntegrations] ); const getPostsMarketplace = useCallback(async () => { diff --git a/apps/frontend/src/components/launches/helpers/use.values.ts b/apps/frontend/src/components/launches/helpers/use.values.ts index 0334285f..f3e18309 100644 --- a/apps/frontend/src/components/launches/helpers/use.values.ts +++ b/apps/frontend/src/components/launches/helpers/use.values.ts @@ -8,9 +8,7 @@ const finalInformation = {} as { settings: () => object; trigger: () => Promise; isValid: boolean; - firstCommentRequirements?: 'video' | 'image'; - minimumMediaRequirements?: number; - maximumMediaRequirements?: number; + checkValidity?: (value: Array>) => Promise; }; }; export const useValues = ( @@ -19,9 +17,7 @@ export const useValues = ( identifier: string, value: Array<{ id?: string; content: string; media?: Array }>, dto: any, - firstCommentRequirements?: 'video' | 'image', - minimumMediaRequirements?: number, - maximumMediaRequirements?: number + checkValidity?: (value: Array>) => Promise, ) => { const resolver = useMemo(() => { return classValidatorResolver(dto); @@ -44,17 +40,9 @@ export const useValues = ( finalInformation[integration].settings = getValues; finalInformation[integration].trigger = form.trigger; - if (firstCommentRequirements) { - finalInformation[integration].firstCommentRequirements = - firstCommentRequirements; - } - if (minimumMediaRequirements) { - finalInformation[integration].minimumMediaRequirements = - minimumMediaRequirements; - } - if (maximumMediaRequirements) { - finalInformation[integration].maximumMediaRequirements = - maximumMediaRequirements; + if (checkValidity) { + finalInformation[integration].checkValidity = + checkValidity; } useEffect(() => { diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx index 0ad00ab1..b9b6520e 100644 --- a/apps/frontend/src/components/launches/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/launches/providers/high.order.provider.tsx @@ -67,9 +67,7 @@ export const withProvider = ( SettingsComponent: FC | null, PreviewComponent: FC, dto?: any, - firstCommentRequirements?: 'video' | 'image', - minimumMediaRequirements?: number, - maximumMediaRequirements?: number, + checkValidity?: (value: Array>) => Promise ) => { return (props: { identifier: string; @@ -121,9 +119,7 @@ export const withProvider = ( props.identifier, editInPlace ? InPlaceValue : props.value, dto, - firstCommentRequirements, - minimumMediaRequirements, - maximumMediaRequirements + checkValidity ); // change editor value diff --git a/apps/frontend/src/components/launches/providers/instagram/instagram.provider.tsx b/apps/frontend/src/components/launches/providers/instagram/instagram.provider.tsx index 42f14098..f6d8351d 100644 --- a/apps/frontend/src/components/launches/providers/instagram/instagram.provider.tsx +++ b/apps/frontend/src/components/launches/providers/instagram/instagram.provider.tsx @@ -109,4 +109,10 @@ const InstagramPreview: FC = (props) => { ); }; -export default withProvider(null, InstagramPreview, undefined, undefined, 1, 10); +export default withProvider(null, InstagramPreview, undefined, async ([firstPost, ...otherPosts]) => { + if (!firstPost.length) { + return 'Instagram should have at least one media'; + } + + return true; +}); diff --git a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx b/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx index b883e18c..7d94d5b2 100644 --- a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx +++ b/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx @@ -130,6 +130,47 @@ export default withProvider( PinterestSettings, PinterestPreview, PinterestSettingsDto, - undefined, - 1 + async ([firstItem, ...otherItems]) => { + const isMp4 = firstItem.find((item) => item.path.indexOf('mp4') > -1); + const isPicture = firstItem.find((item) => item.path.indexOf('mp4') === -1); + + if (firstItem.length === 0) { + return 'Pinterest requires at least one media'; + } + + if (isMp4 && firstItem.length !== 2 && !isPicture) { + return 'If posting a video to Pinterest you have to also include a cover image as second media'; + } + + if (isMp4 && firstItem.length > 2) { + return 'If posting a video to Pinterest you can only have two media items'; + } + + if (otherItems.length) { + return 'Pinterest can only have one post'; + } + + if (firstItem.length > 1 && firstItem.every(p => p.path.indexOf('mp4') == -1)) { + const loadAll: Array<{width: number, height: number}> = await Promise.all(firstItem.map(p => { + return new Promise((resolve, reject) => { + const url = new Image(); + url.onload = function() { + // @ts-ignore + resolve({width: this.width, height: this.height}); + } + url.src = p.path; + }); + })) as any; + + const checkAllTheSameWidthHeight = loadAll.every((p, i, arr) => { + return p.width === arr[0].width && p.height === arr[0].height; + }); + + if (!checkAllTheSameWidthHeight) { + return 'Pinterest requires all images to have the same width and height'; + } + } + + return true; + } ); diff --git a/apps/frontend/src/components/media/media.component.tsx b/apps/frontend/src/components/media/media.component.tsx index b2507d33..e2698b0e 100644 --- a/apps/frontend/src/components/media/media.component.tsx +++ b/apps/frontend/src/components/media/media.component.tsx @@ -12,6 +12,7 @@ import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.titl import clsx from 'clsx'; import interClass from '@gitroom/react/helpers/inter.font'; import { VideoFrame } from '@gitroom/react/helpers/video.frame'; +import { useToaster } from '@gitroom/react/toaster/toaster'; const showModalEmitter = new EventEmitter(); export const ShowMediaBoxModal: FC = () => { @@ -58,6 +59,7 @@ export const MediaBox: FC<{ const [mediaList, setListMedia] = useState([]); const fetch = useFetch(); const mediaDirectory = useMediaDirectory(); + const toaster = useToaster(); const loadMedia = useCallback(async () => { return (await fetch('/media')).json(); @@ -69,8 +71,10 @@ export const MediaBox: FC<{ if ( !file?.target?.files?.length || file?.target?.files?.[0]?.size > maxFileSize - ) + ) { + toaster.show('Maximum file size 10mb', 'warning'); return; + } const formData = new FormData(); formData.append('file', file?.target?.files?.[0]); const data = await ( @@ -226,7 +230,7 @@ export const MediaBox: FC<{ .map((media) => (
{media.path.indexOf('mp4') > -1 ? ( @@ -319,7 +323,7 @@ export const MultiMediaComponent: FC<{ {!!currentMedia && currentMedia.map((media, index) => ( <> -
+
window.open(mediaDirectory.set(media.path))} > diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/pinterest.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/pinterest.dto.ts index 7f3ff938..2fe75778 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/pinterest.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/pinterest.dto.ts @@ -1,5 +1,4 @@ -import { IsDefined, IsOptional, IsString, IsUrl, MinLength, ValidateNested } from 'class-validator'; -import { Type } from 'class-transformer'; +import { IsDefined, IsOptional, IsString, IsUrl, MinLength } from 'class-validator'; export class PinterestSettingsDto { @IsString() diff --git a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts index 37d6a6db..8785dc08 100644 --- a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts @@ -34,7 +34,7 @@ export class FacebookProvider implements SocialProvider { `${process.env.FRONTEND_URL}/integrations/social/facebook${refresh ? `?refresh=${refresh}` : ''}` )}` + `&state=${state}` + - '&scope=pages_show_list,business_management,pages_manage_posts,publish_video,pages_manage_engagement,pages_read_engagement', + '&scope=pages_show_list,business_management,pages_manage_posts,pages_manage_engagement,pages_read_engagement', codeVerifier: makeId(10), state, }; diff --git a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts index 3708702f..36ad8673 100644 --- a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts @@ -5,12 +5,10 @@ import { SocialProvider, } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; -import { timer } from '@gitroom/helpers/utils/timer'; -import dayjs from 'dayjs'; import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto'; import axios from 'axios'; import FormData from 'form-data'; -const form = new FormData(); +import { timer } from '@gitroom/helpers/utils/timer'; export class PinterestProvider implements SocialProvider { identifier = 'pinterest'; @@ -126,29 +124,40 @@ export class PinterestProvider implements SocialProvider { }) ).json(); - console.log(media_id, upload_url); - - try { - const { data } = await axios({ - url: postDetails?.[0]?.media?.[0]?.url, - method: 'GET', + const { data, status } = await axios.get( + postDetails?.[0]?.media?.[0]?.url!, + { responseType: 'stream', - }); + } + ); - const p = await ( - await fetch(upload_url, { - method: 'PUT', - body: data.buffer, - headers: { - Authorization: `Bearer ${accessToken}`, - ...upload_parameters, - }, - }) + 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 fetch( + 'https://api-sandbox.pinterest.com/v5/media/' + media_id, + { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ) ).json(); - console.log(p); - } catch (err) { - console.log(err); + await timer(3000); + statusCode = mediafile.status; } mediaId = media_id; @@ -158,8 +167,6 @@ export class PinterestProvider implements SocialProvider { url: m.url, })); - console.log('1'); - try { const { id: pId, @@ -188,7 +195,7 @@ export class PinterestProvider implements SocialProvider { board_id: postDetails?.[0]?.settings.board, media_source: mediaId ? { - source_type: 'video', + source_type: 'video_id', media_id: mediaId, } : mapImages?.length === 1 @@ -204,8 +211,6 @@ export class PinterestProvider implements SocialProvider { }) ).json(); - console.log(all); - return [ { id,