Skip to content

Commit abd2fba

Browse files
authored
Merge pull request #511 from gitroomhq/feat/lemmy
lemmy provider
2 parents 7bb6c46 + a2b6341 commit abd2fba

File tree

10 files changed

+545
-2
lines changed

10 files changed

+545
-2
lines changed

apps/backend/src/api/routes/integrations.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ export class IntegrationsController {
264264
const load = await integrationProvider[body.name](
265265
getIntegration.token,
266266
body.data,
267-
getIntegration.internalId
267+
getIntegration.internalId,
268+
getIntegration
268269
);
269270

270271
return load;
1.66 KB
Loading

apps/frontend/src/components/launches/providers/high.order.provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ export const withProvider = function <T extends object>(
527527
{(showTab === 0 || showTab === 2) && (
528528
<div className={clsx('mt-[20px]', showTab !== 2 && 'hidden')}>
529529
<Component values={editInPlace ? InPlaceValue : props.value} />
530-
{data?.internalPlugs?.length && (
530+
{!!data?.internalPlugs?.length && (
531531
<InternalChannels plugs={data?.internalPlugs} />
532532
)}
533533
</div>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { FC, useCallback } from 'react';
2+
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
3+
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
4+
import { useFieldArray } from 'react-hook-form';
5+
import { Button } from '@gitroom/react/form/button';
6+
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
7+
import { Subreddit } from './subreddit';
8+
import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto';
9+
10+
const LemmySettings: FC = () => {
11+
const { register, control } = useSettings();
12+
const { fields, append, remove } = useFieldArray({
13+
control, // control props comes from useForm (optional: if you are using FormContext)
14+
name: 'subreddit', // unique name for your Field Array
15+
});
16+
17+
const addField = useCallback(() => {
18+
append({});
19+
}, [fields, append]);
20+
21+
const deleteField = useCallback(
22+
(index: number) => async () => {
23+
if (
24+
!(await deleteDialog('Are you sure you want to delete this Subreddit?'))
25+
)
26+
return;
27+
remove(index);
28+
},
29+
[fields, remove]
30+
);
31+
32+
return (
33+
<>
34+
<div className="flex flex-col gap-[20px] mb-[20px]">
35+
{fields.map((field, index) => (
36+
<div key={field.id} className="flex flex-col relative">
37+
<div
38+
onClick={deleteField(index)}
39+
className="absolute -left-[10px] justify-center items-center flex -top-[10px] w-[20px] h-[20px] bg-red-600 rounded-full text-textColor"
40+
>
41+
x
42+
</div>
43+
<Subreddit {...register(`subreddit.${index}.value`)} />
44+
</div>
45+
))}
46+
</div>
47+
<Button onClick={addField}>Add Subreddit</Button>
48+
{fields.length === 0 && (
49+
<div className="text-red-500 text-[12px] mt-[10px]">
50+
Please add at least one Subreddit
51+
</div>
52+
)}
53+
</>
54+
);
55+
};
56+
57+
export default withProvider(
58+
LemmySettings,
59+
undefined,
60+
LemmySettingsDto,
61+
async (items) => {
62+
const [firstItems] = items;
63+
64+
if (
65+
firstItems.length &&
66+
firstItems[0].path.indexOf('png') === -1 &&
67+
firstItems[0].path.indexOf('jpg') === -1 &&
68+
firstItems[0].path.indexOf('jpef') === -1 &&
69+
firstItems[0].path.indexOf('gif') === -1
70+
) {
71+
return 'You can set only one picture for a cover';
72+
}
73+
74+
return true;
75+
},
76+
10000
77+
);
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { FC, FormEvent, useCallback, useState } from 'react';
2+
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
3+
import { Input } from '@gitroom/react/form/input';
4+
import { useDebouncedCallback } from 'use-debounce';
5+
import { useWatch } from 'react-hook-form';
6+
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
7+
8+
export const Subreddit: FC<{
9+
onChange: (event: {
10+
target: {
11+
name: string;
12+
value: {
13+
id: string;
14+
subreddit: string;
15+
title: string;
16+
name: string;
17+
url: string;
18+
body: string;
19+
media: any[];
20+
};
21+
};
22+
}) => void;
23+
name: string;
24+
}> = (props) => {
25+
const { onChange, name } = props;
26+
27+
const state = useSettings();
28+
const split = name.split('.');
29+
const [loading, setLoading] = useState(false);
30+
// @ts-ignore
31+
const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value;
32+
33+
const [results, setResults] = useState([]);
34+
const func = useCustomProviderFunction();
35+
const value = useWatch({ name });
36+
const [searchValue, setSearchValue] = useState('');
37+
38+
const setResult = (result: { id: string; name: string }) => async () => {
39+
setLoading(true);
40+
setSearchValue('');
41+
42+
onChange({
43+
target: {
44+
name,
45+
value: {
46+
id: String(result.id),
47+
subreddit: result.name,
48+
title: '',
49+
name: '',
50+
url: '',
51+
body: '',
52+
media: [],
53+
},
54+
},
55+
});
56+
57+
setLoading(false);
58+
};
59+
60+
const setTitle = useCallback(
61+
(e: any) => {
62+
onChange({
63+
target: {
64+
name,
65+
value: {
66+
...value,
67+
title: e.target.value,
68+
},
69+
},
70+
});
71+
},
72+
[value]
73+
);
74+
75+
const setURL = useCallback(
76+
(e: any) => {
77+
onChange({
78+
target: {
79+
name,
80+
value: {
81+
...value,
82+
url: e.target.value,
83+
},
84+
},
85+
});
86+
},
87+
[value]
88+
);
89+
90+
const search = useDebouncedCallback(
91+
useCallback(async (e: FormEvent<HTMLInputElement>) => {
92+
// @ts-ignore
93+
setResults([]);
94+
// @ts-ignore
95+
if (!e.target.value) {
96+
return;
97+
}
98+
// @ts-ignore
99+
const results = await func.get('subreddits', { word: e.target.value });
100+
// @ts-ignore
101+
setResults(results);
102+
}, []),
103+
500
104+
);
105+
106+
return (
107+
<div className="bg-primary p-[20px]">
108+
{value?.subreddit ? (
109+
<>
110+
<Input
111+
error={errors?.subreddit?.message}
112+
disableForm={true}
113+
value={value.subreddit}
114+
readOnly={true}
115+
label="Community"
116+
name="subreddit"
117+
/>
118+
<Input
119+
error={errors?.title?.message}
120+
value={value.title}
121+
disableForm={true}
122+
label="Title"
123+
name="title"
124+
onChange={setTitle}
125+
/>
126+
<Input
127+
error={errors?.url?.message}
128+
value={value.url}
129+
label="URL"
130+
name="url"
131+
disableForm={true}
132+
onChange={setURL}
133+
/>
134+
</>
135+
) : (
136+
<div className="relative">
137+
<Input
138+
placeholder="Community"
139+
name="search"
140+
label="Search Community"
141+
readOnly={loading}
142+
value={searchValue}
143+
error={errors?.message}
144+
disableForm={true}
145+
onInput={async (e) => {
146+
// @ts-ignore
147+
setSearchValue(e.target.value);
148+
await search(e);
149+
}}
150+
/>
151+
{!!results.length && !loading && (
152+
<div className="z-[400] w-full absolute bg-input -mt-[20px] outline-none border-fifth border cursor-pointer">
153+
{results.map((r: { id: string; name: string }) => (
154+
<div
155+
onClick={setResult(r)}
156+
key={r.id}
157+
className="px-[16px] py-[5px] hover:bg-secondary"
158+
>
159+
{r.name}
160+
</div>
161+
))}
162+
</div>
163+
)}
164+
</div>
165+
)}
166+
</div>
167+
);
168+
};

apps/frontend/src/components/launches/providers/show.all.providers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import DiscordProvider from '@gitroom/frontend/components/launches/providers/dis
1717
import SlackProvider from '@gitroom/frontend/components/launches/providers/slack/slack.provider';
1818
import MastodonProvider from '@gitroom/frontend/components/launches/providers/mastodon/mastodon.provider';
1919
import BlueskyProvider from '@gitroom/frontend/components/launches/providers/bluesky/bluesky.provider';
20+
import LemmyProvider from '@gitroom/frontend/components/launches/providers/lemmy/lemmy.provider';
2021

2122
export const Providers = [
2223
{identifier: 'devto', component: DevtoProvider},
@@ -37,6 +38,7 @@ export const Providers = [
3738
{identifier: 'slack', component: SlackProvider},
3839
{identifier: 'mastodon', component: MastodonProvider},
3940
{identifier: 'bluesky', component: BlueskyProvider},
41+
{identifier: 'lemmy', component: LemmyProvider},
4042
];
4143

4244

libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-sett
1414
import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto';
1515
import { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto';
1616
import { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto';
17+
import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto';
1718

1819
export class EmptySettings {}
1920
export class Integration {
@@ -66,6 +67,7 @@ export class Post {
6667
{ value: MediumSettingsDto, name: 'medium' },
6768
{ value: HashnodeSettingsDto, name: 'hashnode' },
6869
{ value: RedditSettingsDto, name: 'reddit' },
70+
{ value: LemmySettingsDto, name: 'lemmy' },
6971
{ value: YoutubeSettingsDto, name: 'youtube' },
7072
{ value: PinterestSettingsDto, name: 'pinterest' },
7173
{ value: DribbbleDto, name: 'dribbble' },
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
ArrayMinSize,
3+
IsDefined,
4+
IsOptional,
5+
IsString,
6+
IsUrl,
7+
MinLength,
8+
ValidateIf,
9+
ValidateNested,
10+
} from 'class-validator';
11+
import { Type } from 'class-transformer';
12+
13+
export class LemmySettingsDtoInner {
14+
@IsString()
15+
@MinLength(2)
16+
@IsDefined()
17+
subreddit: string;
18+
19+
@IsString()
20+
@IsDefined()
21+
id: string;
22+
23+
@IsString()
24+
@MinLength(2)
25+
@IsDefined()
26+
title: string;
27+
28+
@ValidateIf((o) => o.url)
29+
@IsOptional()
30+
@IsUrl()
31+
url: string;
32+
}
33+
34+
export class LemmySettingsValueDto {
35+
@Type(() => LemmySettingsDtoInner)
36+
@IsDefined()
37+
@ValidateNested()
38+
value: LemmySettingsDtoInner;
39+
}
40+
41+
export class LemmySettingsDto {
42+
@Type(() => LemmySettingsValueDto)
43+
@ValidateNested({ each: true })
44+
@ArrayMinSize(1)
45+
subreddit: LemmySettingsValueDto[];
46+
}

libraries/nestjs-libraries/src/integrations/integration.manager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { DiscordProvider } from '@gitroom/nestjs-libraries/integrations/social/d
2121
import { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/slack.provider';
2222
import { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider';
2323
import { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider';
24+
import { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lemmy.provider';
2425
// import { MastodonCustomProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.custom.provider';
2526

2627
const socialIntegrationList: SocialProvider[] = [
@@ -39,6 +40,7 @@ const socialIntegrationList: SocialProvider[] = [
3940
new SlackProvider(),
4041
new MastodonProvider(),
4142
new BlueskyProvider(),
43+
new LemmyProvider(),
4244
// new MastodonCustomProvider(),
4345
];
4446

0 commit comments

Comments
 (0)