Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/frontend/src/components/launches/add.edit.model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ export const AddEditModal: FC<{
for (const key of allKeys) {
if (key.checkValidity) {
const check = await key.checkValidity(
key?.value.map((p: any) => p.image || [])
key?.value.map((p: any) => p.image || []),
key.settings
);
if (typeof check === 'string') {
toaster.show(check, 'warning');
Expand Down
15 changes: 10 additions & 5 deletions apps/frontend/src/components/launches/helpers/use.values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ const finalInformation = {} as {
settings: () => object;
trigger: () => Promise<boolean>;
isValid: boolean;
checkValidity?: (value: Array<Array<{path: string}>>) => Promise<string|true>;
checkValidity?: (
value: Array<Array<{ path: string }>>,
settings: any
) => Promise<string | true>;
Comment on lines +11 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a generic type parameter instead of any for better type safety.

The settings parameter using any type could lead to runtime errors. Consider using a generic type parameter to maintain type safety across different integrations.

- checkValidity?: (
-   value: Array<Array<{ path: string }>>,
-   settings: any
- ) => Promise<string | true>;
+ checkValidity?: <T>(
+   value: Array<Array<{ path: string }>>,
+   settings: T
+ ) => Promise<string | true>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checkValidity?: (
value: Array<Array<{ path: string }>>,
settings: any
) => Promise<string | true>;
checkValidity?: <T>(
value: Array<Array<{ path: string }>>,
settings: T
) => Promise<string | true>;

maximumCharacters?: number;
};
};
Expand All @@ -18,8 +21,11 @@ export const useValues = (
identifier: string,
value: Array<{ id?: string; content: string; media?: Array<string> }>,
dto: any,
checkValidity?: (value: Array<Array<{path: string}>>) => Promise<string|true>,
maximumCharacters?: number,
checkValidity?: (
value: Array<Array<{ path: string }>>,
settings: any
) => Promise<string | true>,
Comment on lines +24 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apply the same generic type parameter here for consistency.

For consistency with the type definition above, apply the same generic type parameter here.

- checkValidity?: (
-   value: Array<Array<{ path: string }>>,
-   settings: any
- ) => Promise<string | true>,
+ checkValidity?: <T>(
+   value: Array<Array<{ path: string }>>,
+   settings: T
+ ) => Promise<string | true>,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checkValidity?: (
value: Array<Array<{ path: string }>>,
settings: any
) => Promise<string | true>,
checkValidity?: <T>(
value: Array<Array<{ path: string }>>,
settings: T
) => Promise<string | true>,

maximumCharacters?: number
) => {
const resolver = useMemo(() => {
return classValidatorResolver(dto);
Expand All @@ -43,8 +49,7 @@ export const useValues = (
finalInformation[integration].trigger = form.trigger;

if (checkValidity) {
finalInformation[integration].checkValidity =
checkValidity;
finalInformation[integration].checkValidity = checkValidity;
}

if (maximumCharacters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,16 @@ export const EditorWrapper: FC<{ children: ReactNode }> = ({ children }) => {
return children;
};

export const withProvider = (
export const withProvider = function <T extends object>(
SettingsComponent: FC<{values?: any}> | null,
CustomPreviewComponent?: FC<{maximumCharacters?: number}>,
dto?: any,
checkValidity?: (
value: Array<Array<{ path: string }>>
value: Array<Array<{ path: string }>>,
settings: T
) => Promise<string | true>,
maximumCharacters?: number
) => {
) {
return (props: {
identifier: string;
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
import { FC } from 'react';
import { Select } from '@gitroom/react/form/select';
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';
import { InstagramCollaboratorsTags } from '@gitroom/frontend/components/launches/providers/instagram/instagram.tags';

const postType = [
{
value: 'post',
label: 'Post / Reel',
},
{
value: 'story',
label: 'Story',
},
];
const InstagramCollaborators: FC<{ values?: any }> = (props) => {
const { watch, register, formState, control } = useSettings();

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'formState' is assigned a value but never used.

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'control' is assigned a value but never used.
const postCurrentType = watch('post_type');
return (
<>
<Select
label="Post Type"
{...register('post_type', {
value: 'post',
})}
>
<option value="">Select Post Type...</option>
{postType.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</Select>

{postCurrentType !== 'story' && (
<InstagramCollaboratorsTags
label="Collaborators (max 3) - accounts can't be private"
{...register('collaborators')}
/>
)}
</>
);
};

export default withProvider<InstagramDto>(
InstagramCollaborators,
undefined,
InstagramDto,
async ([firstPost, ...otherPosts], settings) => {
if (!firstPost.length) {
return 'Instagram should have at least one media';
}

if (firstPost.length > 1 && settings.post_type === 'story') {
return 'Instagram stories can only have one media';
}

const checkVideosLength = await Promise.all(
firstPost
.filter((f) => f.path.indexOf('mp4') > -1)
.flatMap((p) => p.path)
.map((p) => {
Comment on lines +62 to +64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the usage of flatMap and improve video file detection

The use of flatMap is unnecessary here since p.path is a string. It should be replaced with map. Additionally, detecting video files by checking if 'mp4' is in the path string is case-sensitive and may not cover all video formats. Consider using a regular expression to match video file extensions case-insensitively.

Apply this diff to correct the code:

-        .filter((f) => f.path.indexOf('mp4') > -1)
+        .filter((f) => /\.(mp4|mov|avi)$/i.test(f.path))
-        .flatMap((p) => p.path)
+        .map((p) => p.path)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.filter((f) => f.path.indexOf('mp4') > -1)
.flatMap((p) => p.path)
.map((p) => {
.filter((f) => /\.(mp4|mov|avi)$/i.test(f.path))
.map((p) => p.path)
.map((p) => {

return new Promise<number>((res) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.src = p;
video.addEventListener('loadedmetadata', () => {
res(video.duration);
});
});
})
);

for (const video of checkVideosLength) {
if (video > 60 && settings.post_type === 'story') {
return 'Instagram stories should be maximum 60 seconds';
}

if (video > 90 && settings.post_type === 'post') {
return 'Instagram reel should be maximum 90 seconds';
}
}

return true;
},
2200
);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
import { ReactTags } from 'react-tag-autocomplete';
import interClass from '@gitroom/react/helpers/inter.font';

export const InstagramCollaboratorsTags: FC<{
name: string;
label: string;
onChange: (event: { target: { value: any[]; name: string } }) => void;
}> = (props) => {
const { onChange, name, label } = props;
const { getValues } = useSettings();
const [tagValue, setTagValue] = useState<any[]>([]);
const [suggestions, setSuggestions] = useState<string>('');

const onDelete = useCallback(
(tagIndex: number) => {
const modify = tagValue.filter((_, i) => i !== tagIndex);
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue]

Check warning

Code scanning / ESLint

verifies the list of dependencies for Hooks like useEffect and similar Warning

React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.
);
Comment on lines +16 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include missing dependencies in useCallback for onDelete

The onDelete function uses name and onChange, but they are not included in the dependency array of the useCallback hook. This omission may lead to stale references if name or onChange change. Include them in the dependency array to ensure consistency.

Apply this diff:

-    [tagValue]
+    [tagValue, name, onChange]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onDelete = useCallback(
(tagIndex: number) => {
const modify = tagValue.filter((_, i) => i !== tagIndex);
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue]
);
const onDelete = useCallback(
(tagIndex: number) => {
const modify = tagValue.filter((_, i) => i !== tagIndex);
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue, name, onChange]
);
🧰 Tools
🪛 GitHub Check: ESLint

[warning] 22-22: verifies the list of dependencies for Hooks like useEffect and similar
React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.


const onAddition = useCallback(
(newTag: any) => {
if (tagValue.length >= 3) {
return;
}
const modify = [...tagValue, newTag];
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue]

Check warning

Code scanning / ESLint

verifies the list of dependencies for Hooks like useEffect and similar Warning

React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.
);
Comment on lines +25 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include missing dependencies in useCallback for onAddition

The onAddition function uses name and onChange, but they are not included in the dependency array of the useCallback hook. This could cause issues if name or onChange update. Add them to the dependency array to maintain proper references.

Apply this diff:

-    [tagValue]
+    [tagValue, name, onChange]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onAddition = useCallback(
(newTag: any) => {
if (tagValue.length >= 3) {
return;
}
const modify = [...tagValue, newTag];
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue]
);
const onAddition = useCallback(
(newTag: any) => {
if (tagValue.length >= 3) {
return;
}
const modify = [...tagValue, newTag];
setTagValue(modify);
onChange({ target: { value: modify, name } });
},
[tagValue, name, onChange]
);
🧰 Tools
🪛 GitHub Check: ESLint

[warning] 34-34: verifies the list of dependencies for Hooks like useEffect and similar
React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.


useEffect(() => {
const settings = getValues()[props.name];
if (settings) {
setTagValue(settings);
}
}, []);

Check warning

Code scanning / ESLint

verifies the list of dependencies for Hooks like useEffect and similar Warning

React Hook useEffect has missing dependencies: 'getValues' and 'props.name'. Either include them or remove the dependency array.
Comment on lines +37 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include missing dependencies in useEffect

The useEffect hook uses getValues and name, but these are not included in the dependency array. To ensure the effect runs correctly when these values change, include them in the dependency array.

Apply this diff:

-  }, []);
+  }, [getValues, name]);

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: ESLint

[warning] 42-42: verifies the list of dependencies for Hooks like useEffect and similar
React Hook useEffect has missing dependencies: 'getValues' and 'props.name'. Either include them or remove the dependency array.


const suggestionsArray = useMemo(() => {
return [...tagValue, { label: suggestions, value: suggestions }].filter(f => f.label);
}, [suggestions, tagValue]);

return (
<div>
<div className={`${interClass} text-[14px] mb-[6px]`}>{label}</div>
<ReactTags
placeholderText="Add a tag"
suggestions={suggestionsArray}
selected={tagValue}
onAdd={onAddition}
onInput={setSuggestions}
onDelete={onDelete}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import RedditProvider from "@gitroom/frontend/components/launches/providers/redd
import MediumProvider from "@gitroom/frontend/components/launches/providers/medium/medium.provider";
import HashnodeProvider from "@gitroom/frontend/components/launches/providers/hashnode/hashnode.provider";
import FacebookProvider from '@gitroom/frontend/components/launches/providers/facebook/facebook.provider';
import InstagramProvider from '@gitroom/frontend/components/launches/providers/instagram/instagram.provider';
import InstagramProvider from '@gitroom/frontend/components/launches/providers/instagram/instagram.collaborators';
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';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Type } from 'class-transformer';
import { IsArray, IsDefined, IsIn, IsString, ValidateNested } from 'class-validator';

export class Collaborators {
@IsDefined()
@IsString()
label: string;
}
export class InstagramDto {
@IsIn(['post', 'story'])
@IsDefined()
post_type: 'post' | 'story';

@Type(() => Collaborators)
@ValidateNested({ each: true })
@IsArray()
collaborators: Collaborators[];
}
11 changes: 9 additions & 2 deletions libraries/nestjs-libraries/src/integrations/social.abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export class NotEnoughScopes {
}

export abstract class SocialAbstract {
async fetch(url: string, options: RequestInit = {}, identifier = ''): Promise<Response> {
async fetch(
url: string,
options: RequestInit = {},
identifier = ''
): Promise<Response> {
const request = await fetch(url, options);

if (request.status === 200 || request.status === 201) {
Expand All @@ -40,7 +44,10 @@ export abstract class SocialAbstract {
return this.fetch(url, options, identifier);
}

if (request.status === 401 || json.includes('OAuthException')) {
if (
request.status === 401 ||
(json.includes('OAuthException') && !json.includes("Unsupported format") && !json.includes('2207018'))
) {
throw new RefreshToken(identifier, json, options.body!);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { timer } from '@gitroom/helpers/utils/timer';
import dayjs from 'dayjs';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';

export class InstagramProvider
extends SocialAbstract
Expand Down Expand Up @@ -203,10 +204,11 @@ export class InstagramProvider
async post(
id: string,
accessToken: string,
postDetails: PostDetails[]
postDetails: PostDetails<InstagramDto>[]
): Promise<PostResponse[]> {
const [firstPost, ...theRest] = postDetails;

console.log('in progress');
const isStory = firstPost.settings.post_type === 'story';
const medias = await Promise.all(
firstPost?.media?.map(async (m) => {
const caption =
Expand All @@ -218,18 +220,34 @@ export class InstagramProvider
const mediaType =
m.url.indexOf('.mp4') > -1
? firstPost?.media?.length === 1
? `video_url=${m.url}&media_type=REELS`
? isStory
? `video_url=${m.url}&media_type=STORIES`
: `video_url=${m.url}&media_type=REELS`
: isStory
? `video_url=${m.url}&media_type=STORIES`
: `video_url=${m.url}&media_type=VIDEO`
: isStory
? `image_url=${m.url}&media_type=STORIES`
: `image_url=${m.url}`;
Comment on lines +223 to 231
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor complex media type logic for better readability.

The nested ternary operations make the code hard to read and maintain. Consider extracting this logic into a separate function.

+ private getMediaType(url: string, isStory: boolean, isMultiMedia: boolean): string {
+   const isVideo = url.indexOf('.mp4') > -1;
+   if (!isVideo) {
+     return isStory ? `image_url=${url}&media_type=STORIES` : `image_url=${url}`;
+   }
+   if (isStory) {
+     return `video_url=${url}&media_type=STORIES`;
+   }
+   if (!isMultiMedia) {
+     return `video_url=${url}&media_type=REELS`;
+   }
+   return `video_url=${url}&media_type=VIDEO`;
+ }

Then use it in the code:

- const mediaType =
-   m.url.indexOf('.mp4') > -1
-     ? firstPost?.media?.length === 1
-       ? isStory
-         ? `video_url=${m.url}&media_type=STORIES`
-         : `video_url=${m.url}&media_type=REELS`
-       : isStory
-       ? `video_url=${m.url}&media_type=STORIES`
-       : `video_url=${m.url}&media_type=VIDEO`
-     : isStory
-     ? `image_url=${m.url}&media_type=STORIES`
-     : `image_url=${m.url}`;
+ const mediaType = this.getMediaType(m.url, isStory, firstPost?.media?.length > 1);

Committable suggestion skipped: line range outside the PR's diff.

console.log('in progress1');

const collaborators =
firstPost?.settings?.collaborators?.length && !isStory
? `&collaborators=${JSON.stringify(
firstPost?.settings?.collaborators.map((p) => p.label)
)}`
: ``;

console.log(collaborators);
const { id: photoId } = await (
await this.fetch(
`https://graph.facebook.com/v20.0/${id}/media?${mediaType}${isCarousel}&access_token=${accessToken}${caption}`,
`https://graph.facebook.com/v20.0/${id}/media?${mediaType}${isCarousel}${collaborators}&access_token=${accessToken}${caption}`,
{
method: 'POST',
}
)
).json();
console.log('in progress2');

let status = 'IN_PROGRESS';
while (status === 'IN_PROGRESS') {
Expand All @@ -241,6 +259,7 @@ export class InstagramProvider
await timer(3000);
status = status_code;
}
console.log('in progress3');

return photoId;
}) || []
Expand Down Expand Up @@ -376,4 +395,12 @@ export class InstagramProvider
})) || []
);
}

music(accessToken: string, data: { q: string }) {
return this.fetch(
`https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(
data.q
)}&access_token=${accessToken}`
);
}
Comment on lines +399 to +405
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling to the music method.

The music method should include error handling and type safety.

- music(accessToken: string, data: { q: string }) {
-   return this.fetch(
-     `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(
-       data.q
-     )}&access_token=${accessToken}`
-   );
+ async music(accessToken: string, data: { q: string }): Promise<any> {
+   try {
+     const response = await this.fetch(
+       `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(
+         data.q
+       )}&access_token=${accessToken}`
+     );
+     return response.json();
+   } catch (error) {
+     throw new Error(`Failed to search music: ${error.message}`);
+   }
}

Committable suggestion skipped: line range outside the PR's diff.

}
Loading