feat: pinterest

This commit is contained in:
Nevo David 2024-05-29 21:40:10 +07:00
parent 6eac29789c
commit 7d3302e3b8
21 changed files with 549 additions and 13 deletions

View File

@ -87,6 +87,7 @@ export class PostsController {
@GetOrgFromRequest() org: Organization,
@Body() body: CreatePostDto
) {
console.log(JSON.stringify(body, null, 2));
return this._postsService.createPost(org.id, body);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,6 @@
<svg width="366" height="167" viewBox="0 0 366 167" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.9659 30.4263V43.3825C26.9237 41.3095 29.3998 39.582 32.3941 38.2C35.3885 36.7028 39.0162 35.9543 43.2774 35.9543C47.1931 35.9543 50.8784 36.7028 54.3334 38.2C57.9036 39.6972 61.0131 42.1157 63.6619 45.4555C66.4259 48.6802 68.6141 52.9989 70.2264 58.4118C71.8387 63.8246 72.6449 70.3891 72.6449 78.1053C72.6449 83.6333 72.1266 89.1613 71.0902 94.6893C70.1688 100.217 68.4989 105.169 66.0804 109.546C63.6619 113.922 60.3796 117.492 56.2336 120.256C52.2028 122.905 47.1355 124.23 41.0316 124.23C36.6553 124.23 33.2003 123.654 30.6666 122.502C28.1329 121.235 26.2327 119.796 24.9659 118.183V160.162L0.0898438 166.381V30.4263H24.9659ZM32.7396 109.2C35.734 109.2 38.2676 108.221 40.3406 106.264C42.4136 104.191 44.026 101.542 45.1776 98.3171C46.4445 95.0924 47.3082 91.5222 47.7689 87.6066C48.3447 83.5757 48.6326 79.6025 48.6326 75.6868C48.6326 69.3526 48.0568 64.3429 46.9051 60.6575C45.8686 56.9722 44.6018 54.2658 43.1046 52.5383C41.6075 50.6956 40.1103 49.5439 38.6131 49.0833C37.2311 48.6226 36.137 48.3923 35.3309 48.3923C33.2579 48.3923 31.2425 49.1409 29.2846 50.638C27.3268 52.02 25.8872 54.1506 24.9659 57.0298V105.227C25.5417 106.148 26.463 107.07 27.7299 107.991C28.9967 108.797 30.6666 109.2 32.7396 109.2Z" fill="white"/>
<path d="M188.176 31.4627C191.055 42.5188 193.588 51.5593 195.777 58.5845C197.965 65.4945 199.807 71.3105 201.305 76.0323C202.917 80.7541 204.126 84.9001 204.932 88.4703C205.854 92.0405 206.314 96.0137 206.314 100.39C208.272 99.1232 210.172 97.7988 212.015 96.4168C213.858 94.9196 215.413 93.5376 216.679 92.2708H223.935C220.825 96.9926 217.543 100.908 214.088 104.018C210.633 107.012 207.293 109.661 204.069 111.964C201.996 116.456 198.829 119.623 194.567 121.466C190.306 123.308 185.872 124.23 181.266 124.23C176.083 124.23 171.649 123.539 167.964 122.157C164.279 120.659 161.227 118.702 158.808 116.283C156.505 113.749 154.777 110.87 153.626 107.646C152.474 104.421 151.898 101.023 151.898 97.4533C151.898 93.5376 152.819 90.4857 154.662 88.2975C156.62 85.9942 158.866 84.8426 161.399 84.8426C168.424 84.8426 171.937 87.6641 171.937 93.3073C171.937 95.15 171.304 96.7047 170.037 97.9716C168.77 99.2384 167.158 99.8718 165.2 99.8718C164.278 99.8718 163.3 99.7566 162.263 99.5263C161.342 99.1808 160.593 98.5474 160.017 97.6261C160.939 101.657 162.436 104.824 164.509 107.127C166.697 109.431 169.461 110.582 172.801 110.582C175.68 110.582 177.811 109.891 179.193 108.509C180.575 107.012 181.266 104.478 181.266 100.908C181.266 97.1078 180.92 93.7104 180.229 90.7161C179.653 87.6066 178.732 84.2091 177.465 80.5238C176.198 76.8385 174.644 72.4621 172.801 67.3948C170.958 62.2123 168.885 55.5326 166.582 47.3558C160.823 59.6786 153.222 67.5675 143.779 71.0225C143.779 71.9439 143.779 72.8652 143.779 73.7865C143.894 74.5927 143.952 75.4565 143.952 76.3778C143.952 83.0575 143.376 89.334 142.224 95.2076C141.072 100.966 139.115 106.033 136.351 110.41C133.702 114.671 130.247 118.068 125.986 120.602C121.724 123.02 116.484 124.23 110.265 124.23C106.004 124.23 101.916 123.596 98 122.329C94.1995 120.947 90.8021 118.759 87.8078 115.765C84.8134 112.655 82.3949 108.624 80.5523 103.672C78.8248 98.605 77.961 92.4436 77.961 85.188C77.961 80.2359 78.4793 74.9382 79.5158 69.295C80.5523 63.5367 82.4525 58.1814 85.2165 53.2293C87.9805 48.2771 91.7234 44.1887 96.4453 40.964C101.282 37.6242 107.444 35.9543 114.93 35.9543C122.646 35.9543 128.807 38.0273 133.414 42.1733C138.136 46.3193 141.303 52.9989 142.915 62.2123C146.946 61.2909 150.574 58.5269 153.798 53.9203C157.138 49.1984 160.305 42.8643 163.3 34.9177L188.176 31.4627ZM115.102 107.991C117.521 107.991 119.594 107.185 121.321 105.573C123.164 103.845 124.661 101.542 125.813 98.6626C126.964 95.6682 127.771 92.1556 128.231 88.1248C128.807 84.094 129.095 79.7176 129.095 74.9958V72.75C124.488 71.7135 122.185 68.3161 122.185 62.5578C122.185 58.8724 123.682 56.4539 126.677 55.3023C125.41 51.6169 123.855 49.1984 122.012 48.0468C120.285 46.8951 118.788 46.3193 117.521 46.3193C114.987 46.3193 112.799 47.5285 110.956 49.947C109.229 52.2504 107.789 55.2447 106.638 58.93C105.486 62.5002 104.622 66.4734 104.046 70.8498C103.586 75.2261 103.355 79.4297 103.355 83.4605C103.355 88.6431 103.701 92.8466 104.392 96.0713C105.198 99.296 106.177 101.772 107.329 103.5C108.48 105.227 109.747 106.436 111.129 107.127C112.511 107.703 113.835 107.991 115.102 107.991Z" fill="white"/>
<path d="M239.554 9.52348V36.818H250.092V43.728H239.554V95.5531C239.554 100.39 240.187 103.615 241.454 105.227C242.836 106.724 245.197 107.473 248.537 107.473C251.877 107.473 254.641 106.033 256.829 103.154C259.132 100.275 260.457 96.6471 260.802 92.2708H268.058C267.136 99.296 265.524 104.939 263.221 109.2C260.917 113.346 258.326 116.571 255.447 118.874C252.568 121.062 249.631 122.502 246.637 123.193C243.642 123.884 240.993 124.23 238.69 124.23C229.822 124.23 223.603 121.811 220.033 116.974C216.463 112.022 214.678 105.515 214.678 97.4533V43.728H209.15V36.818H214.678V12.9785L239.554 9.52348Z" fill="white"/>
<path d="M258.833 13.8422C258.833 10.0417 260.158 6.81706 262.806 4.16823C265.455 1.40422 268.68 0.0222168 272.48 0.0222168C276.281 0.0222168 279.506 1.40422 282.154 4.16823C284.918 6.81706 286.3 10.0417 286.3 13.8422C286.3 17.6427 284.918 20.8674 282.154 23.5162C279.506 26.1651 276.281 27.4895 272.48 27.4895C268.68 27.4895 265.455 26.1651 262.806 23.5162C260.158 20.8674 258.833 17.6427 258.833 13.8422ZM285.609 36.818V95.5531C285.609 100.39 286.243 103.615 287.51 105.227C288.892 106.724 291.253 107.473 294.592 107.473C296.09 107.473 297.184 107.358 297.875 107.127C298.681 106.897 299.372 106.667 299.948 106.436C300.063 107.012 300.12 107.588 300.12 108.164C300.12 108.74 300.12 109.315 300.12 109.891C300.12 112.77 299.602 115.131 298.566 116.974C297.644 118.817 296.377 120.314 294.765 121.466C293.268 122.502 291.598 123.193 289.755 123.539C288.028 123.999 286.358 124.23 284.746 124.23C275.878 124.23 269.659 121.811 266.089 116.974C262.518 112.022 260.733 105.515 260.733 97.4533V36.818H285.609ZM351.773 107.473C350.391 107.358 349.354 106.897 348.663 106.091C347.972 105.169 347.627 104.133 347.627 102.981C347.627 101.484 348.26 100.045 349.527 98.6626C350.794 97.1654 352.867 96.4168 355.746 96.4168C358.971 96.4168 361.389 97.5109 363.001 99.6991C364.614 101.772 365.42 104.248 365.42 107.127C365.42 108.97 365.074 110.87 364.383 112.828C363.692 114.671 362.598 116.398 361.101 118.011C359.604 119.508 357.761 120.775 355.573 121.811C353.385 122.732 350.851 123.193 347.972 123.193H300.293L334.152 46.1465H321.369C318.835 46.1465 316.704 46.3193 314.977 46.6648C313.365 46.8951 312.558 47.5285 312.558 48.565C312.558 49.0257 312.674 49.256 312.904 49.256C313.249 49.256 313.595 49.3712 313.94 49.6015C314.401 49.8318 314.747 50.2925 314.977 50.9835C315.322 51.6745 315.495 52.8838 315.495 54.6113C315.495 57.1449 314.689 58.9876 313.077 60.1393C311.579 61.2909 309.852 61.8668 307.894 61.8668C305.591 61.8668 303.345 61.1182 301.157 59.621C299.084 58.0087 298.047 55.5902 298.047 52.3655C298.047 50.638 298.393 48.9105 299.084 47.183C299.775 45.3403 300.811 43.6704 302.193 42.1733C303.575 40.5609 305.303 39.2941 307.376 38.3728C309.449 37.3363 311.867 36.818 314.631 36.818H362.138L329.142 109.891C329.833 109.891 330.812 109.949 332.079 110.064C333.346 110.179 334.67 110.294 336.052 110.41C337.55 110.525 338.989 110.64 340.371 110.755C341.868 110.87 343.193 110.928 344.344 110.928C346.417 110.928 348.145 110.697 349.527 110.237C351.024 109.776 351.773 108.855 351.773 107.473Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,6 @@
<svg width="145" height="160" viewBox="0 0 145 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.67 27.5459C23.7772 29.2774 23.9284 31.2308 24.101 33.4605L30.3317 113.972C31.0281 122.971 31.3764 127.47 33.3936 130.772C35.168 133.676 37.8163 135.944 40.9588 137.25C44.5314 138.735 49.0308 138.387 58.0295 137.691L119.694 132.918C125.528 132.467 129.471 132.162 132.422 131.453C131.433 133.777 129.905 135.847 127.952 137.486C124.988 139.972 120.59 140.987 111.796 143.015L51.529 156.917C42.7343 158.945 38.337 159.96 34.583 159.023C31.281 158.199 28.3246 156.351 26.1375 153.743C23.6511 150.779 22.6367 146.382 20.6081 137.587L2.45777 58.901C0.42913 50.1063 -0.585193 45.709 0.351525 41.9551C1.17549 38.653 3.02365 35.6966 5.63114 33.5095C8.59546 31.0231 12.9928 30.0088 21.7875 27.9801L23.67 27.5459Z" fill="#612BD3"/>
<path d="M26.5624 33.2683C26.2111 28.7284 25.9609 25.4713 25.9719 22.8993C25.9828 20.3562 26.2536 18.7014 26.8213 17.3359C27.9276 14.6747 29.848 12.4321 32.3073 10.9295C33.5692 10.1585 35.1627 9.63635 37.6738 9.23431C40.2134 8.82769 43.4702 8.57372 48.0102 8.22237L109.675 3.45015C114.215 3.0988 117.472 2.84866 120.044 2.85968C122.587 2.87059 124.242 3.14131 125.607 3.70902C128.269 4.81537 130.511 6.73578 132.014 9.19502C132.785 10.4569 133.307 12.0504 133.709 14.5615C134.116 17.1012 134.37 20.358 134.721 24.898L140.952 105.41C141.303 109.95 141.553 113.207 141.542 115.779C141.531 118.322 141.261 119.976 140.693 121.342C139.586 124.003 137.666 126.246 135.207 127.748C133.945 128.519 132.351 129.041 129.84 129.444C127.301 129.85 124.044 130.104 119.504 130.455L57.8391 135.228C53.2992 135.579 50.0421 135.829 47.4701 135.818C44.927 135.807 43.2722 135.537 41.9067 134.969C39.2455 133.862 37.0029 131.942 35.5003 129.483C34.7293 128.221 34.2072 126.627 33.8051 124.116C33.3985 121.577 33.1445 118.32 32.7932 113.78L26.5624 33.2683Z" stroke="#131019" stroke-width="4.93733"/>
<path d="M48.2009 10.6828L109.866 5.91061C114.446 5.55612 117.586 5.31699 120.034 5.32748C122.424 5.33773 123.715 5.59492 124.66 5.98769C126.84 6.89391 128.677 8.46693 129.907 10.4813C130.441 11.3544 130.894 12.5911 131.272 14.951C131.659 17.3679 131.905 20.507 132.26 25.0876L138.491 105.599C138.845 110.18 139.084 113.319 139.074 115.767C139.063 118.157 138.806 119.449 138.413 120.393C137.507 122.573 135.934 124.41 133.92 125.641C133.047 126.174 131.81 126.627 129.45 127.005C127.033 127.392 123.894 127.639 119.314 127.993L57.6488 132.766C53.0683 133.12 49.9286 133.359 47.4808 133.349C45.0909 133.338 43.7993 133.081 42.8545 132.688C40.6748 131.782 38.8379 130.209 37.6071 128.195C37.0736 127.322 36.6207 126.085 36.2429 123.725C35.8559 121.308 35.6092 118.169 35.2547 113.589L29.0239 33.077C28.6694 28.4964 28.4303 25.3567 28.4408 22.909C28.451 20.5191 28.7082 19.2275 29.101 18.2827C30.0072 16.1029 31.5802 14.266 33.5946 13.0352C34.4677 12.5018 35.7044 12.0489 38.0642 11.6711C40.4812 11.2841 43.6203 11.0373 48.2009 10.6828Z" fill="white"/>
<path d="M74.8847 29.3257L75.4745 36.9536C76.5328 35.644 77.9119 34.5142 79.6119 33.5643C81.3066 32.5466 83.4083 31.9407 85.9171 31.7468C88.2224 31.5685 90.4261 31.8415 92.5284 32.5657C94.6984 33.2846 96.6392 34.5669 98.3507 36.4127C100.125 38.1853 101.61 40.6284 102.805 43.7417C104.001 46.8551 104.774 50.6832 105.125 55.226C105.377 58.4805 105.324 61.7587 104.965 65.0604C104.674 68.3569 103.916 71.3484 102.692 74.035C101.467 76.7216 99.6973 78.9729 97.3822 80.7889C95.1296 82.5319 92.2066 83.5423 88.613 83.8201C86.0365 84.0193 83.9762 83.8376 82.4321 83.2749C80.8828 82.6443 79.6985 81.8833 78.8792 80.9917L80.79 105.706L66.4276 110.5L60.2393 30.458L74.8847 29.3257ZM83.0471 75.3492C84.81 75.2129 86.2571 74.5213 87.3884 73.2743C88.5145 71.9595 89.3432 70.3266 89.8744 68.3757C90.4735 66.4195 90.8195 64.2783 90.9125 61.952C91.068 59.5527 91.0567 57.2004 90.8784 54.8951C90.5901 51.1659 90.0231 48.2427 89.1773 46.1254C88.3993 44.0029 87.5303 42.4672 86.5702 41.5183C85.6049 40.5016 84.6711 39.8917 83.7686 39.6886C82.934 39.4803 82.2794 39.3945 81.8048 39.4312C80.5843 39.5256 79.4319 40.058 78.3474 41.0286C77.2576 41.9313 76.507 43.2512 76.0957 44.9882L78.2895 73.3639C78.6705 73.8801 79.2548 74.3806 80.0426 74.8653C80.8251 75.2823 81.8266 75.4436 83.0471 75.3492Z" fill="#131019"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -259,12 +259,12 @@ export const AddEditModal: FC<{
// @ts-ignore
const images = key?.value[0].image;
if (
(images?.length || 0) > (key.maximumMediaRequirements || 0) ||
(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) || !key.maximumMediaRequirements
? key.minimumMediaRequirements
: `between ${key.minimumMediaRequirements} to ${key.maximumMediaRequirements}`
}`,

View File

@ -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 PinterestBoard: FC<{
name: string;
onChange: (event: { target: { value: string; name: string } }) => void;
}> = (props) => {
const { onChange, name } = props;
const customFunc = useCustomProviderFunction();
const [orgs, setOrgs] = useState<undefined|any[]>();
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('boards').then((data) => setOrgs(data));
const settings = getValues()[props.name];
if (settings) {
setCurrentMedia(settings);
}
}, []);
if (!orgs) {
return null;
}
if (!orgs.length) {
return 'No boards found, you have to create a board first';
}
return (
<Select name={name} label="Select board" onChange={onChangeInner} value={currentMedia}>
<option value="">--Select--</option>
{orgs.map((org: any) => (
<option key={org.id} value={org.id}>
{org.name}
</option>
))}
</Select>
);
};

View File

@ -0,0 +1,135 @@
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 { PinterestBoard } from '@gitroom/frontend/components/launches/providers/pinterest/pinterest.board';
import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';
import { Input } from '@gitroom/react/form/input';
import { ColorPicker } from '@gitroom/react/form/color.picker';
const PinterestSettings: FC = () => {
const { register, control } = useSettings();
return (
<div className="flex flex-col">
<Input label={'Title'} {...register('title')} />
<Input label={'Description'} {...register('description')} />
<PinterestBoard {...register('board')} />
<ColorPicker label="Select Pin Color" name="dominant_color" enabled={false} canBeCancelled={true} />
</div>
);
};
const PinterestPreview: 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 (
<div className="rounded-[8px] flex flex-col gap-[8px] border border-black/90 w-[555px] pt-[12px] pl-[16px] pb-[12px] pr-[40px] bg-white text-black font-['helvetica']">
<div className="flex gap-[8px]">
<div className="w-[48px] h-[48px]">
<img
src={integration?.picture}
alt="x"
className="rounded-full w-full h-full relative z-[2]"
/>
</div>
<div className="flex flex-col leading-[16px]">
<div className="text-[14px] font-[600]">{integration?.name}</div>
<div className="text-[12px] font-[400] text-black/60">
CEO @ Gitroom
</div>
<div className="text-[12px] font-[400] text-black/60">1m</div>
</div>
</div>
<div>
<pre
className="font-['helvetica'] text-[14px] font-[400] text-wrap"
dangerouslySetInnerHTML={{ __html: firstPost?.text }}
/>
{!!firstPost?.images?.length && (
<div className="-ml-[16px] -mr-[40px] flex-1 h-[555px] flex overflow-hidden mt-[12px] gap-[2px]">
{firstPost.images.map((image, index) => (
<a
key={`image_${index}`}
href={mediaDir.set(image.path)}
className="flex-1"
target="_blank"
>
<VideoOrImage autoplay={true} src={mediaDir.set(image.path)} />
</a>
))}
</div>
)}
</div>
{morePosts.map((p, index) => (
<div className="flex gap-[8px]" key={index}>
<div className="w-[40px] h-[40px]">
<img
src={integration?.picture}
alt="x"
className="rounded-full w-full h-full relative z-[2]"
/>
</div>
<div className="flex-1 flex flex-col leading-[16px] bg-[#F2F2F2] w-full pt-[8px] pr-[64px] pl-[12px] pb-[8px] rounded-[8px]">
<div className="text-[14px] font-[600]">{integration?.name}</div>
<div className="text-[12px] font-[400] text-black/60">
CEO @ Gitroom
</div>
<div className="text-[14px] mt-[8px] font-[400] text-black/90">
{p.text}
</div>
{!!p?.images?.length && (
<div className="w-full h-[120px] flex overflow-hidden mt-[12px] gap-[3px]">
{p.images.map((image, index) => (
<a
key={`image_${index}`}
href={mediaDir.set(image.path)}
target="_blank"
>
<div className="w-[120px] h-full">
<VideoOrImage
autoplay={true}
src={mediaDir.set(image.path)}
/>
</div>
</a>
))}
</div>
)}
</div>
</div>
))}
</div>
);
};
export default withProvider(
PinterestSettings,
PinterestPreview,
PinterestSettingsDto,
undefined,
1
);

View File

@ -10,6 +10,7 @@ import FacebookProvider from '@gitroom/frontend/components/launches/providers/fa
import InstagramProvider from '@gitroom/frontend/components/launches/providers/instagram/instagram.provider';
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';
export const Providers = [
{identifier: 'devto', component: DevtoProvider},
@ -22,6 +23,7 @@ export const Providers = [
{identifier: 'instagram', component: InstagramProvider},
{identifier: 'youtube', component: YoutubeProvider},
{identifier: 'tiktok', component: TiktokProvider},
{identifier: 'pinterest', component: PinterestProvider},
];

View File

@ -27,6 +27,7 @@ import { Support } from '@gitroom/frontend/components/layout/support';
import { ContinueProvider } from '@gitroom/frontend/components/layout/continue.provider';
import { isGeneral } from '@gitroom/react/helpers/is.general';
import { Impersonate } from '@gitroom/frontend/components/layout/impersonate';
import clsx from 'clsx';
dayjs.extend(utc);
dayjs.extend(weekOfYear);
@ -62,10 +63,10 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
{user?.admin && <Impersonate />}
<div className="px-[23px] flex h-[80px] items-center justify-between z-[200] sticky top-0 bg-primary">
<Link href="/" className="text-2xl flex items-center gap-[10px]">
<div>
<Image src="/logo.svg" width={55} height={53} alt="Logo" />
<div className="min-w-[55px]">
<Image src={isGeneral() ? "/postiz.svg" : "/logo.svg"} width={55} height={53} alt="Logo" />
</div>
<div className="mt-[12px]">{isGeneral() ? 'Postiz' : 'Gitroom'}</div>
<div className={clsx(!isGeneral() ? "mt-[12px]" : "min-w-[80px]")}>{isGeneral() ? <img src="/postiz-text.svg" className="w-[80px]" /> : 'Gitroom'}</div>
</Link>
{user?.orgId ? <TopMenu /> : <div />}
<div className="flex items-center gap-[8px]">

View File

@ -9,6 +9,7 @@ import {MediumSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers-
import {HashnodeSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto";
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';
export class EmptySettings {}
export class Integration {
@ -62,6 +63,7 @@ export class Post {
{ value: HashnodeSettingsDto, name: 'hashnode' },
{ value: RedditSettingsDto, name: 'reddit' },
{ value: YoutubeSettingsDto, name: 'youtube' },
{ value: PinterestSettingsDto, name: 'pinterest' },
],
},
})

View File

@ -2,9 +2,13 @@ import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers
import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';
import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';
import { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto';
import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';
import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';
export type AllProvidersSettings =
| DevToSettingsDto
| MediumSettingsDto
| HashnodeSettingsDto
| RedditSettingsDto;
| RedditSettingsDto
| YoutubeSettingsDto
| PinterestSettingsDto;

View File

@ -11,7 +11,7 @@ import {
} from 'class-validator';
import { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';
import { Type } from 'class-transformer';
import { DevToTagsSettings } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.tags.settings';
import { DevToTagsSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.tags.settings.dto';
export class DevToSettingsDto {
@IsString()
@ -42,5 +42,5 @@ export class DevToSettingsDto {
@IsArray()
@ArrayMaxSize(4)
@IsOptional()
tags: DevToTagsSettings[];
tags: DevToTagsSettingsDto[];
}

View File

@ -1,6 +1,6 @@
import {IsNumber, IsString} from "class-validator";
export class DevToTagsSettings {
export class DevToTagsSettingsDto {
@IsNumber()
value: number;

View File

@ -0,0 +1,32 @@
import { IsDefined, IsOptional, IsString, IsUrl, MinLength, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class PinterestSettingsDto {
@IsString()
@IsOptional()
title: string;
@IsString()
@IsOptional()
description: string;
@IsString()
@IsOptional()
@IsUrl()
link: string;
@IsString()
@IsOptional()
dominant_color: string;
@IsDefined({
message: 'Board is required'
})
@IsString({
message: 'Board is required'
})
@MinLength(1, {
message: 'Board is required'
})
board: string;
}

View File

@ -1,5 +1,4 @@
import {
ArrayMaxSize,
IsArray,
IsDefined,
IsOptional,

View File

@ -11,6 +11,7 @@ import { FacebookProvider } from '@gitroom/nestjs-libraries/integrations/social/
import { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider';
import { YoutubeProvider } from '@gitroom/nestjs-libraries/integrations/social/youtube.provider';
import { TiktokProvider } from '@gitroom/nestjs-libraries/integrations/social/tiktok.provider';
import { PinterestProvider } from '@gitroom/nestjs-libraries/integrations/social/pinterest.provider';
const socialIntegrationList = [
new XProvider(),
@ -20,6 +21,7 @@ const socialIntegrationList = [
new InstagramProvider(),
new YoutubeProvider(),
new TiktokProvider(),
new PinterestProvider()
];
const articleIntegrationList = [

View File

@ -219,7 +219,7 @@ export class InstagramProvider implements SocialProvider {
let containerIdGlobal = '';
let linkGlobal = '';
if (medias.length === 1) {
const { id: mediaId, ...all } = await (
const { id: mediaId } = await (
await fetch(
`https://graph.facebook.com/v20.0/${id}/media_publish?creation_id=${medias[0]}&access_token=${accessToken}&field=id`,
{
@ -228,8 +228,6 @@ export class InstagramProvider implements SocialProvider {
)
).json();
console.log(all);
containerIdGlobal = mediaId;
const { permalink } = await (

View File

@ -0,0 +1,222 @@
import {
AuthTokenDetails,
PostDetails,
PostResponse,
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();
export class PinterestProvider implements SocialProvider {
identifier = 'pinterest';
name = 'Pinterest';
isBetweenSteps = false;
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
return {
refreshToken: '',
expiresIn: 0,
accessToken: '',
id: '',
name: '',
picture: '',
username: '',
};
}
async generateAuthUrl(refresh?: string) {
const state = makeId(6);
return {
url: `https://www.pinterest.com/oauth/?client_id=${
process.env.PINTEREST_CLIENT_ID
}&redirect_uri=${encodeURIComponent(
`${process.env.FRONTEND_URL}/integrations/social/pinterest${
refresh ? `?refresh=${refresh}` : ''
}`
)}&response_type=code&scope=${encodeURIComponent(
'boards:read,boards:write,pins:read,pins:write,user_accounts:read'
)}&state=${state}`,
codeVerifier: makeId(10),
state,
};
}
async authenticate(params: {
code: string;
codeVerifier: string;
refresh: string;
}) {
const { access_token, refresh_token, expires_in } = await (
await fetch('https://api-sandbox.pinterest.com/v5/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(
`${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`
).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: params.code,
redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,
}),
})
).json();
const { id, profile_image, username } = await (
await fetch('https://api-sandbox.pinterest.com/v5/user_account', {
method: 'GET',
headers: {
Authorization: `Bearer ${access_token}`,
},
})
).json();
return {
id: id,
name: username,
accessToken: access_token,
refreshToken: refresh_token,
expiresIn: expires_in,
picture: profile_image,
username,
};
}
async boards(accessToken: string) {
const { items } = await (
await 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<PinterestSettingsDto>[]
): Promise<PostResponse[]> {
let mediaId = '';
if ((postDetails?.[0]?.media?.[0]?.path?.indexOf('mp4') || -1) > -1) {
const { upload_url, media_id, upload_parameters } = await (
await 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();
console.log(media_id, upload_url);
try {
const { data } = await axios({
url: postDetails?.[0]?.media?.[0]?.url,
method: 'GET',
responseType: 'stream',
});
const p = await (
await fetch(upload_url, {
method: 'PUT',
body: data.buffer,
headers: {
Authorization: `Bearer ${accessToken}`,
...upload_parameters,
},
})
).json();
console.log(p);
} catch (err) {
console.log(err);
}
mediaId = media_id;
}
const mapImages = postDetails?.[0]?.media?.map((m) => ({
url: m.url,
}));
console.log('1');
try {
const {
id: pId,
link,
...all
} = await (
await 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',
media_id: mediaId,
}
: mapImages?.length === 1
? {
source_type: 'image_url',
url: mapImages?.[0]?.url,
}
: {
source_type: 'multiple_image_urls',
items: mapImages,
},
}),
})
).json();
console.log(all);
return [
{
id,
postId: pId,
releaseURL: link,
status: 'success',
},
];
} catch (err) {
console.log(err);
return [];
}
}
}

View File

@ -0,0 +1,68 @@
import { FC, useCallback, useState } from 'react';
import { Button } from './button';
import { HexColorPicker } from 'react-colorful';
import { useFormContext } from 'react-hook-form';
import interClass from '../helpers/inter.font';
export const ColorPicker: FC<{
name: string;
label: string;
enabled: boolean;
canBeCancelled: boolean;
}> = (props) => {
const { name, label, enabled, canBeCancelled } = props;
const form = useFormContext();
const [enabledState, setEnabledState] = useState(enabled);
const color = form.register(name);
const watch = form.watch(name);
const enable = useCallback(async () => {
await color.onChange({ target: { name, value: '#FFFFFF' } });
setEnabledState(true);
}, []);
const cancel = useCallback(async () => {
await color.onChange({ target: { name, value: '' } });
setEnabledState(false);
}, []);
if (!enabledState) {
return (
<div>
<Button onClick={enable}>
Enable color picker
</Button>
</div>
);
}
return (
<div className="flex flex-col gap-[6px]">
<div>
{!!label && <div className={`${interClass} text-[14px]`}>{label}</div>}
</div>
{canBeCancelled && (
<div>
<Button onClick={cancel}>Cancel the color picker</Button>
</div>
)}
<div className="flex items-end gap-[20px]">
<div>
<HexColorPicker
color={watch}
onChange={(value) => color.onChange({ target: { name, value } })}
/>
</div>
<div className="flex gap-[10px]">
<div>
<div
className="w-[20px] h-[20px]"
style={{ backgroundColor: watch }}
/>
</div>
<div>{watch}</div>
</div>
</div>
</div>
);
};

10
package-lock.json generated
View File

@ -70,6 +70,7 @@
"openai": "^4.47.1",
"prisma-paginate": "^5.2.1",
"react": "18.2.0",
"react-colorful": "^5.6.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
@ -33283,6 +33284,15 @@
"node": ">=0.10.0"
}
},
"node_modules/react-colorful": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-dnd": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",

View File

@ -74,6 +74,7 @@
"openai": "^4.47.1",
"prisma-paginate": "^5.2.1",
"react": "18.2.0",
"react-colorful": "^5.6.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",