diff --git a/apps/frontend/src/app/global.scss b/apps/frontend/src/app/global.scss index d968196e..d7473664 100644 --- a/apps/frontend/src/app/global.scss +++ b/apps/frontend/src/app/global.scss @@ -99,6 +99,7 @@ html { cursor: text; display: flex; align-items: center; + margin-right: 0 !important; } .react-tags.is-active { @@ -191,6 +192,7 @@ html { .react-tags__combobox-input { /* prevent autoresize overflowing the container */ max-width: 100%; + width: 100% !important; /* remove styles and layout from this element */ margin: 0; padding: 0; @@ -198,7 +200,7 @@ html { outline: none; background: none; /* match the font styles */ - font-size: inherit; + font-size: 16px; line-height: inherit; } @@ -434,7 +436,7 @@ div div .set-font-family { } .tags-top .react-tags__combobox { - height: 35px; + height: 44px; display: flex; background-color: #141c2c; padding-left: 10px; diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx deleted file mode 100644 index 80caafd4..00000000 --- a/apps/frontend/src/components/launches/add.edit.model.tsx +++ /dev/null @@ -1,1002 +0,0 @@ -'use client'; - -import React, { - FC, - Fragment, - MouseEventHandler, - useCallback, - useEffect, - useMemo, - ClipboardEvent, - useState, - memo, -} from 'react'; -import dayjs from 'dayjs'; -import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; -import clsx from 'clsx'; -import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import { useModals } from '@mantine/modals'; -import { useHideTopEditor } from '@gitroom/frontend/components/launches/helpers/use.hide.top.editor'; -import { Button } from '@gitroom/react/form/button'; -// @ts-ignore -import useKeypress from 'react-use-keypress'; -import { - getValues, - resetValues, -} from '@gitroom/frontend/components/launches/helpers/use.values'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useMoveToIntegration } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration'; -import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; -import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { useExpend } from '@gitroom/frontend/components/launches/helpers/use.expend'; -import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; -import { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component'; -import { ProvidersOptions } from '@gitroom/frontend/components/launches/providers.options'; -import useSWR from 'swr'; -import { useToaster } from '@gitroom/react/toaster/toaster'; -import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow'; -import { DatePicker } from '@gitroom/frontend/components/launches/helpers/date.picker'; -import { arrayMoveImmutable } from 'array-move'; -import { - Information, - PostToOrganization, -} from '@gitroom/frontend/components/launches/post.to.organization'; -import { Submitted } from '@gitroom/frontend/components/launches/submitted'; -import { supportEmitter } from '@gitroom/frontend/components/layout/support'; -import { Editor } from '@gitroom/frontend/components/launches/editor'; -import { AddPostButton } from '@gitroom/frontend/components/launches/add.post.button'; -import { useStateCallback } from '@gitroom/react/helpers/use.state.callback'; -import { CopilotPopup } from '@copilotkit/react-ui'; -import { useUser } from '@gitroom/frontend/components/layout/user.context'; -import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; -import Image from 'next/image'; -import { weightedLength } from '@gitroom/helpers/utils/count.length'; -import { useClickOutside } from '@gitroom/frontend/components/layout/click.outside'; -import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader'; -import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; -import { DropFiles } from '@gitroom/frontend/components/layout/drop.files'; -import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer'; -import { TagsComponent } from './tags.component'; -import { RepeatComponent } from '@gitroom/frontend/components/launches/repeat.component'; -import { MergePost } from '@gitroom/frontend/components/launches/merge.post'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { type CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; -import { uniq } from 'lodash'; -import { SetContext } from '@gitroom/frontend/components/launches/set.context'; -function countCharacters(text: string, type: string): number { - if (type !== 'x') { - return text.length; - } - return weightedLength(text); -} - -export const AddEditModal: FC<{ - date: dayjs.Dayjs; - integrations: Integrations[]; - allIntegrations?: Integrations[]; - set?: CreatePostDto; - addEditSets?: (data: any) => void; - reopenModal: () => void; - mutate: () => void; - padding?: string; - customClose?: () => void; - onlyValues?: Array<{ - content: string; - id?: string; - image?: Array<{ - id: string; - path: string; - }>; - }>; -}> = memo((props) => { - const { - date, - integrations: ints, - reopenModal, - mutate, - onlyValues, - padding, - customClose, - addEditSets, - set, - } = props; - - const [customer, setCustomer] = useState(''); - const [loading, setLoading] = useState(false); - const [uploading, setUploading] = useState(false); - const [canUseClose, setCanUseClose] = useState(true); - const t = useT(); - - // selected integrations to allow edit - const [selectedIntegrations, setSelectedIntegrations] = useStateCallback< - Integrations[] - >([]); - - const integrations = useMemo(() => { - if (!customer) { - return ints; - } - const list = ints.filter((f) => f?.customer?.id === customer); - if (list.length === 1 && !set) { - setSelectedIntegrations([list[0]]); - } - return list; - }, [customer, ints, set]); - const [dateState, setDateState] = useState(date); - - // hook to open a new modal - const modal = useModals(); - - const selectIntegrationsDefault = useMemo(() => { - if (!set) { - return []; - } - - const keepReference: Integrations[] = []; - const neededIntegrations = uniq(set.posts.flatMap((p) => p.integration.id)); - for (const i of ints) { - if (neededIntegrations.indexOf(i.id) > -1) { - keepReference.push(i); - } - } - - return keepReference; - }, [set]); - - useEffect(() => { - if (set?.posts) { - setSelectedIntegrations(selectIntegrationsDefault); - } - }, [selectIntegrationsDefault]); - - // value of each editor - const [value, setValue] = useState< - Array<{ - content: string; - id?: string; - image?: Array<{ - id: string; - path: string; - }>; - }> - >( - onlyValues - ? onlyValues - : set - ? set?.posts?.[0]?.value || [ - { - content: '', - }, - ] - : [ - { - content: '', - }, - ] - ); - const fetch = useFetch(); - const user = useUser(); - const updateOrder = useCallback(() => { - modal.closeAll(); - reopenModal(); - }, [reopenModal, modal]); - - // prevent the window exit by mistake - usePreventWindowUnload(true); - - // hook to move the settings in the right place to fix missing fields - const moveToIntegration = useMoveToIntegration(); - - // hook to test if the top editor should be hidden - const showHide = useHideTopEditor(); - - // merge all posts and delete all the comments - const merge = useCallback(() => { - setValue( - value.reduce( - (all, current) => { - all[0].content = all[0].content + current.content + '\n'; - all[0].image = [...all[0].image, ...(current.image || [])]; - return all; - }, - [ - { - content: '', - id: value[0].id, - image: [] as { - id: string; - path: string; - }[], - }, - ] - ) - ); - }, [value]); - const [showError, setShowError] = useState(false); - - // are we in edit mode? - const existingData = useExistingData(); - const [inter, setInter] = useState(existingData?.posts?.[0]?.intervalInDays); - const [tags, setTags] = useState( - // @ts-ignore - existingData?.posts?.[0]?.tags?.map((p: any) => ({ - label: p.tag.name, - value: p.tag.name, - })) || [] - ); - - // Post for - const [postFor, setPostFor] = useState(); - const expend = useExpend(); - const toaster = useToaster(); - - // if it's edit just set the current integration - useEffect(() => { - if (existingData.integration) { - setSelectedIntegrations([ - integrations.find((p) => p.id === existingData.integration)!, - ]); - } else if (integrations.length === 1) { - setSelectedIntegrations([integrations[0]]); - } - }, [existingData.integration]); - - // if the user exit the popup we reset the global variable with all the values - useEffect(() => { - supportEmitter.emit('change', false); - return () => { - supportEmitter.emit('change', true); - resetValues(); - }; - }, []); - - // Change the value of the global editor - const changeValue = useCallback( - (index: number) => (newValue: string) => { - return setValue((prev) => { - prev[index].content = newValue; - return [...prev]; - }); - }, - [value] - ); - const changeImage = useCallback( - (index: number) => - (newValue: { - target: { - name: string; - value?: Array<{ - id: string; - path: string; - }>; - }; - }) => { - return setValue((prev) => { - return prev.map((p, i) => { - if (i === index) { - return { - ...p, - image: newValue.target.value, - }; - } - return p; - }); - }); - }, - [] - ); - - // Add another editor - const addValue = useCallback( - (index: number) => () => { - setValue((prev) => { - return prev.reduce( - (acc, p, i) => { - acc.push(p); - if (i === index) { - acc.push({ - content: '', - }); - } - return acc; - }, - [] as Array<{ - content: string; - }> - ); - }); - }, - [] - ); - const changePosition = useCallback( - (index: number) => (type: 'up' | 'down') => { - if (type === 'up' && index !== 0) { - setValue((prev) => { - return arrayMoveImmutable(prev, index, index - 1); - }); - } else if (type === 'down') { - setValue((prev) => { - return arrayMoveImmutable(prev, index, index + 1); - }); - } - }, - [] - ); - - // Delete post - const deletePost = useCallback( - (index: number) => async () => { - if ( - !(await deleteDialog( - 'Are you sure you want to delete this post?', - 'Yes, delete it!' - )) - ) { - return; - } - setValue((prev) => { - prev.splice(index, 1); - return [...prev]; - }); - }, - [value] - ); - - // override the close modal to ask the user if he is sure to close - const askClose = useCallback(async () => { - if (!canUseClose) { - return; - } - if ( - await deleteDialog( - t( - 'are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost', - 'Are you sure you want to close this modal? (all data will be lost)' - ), - t('yes_close_it', 'Yes, close it!') - ) - ) { - if (customClose) { - customClose(); - return; - } - modal.closeAll(); - } - }, [canUseClose]); - - // sometimes it's easier to click escape to close - useKeypress('Escape', askClose); - const postNow = useCallback( - ((e) => { - e.stopPropagation(); - e.preventDefault(); - return schedule('now')(); - }) as MouseEventHandler, - [] - ); - - // function to send to the server and save - const schedule = useCallback( - (type: 'draft' | 'now' | 'schedule' | 'delete') => async () => { - setLoading(true); - if (type === 'delete') { - if ( - !(await deleteDialog( - 'Are you sure you want to delete this post?', - 'Yes, delete it!' - )) - ) { - setLoading(false); - return; - } - await fetch(`/posts/${existingData.group}`, { - method: 'DELETE', - }); - mutate(); - modal.closeAll(); - return; - } - const values = getValues(); - const allKeys = Object.keys(values).map((v) => ({ - integration: integrations.find((p) => p.id === v), - value: values[v].posts, - valid: values[v].isValid, - group: existingData?.group, - trigger: values[v].trigger, - settings: values[v].settings(), - checkValidity: values[v].checkValidity, - maximumCharacters: values[v].maximumCharacters, - })); - if (type !== 'draft') { - for (const key of allKeys) { - if (key.checkValidity) { - const check = await key.checkValidity( - key?.value.map((p: any) => p.image || []), - key.settings, - JSON.parse(key?.integration?.additionalSettings || '[]') - ); - if (typeof check === 'string') { - toaster.show(check, 'warning'); - setLoading(false); - return; - } - } - if ( - key.value.some((p) => { - return ( - countCharacters(p.content, key?.integration?.identifier || '') > - (key.maximumCharacters || 1000000) - ); - }) - ) { - if ( - !(await deleteDialog( - `${key?.integration?.name} post is too long, it will be cropped, do you want to continue?`, - 'Yes, continue' - )) - ) { - await key.trigger(); - moveToIntegration({ - identifier: key?.integration?.id!, - toPreview: true, - }); - setLoading(false); - return; - } - } - if (key.value.some((p) => !p.content || p.content.length < 6)) { - setShowError(true); - setLoading(false); - return; - } - if (!key.valid) { - await key.trigger(); - moveToIntegration({ - identifier: key?.integration?.id!, - }); - setLoading(false); - return; - } - } - } - const shortLinkUrl = await ( - await fetch('/posts/should-shortlink', { - method: 'POST', - body: JSON.stringify({ - messages: allKeys.flatMap((p) => - p.value.flatMap((a) => - a.content.slice(0, p.maximumCharacters || 1000000) - ) - ), - }), - }) - ).json(); - - setLoading(false); - const shortLink = !shortLinkUrl.ask - ? false - : await deleteDialog( - 'Do you want to shortlink the URLs? it will let you get statistics over clicks', - 'Yes, shortlink it!' - ); - - setLoading(true); - - const data = { - ...(postFor - ? { - order: postFor.id, - } - : {}), - type, - inter, - tags, - shortLink, - date: dateState.utc().format('YYYY-MM-DDTHH:mm:ss'), - posts: allKeys.map((p) => ({ - ...p, - value: p.value.map((a) => ({ - ...a, - content: a.content.slice(0, p.maximumCharacters || 1000000), - })), - })), - }; - - addEditSets - ? addEditSets(data) - : await fetch('/posts', { - method: 'POST', - body: JSON.stringify(data), - }); - existingData.group = makeId(10); - - if (!addEditSets) { - mutate(); - toaster.show( - !existingData.integration - ? 'Added successfully' - : 'Updated successfully' - ); - } - if (customClose) { - setTimeout(() => { - customClose(); - }, 2000); - } - - if (!addEditSets) { - modal.closeAll(); - } - }, - [ - inter, - postFor, - dateState, - value, - integrations, - existingData, - selectedIntegrations, - tags, - addEditSets, - ] - ); - const uppy = useUppyUploader({ - onUploadSuccess: () => { - /**empty**/ - }, - allowedFileTypes: 'image/*,video/mp4', - }); - const pasteImages = useCallback( - (index: number, currentValue: any[], isFile?: boolean) => { - return async (event: ClipboardEvent | File[]) => { - // @ts-ignore - const clipboardItems = isFile - ? // @ts-ignore - event.map((p) => ({ - kind: 'file', - getAsFile: () => p, - })) - : // @ts-ignore - event.clipboardData?.items; // Ensure clipboardData is available - if (!clipboardItems) { - return; - } - const files: File[] = []; - - // @ts-ignore - for (const item of clipboardItems) { - if (item.kind === 'file') { - const file = item.getAsFile(); - if (file) { - const isImage = file.type.startsWith('image/'); - const isVideo = file.type.startsWith('video/'); - if (isImage || isVideo) { - files.push(file); // Collect images or videos - } - } - } - } - if (files.length === 0) { - return; - } - setUploading(true); - const lastValues = [...currentValue]; - for (const file of files) { - uppy.addFile(file); - const upload = await uppy.upload(); - uppy.clear(); - if (upload?.successful?.length) { - lastValues.push(upload?.successful[0]?.response?.body?.saved!); - changeImage(index)({ - target: { - name: 'image', - value: [...lastValues], - }, - }); - } - } - setUploading(false); - }; - }, - [changeImage] - ); - const getPostsMarketplace = useCallback(async () => { - return ( - await fetch(`/posts/marketplace/${existingData?.posts?.[0]?.id}`) - ).json(); - }, []); - const { data } = useSWR( - `/posts/marketplace/${existingData?.posts?.[0]?.id}`, - getPostsMarketplace - ); - const canSendForPublication = useMemo(() => { - if (!postFor) { - return true; - } - return selectedIntegrations.every((integration) => { - const find = postFor.missing.find( - (p) => p.integration.integration.id === integration.id - ); - if (!find) { - return false; - } - return find.missing !== 0; - }); - }, [data, postFor, selectedIntegrations]); - useClickOutside(askClose); - return ( - - {user?.tier?.ai && ( - - )} -
- {uploading && ( -
- -
- )} -
-
- -
- - { - setCustomer(val); - setSelectedIntegrations([]); - }} - /> - - - {!selectedIntegrations.length && ( - - - - )} -
-
- - {!existingData.integration && integrations.length > 1 ? ( -
- !f.disabled)} - selectedIntegrations={selectIntegrationsDefault} - singleSelect={false} - onChange={setSelectedIntegrations} - isMain={true} - /> -
- ) : ( -
- {selectedIntegrations?.[0]?.identifier} - {selectedIntegrations?.[0]?.identifier === 'youtube' ? ( - - ) : ( - {selectedIntegrations?.[0]?.identifier} - )} -
- )} -
- {!existingData.integration && !showHide.hideTopEditor ? ( - <> -
- {t( - 'you_are_in_global_editing_mode', - 'You are in global editing mode' - )} -
- {value.map((p, index) => ( - -
-
-
- - 1 ? 150 : 250} - value={p.content} - totalPosts={value.length} - preview="edit" - onPaste={pasteImages(index, p.image || [])} - // @ts-ignore - onChange={changeValue(index)} - /> - - - {showError && - (!p.content || p.content.length < 6) && ( -
- {t( - 'the_post_should_be_at_least_6_characters_long', - 'The post should be at least 6 characters long' - )} -
- )} -
-
- setCanUseClose(false)} - onClose={() => setCanUseClose(true)} - /> -
-
- {value.length > 1 && ( -
-
- - - -
-
- {t('delete_post', 'Delete Post')} -
-
- )} -
-
-
-
- -
-
-
-
- -
-
- ))} - {value.length > 1 && ( -
- -
- )} - - ) : null} -
-
-
-
- - {!!existingData.integration && ( - - )} - - {addEditSets && ( - - )} - - {!addEditSets && ( - - )} - - {!addEditSets && ( - - )} - -
-
-
-
-
-
- - setTags(e.target.value)} - /> - - - - -
- {!!selectedIntegrations.length && ( -
- -
- )} -
-
- - ); -}); diff --git a/apps/frontend/src/components/launches/add.post.button.tsx b/apps/frontend/src/components/launches/add.post.button.tsx deleted file mode 100644 index 18c3e728..00000000 --- a/apps/frontend/src/components/launches/add.post.button.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Button } from '@gitroom/react/form/button'; -import React, { FC } from 'react'; -import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const AddPostButton: FC<{ - onClick: () => void; - num: number; -}> = (props) => { - const { onClick, num } = props; - const t = useT(); - useCopilotAction({ - name: 'addPost_' + num, - description: 'Add a post after the post number ' + num, - handler: () => { - onClick(); - }, - }); - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/ai.image.tsx b/apps/frontend/src/components/launches/ai.image.tsx index 700e0b54..862fc67c 100644 --- a/apps/frontend/src/components/launches/ai.image.tsx +++ b/apps/frontend/src/components/launches/ai.image.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import Loading from 'react-loading'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; const list = [ 'Realistic', 'Cartoon', @@ -27,10 +28,12 @@ export const AiImage: FC<{ const t = useT(); const { value, onChange } = props; const [loading, setLoading] = useState(false); + const setLocked = useLaunchStore(p => p.setLocked); const fetch = useFetch(); const generateImage = useCallback( (type: string) => async () => { setLoading(true); + setLocked(true); const image = await ( await fetch('/media/generate-image-with-prompt', { method: 'POST', @@ -49,6 +52,7 @@ ${type} }) ).json(); setLoading(false); + setLocked(false); onChange(image); }, [value, onChange] diff --git a/apps/frontend/src/components/launches/bold.text.tsx b/apps/frontend/src/components/launches/bold.text.tsx deleted file mode 100644 index 96ad3318..00000000 --- a/apps/frontend/src/components/launches/bold.text.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { FC, useCallback } from 'react'; -import { Editor, Transforms } from 'slate'; -import { ReactEditor } from 'slate-react'; -const originalMap = { - a: '𝗮', - b: '𝗯', - c: '𝗰', - d: '𝗱', - e: '𝗲', - f: '𝗳', - g: '𝗴', - h: '𝗵', - i: '𝗶', - j: '𝗷', - k: '𝗸', - l: '𝗹', - m: '𝗺', - n: '𝗻', - o: '𝗼', - p: '𝗽', - q: '𝗾', - r: '𝗿', - s: '𝘀', - t: '𝘁', - u: '𝘂', - v: '𝘃', - w: '𝘄', - x: '𝘅', - y: '𝘆', - z: '𝘇', - A: '𝗔', - B: '𝗕', - C: '𝗖', - D: '𝗗', - E: '𝗘', - F: '𝗙', - G: '𝗚', - H: '𝗛', - I: '𝗜', - J: '𝗝', - K: '𝗞', - L: '𝗟', - M: '𝗠', - N: '𝗡', - O: '𝗢', - P: '𝗣', - Q: '𝗤', - R: '𝗥', - S: '𝗦', - T: '𝗧', - U: '𝗨', - V: '𝗩', - W: '𝗪', - X: '𝗫', - Y: '𝗬', - Z: '𝗭', - '1': '𝟭', - '2': '𝟮', - '3': '𝟯', - '4': '𝟰', - '5': '𝟱', - '6': '𝟲', - '7': '𝟳', - '8': '𝟴', - '9': '𝟵', - '0': '𝟬', -}; -const reverseMap = Object.fromEntries( - Object.entries(originalMap).map(([key, value]) => [value, key]) -); -export const BoldText: FC<{ - editor: any; - currentValue: string; -}> = ({ editor }) => { - const mark = () => { - const selectedText = Editor.string(editor, editor.selection); - const newText = Array.from( - !selectedText ? prompt('What do you want to write?') || '' : selectedText - ) - .map((char) => { - // @ts-ignore - return originalMap?.[char] || reverseMap?.[char] || char; - }) - .join(''); - Transforms.insertText(editor, newText); - ReactEditor.focus(editor); - }; - return ( -
- - - - - - - - - - -
- ); -}; diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index cb5cf495..c55e617a 100644 --- a/apps/frontend/src/components/launches/calendar.tsx +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -31,7 +31,6 @@ import 'dayjs/locale/tr'; import 'dayjs/locale/vi'; import localizedFormat from 'dayjs/plugin/localizedFormat'; import { useModals } from '@mantine/modals'; -import { AddEditModal as AddEditModal2 } from '@gitroom/frontend/components/launches/add.edit.model'; import clsx from 'clsx'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; diff --git a/apps/frontend/src/components/launches/connect.with.wallet.tsx b/apps/frontend/src/components/launches/connect.with.wallet.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/frontend/src/components/launches/editor.tsx b/apps/frontend/src/components/launches/editor.tsx deleted file mode 100644 index 1e470841..00000000 --- a/apps/frontend/src/components/launches/editor.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { forwardRef, useCallback, useRef, useState } from 'react'; -import type { MDEditorProps } from '@uiw/react-md-editor/src/Types'; -import { RefMDEditor } from '@uiw/react-md-editor/src/Editor'; -import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core'; -import { CopilotTextarea } from '@copilotkit/react-textarea'; -import clsx from 'clsx'; -import { useUser } from '@gitroom/frontend/components/layout/user.context'; -import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; -import { Transforms } from 'slate'; -import EmojiPicker from 'emoji-picker-react'; -import { Theme } from 'emoji-picker-react'; -import { BoldText } from '@gitroom/frontend/components/launches/bold.text'; -import { UText } from '@gitroom/frontend/components/launches/u.text'; -import { SignatureBox } from '@gitroom/frontend/components/signature'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const Editor = forwardRef< - RefMDEditor, - MDEditorProps & { - order: number; - totalPosts: number; - disabledCopilot?: boolean; - } ->( - ( - props: MDEditorProps & { - order: number; - totalPosts: number; - disabledCopilot?: boolean; - }, - ref: React.ForwardedRef - ) => { - const user = useUser(); - const [id] = useState(makeId(10)); - const newRef = useRef(null); - const [emojiPickerOpen, setEmojiPickerOpen] = useState(false); - const t = useT(); - - useCopilotReadable({ - ...(props.disabledCopilot ? { available: 'disabled' } : {}), - description: 'Content of the post number ' + (props.order + 1), - value: JSON.stringify({ - content: props.value, - order: props.order, - allowAddContent: props?.value?.length === 0, - }), - }); - useCopilotAction({ - ...(props.disabledCopilot ? { available: 'disabled' } : {}), - name: 'editPost_' + props.order, - description: `Edit the content of post number ${props.order}`, - parameters: [ - { - name: 'content', - type: 'string', - }, - ], - handler: async ({ content }) => { - props?.onChange?.(content); - }, - }); - const addText = useCallback( - (emoji: string) => { - setTimeout(() => { - // @ts-ignore - Transforms.insertText(newRef?.current?.editor!, emoji); - }, 10); - }, - [props.value, id] - ); - return ( - <> -
- - - -
setEmojiPickerOpen(!emojiPickerOpen)} - > - {t('', '\uD83D\uDE00')} -
-
-
- { - addText(e.emoji); - setEmojiPickerOpen(false); - }} - open={emojiPickerOpen} - /> -
-
- 1 && '!max-h-80' - )} - value={props.value} - onChange={(e) => { - props?.onChange?.(e.target.value); - }} - onPaste={props.onPaste} - placeholder={t('write_your_reply', 'Write your reply...')} - autosuggestionsConfig={{ - textareaPurpose: `Assist me in writing social media posts.`, - chatApiConfigs: {}, - disabled: !user?.tier?.ai, - }} - /> -
- - ); - } -); diff --git a/apps/frontend/src/components/launches/finisher/thread.finisher.tsx b/apps/frontend/src/components/launches/finisher/thread.finisher.tsx deleted file mode 100644 index 736d8291..00000000 --- a/apps/frontend/src/components/launches/finisher/thread.finisher.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Slider } from '@gitroom/react/form/slider'; -import clsx from 'clsx'; -import { useState } from 'react'; -import { Editor } from '@gitroom/frontend/components/launches/editor'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; - -export const ThreadFinisher = () => { - const integration = useIntegration(); - const { register, watch, setValue } = useSettings(); - const t = useT(); - - register('active_thread_finisher', { - value: false, - }); - - register('thread_finisher', { - value: t('that_a_wrap', { - username: - integration.integration?.display || integration.integration?.name, - }), - }); - - const slider = watch('active_thread_finisher'); - const value = watch('thread_finisher'); - - return ( -
-
-
Add a thread finisher
-
- setValue('active_thread_finisher', p === 'on')} - fill={true} - /> -
-
-
-
-
-
-
- setValue('thread_finisher', val)} - value={value} - height={150} - totalPosts={1} - order={1} - preview="edit" - /> -
-
-
-
-
-
- ); -}; diff --git a/apps/frontend/src/components/launches/general.preview.component.tsx b/apps/frontend/src/components/launches/general.preview.component.tsx index 1858f4df..004e193c 100644 --- a/apps/frontend/src/components/launches/general.preview.component.tsx +++ b/apps/frontend/src/components/launches/general.preview.component.tsx @@ -6,10 +6,13 @@ import { VideoOrImage } from '@gitroom/react/helpers/video.or.image'; import { FC } from 'react'; import { textSlicer } from '@gitroom/helpers/utils/count.length'; import interClass from '@gitroom/react/helpers/inter.font'; +import Image from 'next/image'; +import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; export const GeneralPreviewComponent: FC<{ maximumCharacters?: number; }> = (props) => { const { value: topValue, integration } = useIntegration(); + const current = useLaunchStore((state) => state.current); const mediaDir = useMediaDirectory(); const newValues = useFormatting(topValue, { removeMarkdown: true, @@ -41,11 +44,23 @@ export const GeneralPreviewComponent: FC<{ )} >
- x +
+ x + + {current !== 'global' && ( + {integration.identifier} + )} +
{index !== topValue.length - 1 && (
)} @@ -53,7 +68,7 @@ export const GeneralPreviewComponent: FC<{
- {integration?.name} + {current === 'global' ? 'Global Edit' : integration?.name}
- {integration?.display || '@username'} + {current === 'global' ? '' : integration?.display || '@username'}
 {
   const { integrations, reloadCalendarView } = useCalendar();
   const modal = useModals();
diff --git a/apps/frontend/src/components/launches/helpers/linkedin.component.tsx b/apps/frontend/src/components/launches/helpers/linkedin.component.tsx
index 03a0893d..0dc5ecb8 100644
--- a/apps/frontend/src/components/launches/helpers/linkedin.component.tsx
+++ b/apps/frontend/src/components/launches/helpers/linkedin.component.tsx
@@ -15,6 +15,7 @@ import { Input } from '@gitroom/react/form/input';
 import { Button } from '@gitroom/react/form/button';
 import { useToaster } from '@gitroom/react/toaster/toaster';
 import { useT } from '@gitroom/react/translation/get.transation.service.client';
+import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
 const postUrlEmitter = new EventEmitter();
 export const ShowLinkedinCompany = () => {
   const [showPostSelector, setShowPostSelector] = useState(false);
@@ -53,6 +54,37 @@ export const ShowLinkedinCompany = () => {
     
   );
 };
+
+export const LinkedinCompanyPop: FC<{
+  addText: (value: any) => void;
+}> = (props) => {
+  const current = useLaunchStore((state) => state.current);
+  return (
+     {
+        postUrlEmitter.emit('show', {
+          id: current,
+          callback: (value: any) => {
+            props.addText(value);
+          },
+        });
+      }}
+      data-tooltip-id="tooltip"
+      data-tooltip-content="Add a LinkedIn Company"
+      className="mx-[10px] cursor-pointer"
+      width="20"
+      height="20"
+      viewBox="0 0 26 26"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      
+    
+  );
+};
 export const showPostSelector = (id: string) => {
   return new Promise((resolve) => {
     postUrlEmitter.emit('show', {
diff --git a/apps/frontend/src/components/launches/new.post.tsx b/apps/frontend/src/components/launches/new.post.tsx
index f1cb8c8d..efa76eb1 100644
--- a/apps/frontend/src/components/launches/new.post.tsx
+++ b/apps/frontend/src/components/launches/new.post.tsx
@@ -1,12 +1,12 @@
 import React, { useCallback } from 'react';
-import { AddEditModal } from '@gitroom/frontend/components/launches/add.edit.model';
 import { useModals } from '@mantine/modals';
 import dayjs from 'dayjs';
 import { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';
 import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
 import { useT } from '@gitroom/react/translation/get.transation.service.client';
 import { SetSelectionModal } from '@gitroom/frontend/components/launches/calendar';
-import { useSet } from '@gitroom/frontend/components/launches/set.context';
+import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';
+
 export const NewPost = () => {
   const fetch = useFetch();
   const modal = useModals();
diff --git a/apps/frontend/src/components/launches/post.to.organization.tsx b/apps/frontend/src/components/launches/post.to.organization.tsx
deleted file mode 100644
index cda02339..00000000
--- a/apps/frontend/src/components/launches/post.to.organization.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-'use client';
-
-import { FC, useEffect } from 'react';
-import { CustomSelect } from '@gitroom/react/form/custom.select';
-import { FormProvider, useForm } from 'react-hook-form';
-export interface Information {
-  buyer: Buyer;
-  usedIds: Array<{
-    id: string;
-    status: 'NO' | 'WAITING_CONFIRMATION' | 'YES';
-  }>;
-  id: string;
-  missing: Missing[];
-}
-export interface Buyer {
-  id: string;
-  name: string;
-  picture: Picture;
-}
-export interface Picture {
-  id: string;
-  path: string;
-}
-export interface Missing {
-  integration: Integration;
-  missing: number;
-}
-export interface Integration {
-  quantity: number;
-  integration: Integration2;
-}
-export interface Integration2 {
-  id: string;
-  name: string;
-  providerIdentifier: string;
-}
-export const PostToOrganization: FC<{
-  information: Information[];
-  onChange: (order?: Information) => void;
-  selected?: string;
-}> = (props) => {
-  const { information, onChange, selected } = props;
-  const form = useForm();
-  const postFor = form.watch('post_for');
-  useEffect(() => {
-    onChange(information?.find((p) => p.id === postFor?.value)!);
-  }, [postFor]);
-  useEffect(() => {
-    if (!selected || !information?.length) {
-      return;
-    }
-    const findIt = information?.find((p) => p.id === selected);
-    form.setValue('post_for', {
-      value: findIt?.id,
-    });
-    onChange(information?.find((p) => p.id === selected)!);
-  }, [selected, information]);
-  if (!information?.length) {
-    return null;
-  }
-  return (
-    
-       ({
-          label: 'For: ' + p?.buyer?.name,
-          value: p?.id,
-          icon: (
-            
-          ),
-        }))}
-      />
-    
-  );
-};
diff --git a/apps/frontend/src/components/launches/providers.options.tsx b/apps/frontend/src/components/launches/providers.options.tsx
deleted file mode 100644
index 8d4bc337..00000000
--- a/apps/frontend/src/components/launches/providers.options.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { FC, useEffect, useState } from 'react';
-import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
-import { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';
-import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';
-import { ShowAllProviders } from '@gitroom/frontend/components/launches/providers/show.all.providers';
-import dayjs from 'dayjs';
-import { useStateCallback } from '@gitroom/react/helpers/use.state.callback';
-export const ProvidersOptions: FC<{
-  hideEditOnlyThis: boolean;
-  integrations: Integrations[];
-  allIntegrations: Integrations[];
-  editorValue: Array<{
-    id?: string;
-    content: string;
-  }>;
-  date: dayjs.Dayjs;
-}> = (props) => {
-  const { integrations, editorValue, date, hideEditOnlyThis } = props;
-  const [selectedIntegrations, setSelectedIntegrations] = useStateCallback([
-    integrations[0],
-  ]);
-  useEffect(() => {
-    if (integrations.indexOf(selectedIntegrations[0]) === -1) {
-      setSelectedIntegrations([integrations[0]]);
-    }
-  }, [integrations, selectedIntegrations]);
-  return (
-    
- - - - -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx b/apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx deleted file mode 100644 index 5f63ccd5..00000000 --- a/apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { ThreadFinisher } from '@gitroom/frontend/components/launches/finisher/thread.finisher'; - -const SettingsComponent = () => { - return ; -}; - -export default withProvider( - SettingsComponent, - undefined, - undefined, - async (posts) => { - if ( - posts.some( - (p) => p.some((a) => a.path.indexOf('mp4') > -1) && p.length > 1 - ) - ) { - return 'You can only upload one video to Bluesky per post.'; - } - - if (posts.some((p) => p.length > 4)) { - return 'There can be maximum 4 pictures in a post.'; - } - return true; - }, - 300 -); diff --git a/apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx b/apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx deleted file mode 100644 index 97ae18bb..00000000 --- a/apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import useSWR from 'swr'; -import clsx from 'clsx'; -import { Button } from '@gitroom/react/form/button'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const FacebookContinue: FC<{ - closeModal: () => void; - existingId: string[]; -}> = (props) => { - const { closeModal, existingId } = props; - const call = useCustomProviderFunction(); - const { integration } = useIntegration(); - const [page, setSelectedPage] = useState(null); - const fetch = useFetch(); - const loadPages = useCallback(async () => { - try { - const pages = await call.get('pages'); - return pages; - } catch (e) { - closeModal(); - } - }, []); - const setPage = useCallback( - (id: string) => () => { - setSelectedPage(id); - }, - [] - ); - const { data, isLoading } = useSWR('load-pages', loadPages, { - refreshWhenHidden: false, - refreshWhenOffline: false, - revalidateOnFocus: false, - revalidateIfStale: false, - revalidateOnMount: true, - revalidateOnReconnect: false, - refreshInterval: 0, - }); - const t = useT(); - - const saveInstagram = useCallback(async () => { - await fetch(`/integrations/facebook/${integration?.id}`, { - method: 'POST', - body: JSON.stringify({ - page, - }), - }); - closeModal(); - }, [integration, page]); - const filteredData = useMemo(() => { - return ( - data?.filter((p: { id: string }) => !existingId.includes(p.id)) || [] - ); - }, [data]); - if (!isLoading && !data?.length) { - return ( -
- {t( - 'we_couldn_t_find_any_business_connected_to_the_selected_pages', - "We couldn't find any business connected to the selected pages." - )} -
- {t( - 'we_recommend_you_to_connect_all_the_pages_and_all_the_businesses', - 'We recommend you to connect all the pages and all the businesses.' - )} -
- {t( - 'please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again', - 'Please close this dialog, delete your integration and add a new channel\n again.' - )} -
- ); - } - return ( -
-
{t('select_page', 'Select Page:')}
-
- {filteredData?.map( - (p: { - id: string; - username: string; - name: string; - picture: { - data: { - url: string; - }; - }; - }) => ( -
-
- profile -
-
{p.name}
-
- ) - )} -
-
- -
-
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/continue-provider/instagram/instagram.continue.tsx b/apps/frontend/src/components/launches/providers/continue-provider/instagram/instagram.continue.tsx deleted file mode 100644 index ddf6c23f..00000000 --- a/apps/frontend/src/components/launches/providers/continue-provider/instagram/instagram.continue.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import useSWR from 'swr'; -import clsx from 'clsx'; -import { Button } from '@gitroom/react/form/button'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const InstagramContinue: FC<{ - closeModal: () => void; - existingId: string[]; -}> = (props) => { - const { closeModal, existingId } = props; - const call = useCustomProviderFunction(); - const { integration } = useIntegration(); - const [page, setSelectedPage] = useState(null); - const fetch = useFetch(); - const loadPages = useCallback(async () => { - try { - const pages = await call.get('pages'); - return pages; - } catch (e) { - closeModal(); - } - }, []); - const t = useT(); - - const setPage = useCallback( - (param: { id: string; pageId: string }) => () => { - setSelectedPage(param); - }, - [] - ); - const { data, isLoading } = useSWR('load-pages', loadPages, { - refreshWhenHidden: false, - refreshWhenOffline: false, - revalidateOnFocus: false, - revalidateIfStale: false, - revalidateOnMount: true, - revalidateOnReconnect: false, - refreshInterval: 0, - }); - const saveInstagram = useCallback(async () => { - await fetch(`/integrations/instagram/${integration?.id}`, { - method: 'POST', - body: JSON.stringify(page), - }); - closeModal(); - }, [integration, page]); - const filteredData = useMemo(() => { - return ( - data?.filter((p: { id: string }) => !existingId.includes(p.id)) || [] - ); - }, [data]); - if (!isLoading && !data?.length) { - return ( -
- {t( - 'we_couldn_t_find_any_business_connected_to_the_selected_pages', - "We couldn't find any business connected to the selected pages." - )} -
- {t( - 'we_recommend_you_to_connect_all_the_pages_and_all_the_businesses', - 'We recommend you to connect all the pages and all the businesses.' - )} -
- {t( - 'please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again', - 'Please close this dialog, delete your integration and add a new channel\n again.' - )} -
- ); - } - return ( -
-
{t('select_instagram_account', 'Select Instagram Account:')}
-
- {filteredData?.map( - (p: { - id: string; - pageId: string; - username: string; - name: string; - picture: { - data: { - url: string; - }; - }; - }) => ( -
-
- profile -
-
{p.name}
-
- ) - )} -
-
- -
-
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/continue-provider/linkedin/linkedin.continue.tsx b/apps/frontend/src/components/launches/providers/continue-provider/linkedin/linkedin.continue.tsx deleted file mode 100644 index 594fceb4..00000000 --- a/apps/frontend/src/components/launches/providers/continue-provider/linkedin/linkedin.continue.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import useSWR from 'swr'; -import clsx from 'clsx'; -import { Button } from '@gitroom/react/form/button'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const LinkedinContinue: FC<{ - closeModal: () => void; - existingId: string[]; -}> = (props) => { - const { closeModal, existingId } = props; - const t = useT(); - - const call = useCustomProviderFunction(); - const { integration } = useIntegration(); - const [page, setSelectedPage] = useState(null); - const fetch = useFetch(); - const loadPages = useCallback(async () => { - try { - const pages = await call.get('companies'); - return pages; - } catch (e) { - closeModal(); - } - }, []); - const setPage = useCallback( - (param: { id: string; pageId: string }) => () => { - setSelectedPage(param); - }, - [] - ); - const { data, isLoading } = useSWR('load-pages', loadPages, { - refreshWhenHidden: false, - refreshWhenOffline: false, - revalidateOnFocus: false, - revalidateIfStale: false, - revalidateOnMount: true, - revalidateOnReconnect: false, - refreshInterval: 0, - }); - const saveLinkedin = useCallback(async () => { - await fetch(`/integrations/linkedin-page/${integration?.id}`, { - method: 'POST', - body: JSON.stringify(page), - }); - closeModal(); - }, [integration, page]); - const filteredData = useMemo(() => { - return ( - data?.filter((p: { id: string }) => !existingId.includes(p.id)) || [] - ); - }, [data]); - if (!isLoading && !data?.length) { - return ( -
- {t( - 'we_couldn_t_find_any_business_connected_to_your_linkedin_page', - "We couldn't find any business connected to your LinkedIn Page." - )} -
- {t( - 'please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again', - 'Please close this dialog, create a new page, and add a new channel again.' - )} -
- ); - } - return ( -
-
{t('select_linkedin_page', 'Select Linkedin Page:')}
-
- {filteredData?.map( - (p: { - id: string; - pageId: string; - username: string; - name: string; - picture: string; - }) => ( -
-
- profile -
-
{p.name}
-
- ) - )} -
-
- -
-
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/continue-provider/list.tsx b/apps/frontend/src/components/launches/providers/continue-provider/list.tsx deleted file mode 100644 index b621aae1..00000000 --- a/apps/frontend/src/components/launches/providers/continue-provider/list.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { InstagramContinue } from '@gitroom/frontend/components/launches/providers/continue-provider/instagram/instagram.continue'; -import { FacebookContinue } from '@gitroom/frontend/components/launches/providers/continue-provider/facebook/facebook.continue'; -import { LinkedinContinue } from '@gitroom/frontend/components/launches/providers/continue-provider/linkedin/linkedin.continue'; -export const continueProviderList = { - instagram: InstagramContinue, - facebook: FacebookContinue, - 'linkedin-page': LinkedinContinue, -}; diff --git a/apps/frontend/src/components/launches/providers/devto/devto.provider.tsx b/apps/frontend/src/components/launches/providers/devto/devto.provider.tsx deleted file mode 100644 index d457517e..00000000 --- a/apps/frontend/src/components/launches/providers/devto/devto.provider.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Input } from '@gitroom/react/form/input'; -import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { SelectOrganization } from '@gitroom/frontend/components/launches/providers/devto/select.organization'; -import { DevtoTags } from '@gitroom/frontend/components/launches/providers/devto/devto.tags'; -import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; -import clsx from 'clsx'; -import localFont from 'next/font/local'; -import MDEditor from '@uiw/react-md-editor'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { Canonical } from '@gitroom/react/form/canonical'; -const font = localFont({ - src: [ - { - path: './fonts/SFNS.woff2', - }, - ], -}); -const DevtoPreview: FC = () => { - const { value } = useIntegration(); - const settings = useSettings(); - const image = useMediaDirectory(); - const [coverPicture, title, tags] = settings.watch([ - 'main_image', - 'title', - 'tags', - ]); - return ( -
- {!!coverPicture?.path && ( -
- cover_picture -
- )} -
-
{title}
-
- {tags?.map((p: any) => ( -
#{p.label}
- ))} -
-
-
- p.content).join('\n')} - /> -
-
- ); -}; -const DevtoSettings: FC = () => { - const form = useSettings(); - const { date } = useIntegration(); - return ( - <> - - - -
- -
-
- -
- - ); -}; -export default withProvider(DevtoSettings, DevtoPreview, DevToSettingsDto); diff --git a/apps/frontend/src/components/launches/providers/devto/devto.tags.tsx b/apps/frontend/src/components/launches/providers/devto/devto.tags.tsx deleted file mode 100644 index 32ed4469..00000000 --- a/apps/frontend/src/components/launches/providers/devto/devto.tags.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { FC, useCallback, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -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 DevtoTags: FC<{ - name: string; - label: string; - onChange: (event: { - target: { - value: any[]; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name, label } = props; - const customFunc = useCustomProviderFunction(); - const [tags, setTags] = useState([]); - const { getValues } = useSettings(); - const [tagValue, setTagValue] = useState([]); - const onDelete = useCallback( - (tagIndex: number) => { - const modify = tagValue.filter((_, i) => i !== tagIndex); - setTagValue(modify); - onChange({ - target: { - value: modify, - name, - }, - }); - }, - [tagValue] - ); - const onAddition = useCallback( - (newTag: any) => { - if (tagValue.length >= 4) { - return; - } - const modify = [...tagValue, newTag]; - setTagValue(modify); - onChange({ - target: { - value: modify, - name, - }, - }); - }, - [tagValue] - ); - useEffect(() => { - customFunc.get('tags').then((data) => setTags(data)); - const settings = getValues()[props.name]; - if (settings) { - setTagValue(settings); - } - }, []); - if (!tags.length) { - return null; - } - return ( -
-
{label}
- -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/devto/fonts/SFNS.woff2 b/apps/frontend/src/components/launches/providers/devto/fonts/SFNS.woff2 deleted file mode 100644 index c9d0228f..00000000 Binary files a/apps/frontend/src/components/launches/providers/devto/fonts/SFNS.woff2 and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/devto/select.organization.tsx b/apps/frontend/src/components/launches/providers/devto/select.organization.tsx deleted file mode 100644 index d1aa52e8..00000000 --- a/apps/frontend/src/components/launches/providers/devto/select.organization.tsx +++ /dev/null @@ -1,55 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const SelectOrganization: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - 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('organizations').then((data) => setOrgs(data)); - const settings = getValues()[props.name]; - if (settings) { - setCurrentMedia(settings); - } - }, []); - if (!orgs.length) { - return null; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/discord/discord.channel.select.tsx b/apps/frontend/src/components/launches/providers/discord/discord.channel.select.tsx deleted file mode 100644 index 51825ca8..00000000 --- a/apps/frontend/src/components/launches/providers/discord/discord.channel.select.tsx +++ /dev/null @@ -1,55 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const DiscordChannelSelect: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - const customFunc = useCustomProviderFunction(); - const [publications, 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('channels').then((data) => setOrgs(data)); - const settings = getValues()[props.name]; - if (settings) { - setCurrentMedia(settings); - } - }, []); - if (!publications.length) { - return null; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/discord/discord.provider.tsx b/apps/frontend/src/components/launches/providers/discord/discord.provider.tsx deleted file mode 100644 index 4b2eaf4f..00000000 --- a/apps/frontend/src/components/launches/providers/discord/discord.provider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { FC } from 'react'; -import { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto'; -import { DiscordChannelSelect } from '@gitroom/frontend/components/launches/providers/discord/discord.channel.select'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -const DiscordComponent: FC = () => { - const form = useSettings(); - return ( -
- -
- ); -}; -export default withProvider( - DiscordComponent, - undefined, - DiscordDto, - undefined, - 1980 -); diff --git a/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx b/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx deleted file mode 100644 index 09d4d094..00000000 --- a/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -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 ( -
- - -
- ); -}; -export default withProvider( - DribbbleSettings, - undefined, - 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 deleted file mode 100644 index da54e464..00000000 --- a/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx +++ /dev/null @@ -1,58 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const DribbbleTeams: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - 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/facebook/facebook.provider.tsx b/apps/frontend/src/components/launches/providers/facebook/facebook.provider.tsx deleted file mode 100644 index 8372a93f..00000000 --- a/apps/frontend/src/components/launches/providers/facebook/facebook.provider.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -export default withProvider(null, undefined, undefined, undefined, 63206); diff --git a/apps/frontend/src/components/launches/providers/hashnode/hashnode.provider.tsx b/apps/frontend/src/components/launches/providers/hashnode/hashnode.provider.tsx deleted file mode 100644 index 216fb34c..00000000 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.provider.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Input } from '@gitroom/react/form/input'; -import { HashnodePublications } from '@gitroom/frontend/components/launches/providers/hashnode/hashnode.publications'; -import { HashnodeTags } from '@gitroom/frontend/components/launches/providers/hashnode/hashnode.tags'; -import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; -import clsx from 'clsx'; -import MDEditor from '@uiw/react-md-editor'; -import { Plus_Jakarta_Sans } from 'next/font/google'; -import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { Canonical } from '@gitroom/react/form/canonical'; -const font = Plus_Jakarta_Sans({ - subsets: ['latin'], -}); -const HashnodePreview: FC = () => { - const { value } = useIntegration(); - const settings = useSettings(); - const image = useMediaDirectory(); - const [coverPicture, title, subtitle] = settings.watch([ - 'main_image', - 'title', - 'subtitle', - ]); - return ( -
- {!!coverPicture?.path && ( -
- cover_picture -
- )} -
-
- {title} -
-
- {subtitle} -
-
-
- p.content).join('\n')} - /> -
-
- ); -}; -const HashnodeSettings: FC = () => { - const form = useSettings(); - const { date } = useIntegration(); - return ( - <> - - - - -
- -
-
- -
- - ); -}; -export default withProvider( - HashnodeSettings, - HashnodePreview, - HashnodeSettingsDto -); diff --git a/apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx b/apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx deleted file mode 100644 index fdaf25ef..00000000 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx +++ /dev/null @@ -1,55 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const HashnodePublications: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - const customFunc = useCustomProviderFunction(); - const [publications, 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('publications').then((data) => setOrgs(data)); - const settings = getValues()[props.name]; - if (settings) { - setCurrentMedia(settings); - } - }, []); - if (!publications.length) { - return null; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx b/apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx deleted file mode 100644 index 32d22a52..00000000 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -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 HashnodeTags: FC<{ - name: string; - label: string; - onChange: (event: { - target: { - value: any[]; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name, label } = props; - const customFunc = useCustomProviderFunction(); - const [tags, setTags] = useState([]); - const { getValues, formState: form } = useSettings(); - const [tagValue, setTagValue] = useState([]); - const onDelete = useCallback( - (tagIndex: number) => { - const modify = tagValue.filter((_, i) => i !== tagIndex); - setTagValue(modify); - onChange({ - target: { - value: modify, - name, - }, - }); - }, - [tagValue] - ); - const onAddition = useCallback( - (newTag: any) => { - if (tagValue.length >= 4) { - return; - } - const modify = [...tagValue, newTag]; - setTagValue(modify); - onChange({ - target: { - value: modify, - name, - }, - }); - }, - [tagValue] - ); - useEffect(() => { - customFunc.get('tags').then((data) => setTags(data)); - const settings = getValues()[props.name]; - if (settings) { - setTagValue(settings); - } - }, []); - const err = useMemo(() => { - if (!form || !form.errors[props?.name!]) return; - return form?.errors?.[props?.name!]?.message! as string; - }, [form?.errors?.[props?.name!]?.message]); - if (!tags.length) { - return null; - } - return ( -
-
{label}
- -
{err || <> }
-
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx deleted file mode 100644 index 83647bc2..00000000 --- a/apps/frontend/src/components/launches/providers/high.order.provider.tsx +++ /dev/null @@ -1,707 +0,0 @@ -'use client'; - -import React, { - FC, - Fragment, - ReactNode, - useCallback, - useEffect, - useMemo, - useState, - ClipboardEvent, - memo, -} from 'react'; -import { Button } from '@gitroom/react/form/button'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import { useHideTopEditor } from '@gitroom/frontend/components/launches/helpers/use.hide.top.editor'; -import { useValues } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { FormProvider } from 'react-hook-form'; -import { useMoveToIntegrationListener } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration'; -import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; -import { - IntegrationContext, - useIntegration, -} from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { createPortal } from 'react-dom'; -import clsx from 'clsx'; -import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector'; -import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow'; -import { arrayMoveImmutable } from 'array-move'; -import { - LinkedinCompany, - linkedinCompany, -} from '@gitroom/frontend/components/launches/helpers/linkedin.component'; -import { Editor } from '@gitroom/frontend/components/launches/editor'; -import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core'; -import { AddPostButton } from '@gitroom/frontend/components/launches/add.post.button'; -import { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component'; -import { capitalize } from 'lodash'; -import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader'; -import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; -import { DropFiles } from '@gitroom/frontend/components/layout/drop.files'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import useSWR from 'swr'; -import { InternalChannels } from '@gitroom/frontend/components/launches/internal.channels'; -import { MergePost } from '@gitroom/frontend/components/launches/merge.post'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { useSet } from '@gitroom/frontend/components/launches/set.context'; -import { SeparatePost } from '@gitroom/frontend/components/launches/separate.post'; -import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; - -// Simple component to change back to settings on after changing tab -export const SetTab: FC<{ - changeTab: () => void; -}> = (props) => { - useEffect(() => { - return () => { - setTimeout(() => { - props.changeTab(); - }, 500); - }; - }, []); - return null; -}; - -// This is a simple function that if we edit in place, we hide the editor on top -export const EditorWrapper: FC<{ - children: ReactNode; -}> = ({ children }) => { - const showHide = useHideTopEditor(); - const [showEditor, setShowEditor] = useState(false); - useEffect(() => { - setShowEditor(true); - showHide.hide(); - return () => { - showHide.show(); - setShowEditor(false); - }; - }, []); - if (!showEditor) { - return null; - } - return children; -}; -export const withProvider = function ( - SettingsComponent: FC<{ - values?: any; - }> | null, - CustomPreviewComponent?: FC<{ - maximumCharacters?: number; - }>, - dto?: any, - checkValidity?: ( - value: Array< - Array<{ - path: string; - }> - >, - settings: T, - additionalSettings: any, - ) => Promise, - maximumCharacters?: number | ((settings: any) => number) -) { - return (props: { - identifier: string; - id: string; - value: Array<{ - content: string; - id?: string; - image?: Array<{ - path: string; - id: string; - }>; - }>; - hideMenu?: boolean; - show: boolean; - hideEditOnlyThis?: boolean; - }) => { - const existingData = useExistingData(); - const t = useT(); - const { allIntegrations, integration, date } = useIntegration(); - const [showLinkedinPopUp, setShowLinkedinPopUp] = useState(false); - const [uploading, setUploading] = useState(false); - const [showComponent, setShowComponent] = useState(false); - const fetch = useFetch(); - useEffect(() => { - setTimeout(() => { - setShowComponent(true); - }, 1); - }, []); - useCopilotReadable({ - description: - integration?.type === 'social' - ? 'force content always in MD format' - : 'force content always to be fit to social media', - value: '', - }); - const [editInPlace, setEditInPlace] = useState(!!existingData.integration); - const [InPlaceValue, setInPlaceValue] = useState< - Array<{ - id?: string; - content: string; - image?: Array<{ - id: string; - path: string; - }>; - }> - >( - // @ts-ignore - existingData.integration - ? existingData.posts.map((p) => ({ - id: p.id, - content: p.content, - image: p.image, - })) - : [ - { - content: '', - }, - ] - ); - const [showTab, setShowTab] = useState(0); - const Component = useMemo(() => { - return SettingsComponent ? SettingsComponent : () => <>; - }, [SettingsComponent]); - - // in case there is an error on submit, we change to the settings tab for the specific provider - useMoveToIntegrationListener( - [props.id], - true, - ({ identifier, toPreview }) => { - if (identifier === props.id) { - setShowTab(toPreview ? 1 : 2); - } - } - ); - - const set = useSet(); - - // this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation - const form = useValues( - set?.set - ? set?.set?.posts?.find((p) => p?.integration?.id === props?.id) - ?.settings - : existingData.settings, - props.id, - props.identifier, - editInPlace ? InPlaceValue : props.value, - dto, - checkValidity, - !maximumCharacters - ? undefined - : typeof maximumCharacters === 'number' - ? maximumCharacters - : maximumCharacters(JSON.parse(integration?.additionalSettings || '[]')) - ); - - // change editor value - const changeValue = useCallback( - (index: number) => (newValue: string) => { - return setInPlaceValue((prev) => { - prev[index].content = newValue; - return [...prev]; - }); - }, - [InPlaceValue] - ); - - const merge = useCallback(() => { - setInPlaceValue( - InPlaceValue.reduce( - (all, current) => { - all[0].content = all[0].content + current.content + '\n'; - all[0].image = [...all[0].image, ...(current.image || [])]; - return all; - }, - [ - { - content: '', - id: InPlaceValue[0].id, - image: [] as { - id: string; - path: string; - }[], - }, - ] - ) - ); - }, [InPlaceValue]); - - const separatePosts = useCallback( - (posts: string[]) => { - setInPlaceValue( - posts.map((p, i) => ({ - content: p, - id: InPlaceValue?.[i]?.id || makeId(10), - image: InPlaceValue?.[i]?.image || [], - })) - ); - }, - [InPlaceValue] - ); - - const changeImage = useCallback( - (index: number) => - (newValue: { - target: { - name: string; - value?: Array<{ - id: string; - path: string; - }>; - }; - }) => { - return setInPlaceValue((prev) => { - prev[index].image = newValue.target.value; - return [...prev]; - }); - }, - [InPlaceValue] - ); - - // add another local editor - const addValue = useCallback( - (index: number) => () => { - setInPlaceValue((prev) => { - return prev.reduce( - (acc, p, i) => { - acc.push(p); - if (i === index) { - acc.push({ - content: '', - }); - } - return acc; - }, - [] as Array<{ - content: string; - }> - ); - }); - }, - [] - ); - const changePosition = useCallback( - (index: number) => (type: 'up' | 'down') => { - if (type === 'up' && index !== 0) { - setInPlaceValue((prev) => { - return arrayMoveImmutable(prev, index, index - 1); - }); - } else if (type === 'down') { - setInPlaceValue((prev) => { - return arrayMoveImmutable(prev, index, index + 1); - }); - } - }, - [] - ); - - // Delete post - const deletePost = useCallback( - (index: number) => async () => { - if ( - !(await deleteDialog( - 'Are you sure you want to delete this post?', - 'Yes, delete it!' - )) - ) { - return; - } - setInPlaceValue((prev) => { - prev.splice(index, 1); - return [...prev]; - }); - }, - [InPlaceValue] - ); - - // This is a function if we want to switch from the global editor to edit in place - const changeToEditor = useCallback(async () => { - if ( - !(await deleteDialog( - !editInPlace - ? 'Are you sure you want to edit only this?' - : 'Are you sure you want to revert it back to global editing?', - 'Yes, edit in place!' - )) - ) { - return false; - } - setEditInPlace(!editInPlace); - setInPlaceValue( - editInPlace - ? [ - { - content: '', - }, - ] - : props.value.map((p) => ({ - id: p.id, - content: p.content, - image: p.image, - })) - ); - }, [props.value, editInPlace]); - useCopilotAction({ - name: editInPlace - ? 'switchToGlobalEdit' - : `editInPlace_${integration?.identifier}`, - description: editInPlace - ? 'Switch to global editing' - : `Edit only ${integration?.identifier} this, if you want a different identifier, you have to use setSelectedIntegration first`, - handler: async () => { - await changeToEditor(); - }, - }); - const tagPersonOrCompany = useCallback( - (integration: string, editor: (value: string) => void) => () => { - setShowLinkedinPopUp( - { - editor(tag); - }} - id={integration} - onClose={() => setShowLinkedinPopUp(false)} - /> - ); - }, - [] - ); - const uppy = useUppyUploader({ - onUploadSuccess: () => { - /**empty**/ - }, - allowedFileTypes: 'image/*,video/mp4', - }); - const pasteImages = useCallback( - (index: number, currentValue: any[], isFile?: boolean) => { - return async (event: ClipboardEvent | File[]) => { - // @ts-ignore - const clipboardItems = isFile - ? // @ts-ignore - event.map((p) => ({ - kind: 'file', - getAsFile: () => p, - })) - : // @ts-ignore - event.clipboardData?.items; // Ensure clipboardData is available - if (!clipboardItems) { - return; - } - const files: File[] = []; - - // @ts-ignore - for (const item of clipboardItems) { - if (item.kind === 'file') { - const file = item.getAsFile(); - if (file) { - const isImage = file.type.startsWith('image/'); - const isVideo = file.type.startsWith('video/'); - if (isImage || isVideo) { - files.push(file); // Collect images or videos - } - } - } - } - if (files.length === 0) { - return; - } - setUploading(true); - const lastValues = [...currentValue]; - for (const file of files) { - uppy.addFile(file); - const upload = await uppy.upload(); - uppy.clear(); - if (upload?.successful?.length) { - lastValues.push(upload?.successful[0]?.response?.body?.saved!); - changeImage(index)({ - target: { - name: 'image', - value: [...lastValues], - }, - }); - } - } - setUploading(false); - }; - }, - [changeImage] - ); - const getInternalPlugs = useCallback(async () => { - return ( - await fetch(`/integrations/${props.identifier}/internal-plugs`) - ).json(); - }, [props.identifier]); - const { data, isLoading } = useSWR( - `internal-${props.identifier}`, - getInternalPlugs, - { - revalidateOnReconnect: true, - } - ); - - // this is a trick to prevent the data from being deleted, yet we don't render the elements - if (!props.show || !showComponent || isLoading) { - return null; - } - return ( - - setShowTab(0)} /> - {showLinkedinPopUp ? showLinkedinPopUp : null} -
- {!props.hideMenu && ( -
-
- -
- {(!!SettingsComponent || !!data?.internalPlugs?.length) && ( -
- -
- )} - {!existingData.integration && !props.hideEditOnlyThis && ( -
- -
- )} -
- )} - {editInPlace && - createPortal( - - {uploading && ( -
- -
- )} -
- {!existingData?.integration && ( -
- {t( - 'you_are_now_editing_only', - 'You are now editing only' - )} - {integration?.name} ( - {capitalize(integration?.identifier.replace('-', ' '))}) -
- )} - {InPlaceValue.map((val, index) => ( - -
-
-
- {(integration?.identifier === 'linkedin' || - integration?.identifier === 'linkedin-page') && ( - - )} - - 1 ? 200 : 250} - value={val.content} - totalPosts={InPlaceValue.length} - commands={[ - // ...commands - // .getCommands() - // .filter((f) => f.name !== 'image'), - // newImage, - postSelector(date), - ...linkedinCompany( - integration?.identifier!, - integration?.id! - ), - ]} - preview="edit" - onPaste={pasteImages(index, val.image || [])} - // @ts-ignore - onChange={changeValue(index)} - /> - - {(!val.content || val.content.length < 6) && ( -
- {t( - 'the_post_should_be_at_least_6_characters_long', - 'The post should be at least 6 characters long' - )} -
- )} -
-
- -
-
- {InPlaceValue.length > 1 && ( -
-
- - - -
-
- {t('delete_post', 'Delete Post')} -
-
- )} -
-
-
-
- -
-
-
-
- -
-
- ))} -
- {InPlaceValue.length > 1 && ( -
- -
- )} -
- p.content)} - len={ - typeof maximumCharacters === 'number' - ? maximumCharacters - : 10000 - } - merge={separatePosts} - /> -
-
-
-
, - document.querySelector('#renderEditor')! - )} - {(showTab === 0 || showTab === 2) && ( -
- - {!!data?.internalPlugs?.length && ( - - )} -
- )} - {showTab === 0 && ( -
- - {(editInPlace ? InPlaceValue : props.value) - .map((p) => p.content) - .join('').length ? ( - CustomPreviewComponent ? ( - - ) : ( - - ) - ) : ( - <>{t('no_content_yet', 'No Content Yet')} - )} - -
- )} -
-
- ); - }; -}; diff --git a/apps/frontend/src/components/launches/providers/instagram/instagram.collaborators.tsx b/apps/frontend/src/components/launches/providers/instagram/instagram.collaborators.tsx deleted file mode 100644 index 4303108a..00000000 --- a/apps/frontend/src/components/launches/providers/instagram/instagram.collaborators.tsx +++ /dev/null @@ -1,88 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -const postType = [ - { - value: 'post', - label: 'Post / Reel', - }, - { - value: 'story', - label: 'Story', - }, -]; -const InstagramCollaborators: FC<{ - values?: any; -}> = (props) => { - const t = useT(); - const { watch, register, formState, control } = useSettings(); - const postCurrentType = watch('post_type'); - return ( - <> - - - {postCurrentType !== 'story' && ( - - )} - - ); -}; -export default withProvider( - 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) => { - return new Promise((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 > 180 && settings.post_type === 'post') { - return 'Instagram reel should be maximum 180 seconds'; - } - } - return true; - }, - 2200 -); diff --git a/apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx b/apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx deleted file mode 100644 index a219bc59..00000000 --- a/apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx +++ /dev/null @@ -1,100 +0,0 @@ -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'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import clsx from 'clsx'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; - -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 { integration } = useIntegration(); - const [tagValue, setTagValue] = useState([]); - const [suggestions, setSuggestions] = useState(''); - const t = useT(); - - const onDelete = useCallback( - (tagIndex: number) => { - const modify = tagValue.filter((_, i) => i !== tagIndex); - 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] - ); - useEffect(() => { - const settings = getValues()[props.name]; - if (settings) { - setTagValue(settings); - } - }, []); - const suggestionsArray = useMemo(() => { - return [ - ...tagValue, - { - label: suggestions, - value: suggestions, - }, - ].filter((f) => f.label); - }, [suggestions, tagValue]); - return ( -
-
-
- {label} -
- -
-
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx b/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx deleted file mode 100644 index b2f6ac09..00000000 --- a/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { FC, useCallback } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { useFieldArray } from 'react-hook-form'; -import { Button } from '@gitroom/react/form/button'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import { Subreddit } from './subreddit'; -import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -const LemmySettings: FC = () => { - const { register, control } = useSettings(); - const { fields, append, remove } = useFieldArray({ - control, - // control props comes from useForm (optional: if you are using FormContext) - name: 'subreddit', // unique name for your Field Array - }); - const t = useT(); - - const addField = useCallback(() => { - append({}); - }, [fields, append]); - const deleteField = useCallback( - (index: number) => async () => { - if ( - !(await deleteDialog( - t( - 'are_you_sure_you_want_to_delete_this_subreddit', - 'Are you sure you want to delete this Subreddit?' - ) - )) - ) - return; - remove(index); - }, - [fields, remove] - ); - return ( - <> -
- {fields.map((field, index) => ( -
-
- x -
- -
- ))} -
- - {fields.length === 0 && ( -
- {t( - 'please_add_at_least_one_subreddit', - 'Please add at least one Subreddit' - )} -
- )} - - ); -}; -export default withProvider( - LemmySettings, - undefined, - LemmySettingsDto, - async (items) => { - const [firstItems] = items; - if ( - firstItems.length && - firstItems[0].path.indexOf('png') === -1 && - firstItems[0].path.indexOf('jpg') === -1 && - firstItems[0].path.indexOf('jpef') === -1 && - firstItems[0].path.indexOf('gif') === -1 - ) { - return 'You can set only one picture for a cover'; - } - return true; - }, - 10000 -); diff --git a/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx b/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx deleted file mode 100644 index d050ef72..00000000 --- a/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { FC, FormEvent, useCallback, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import { Input } from '@gitroom/react/form/input'; -import { useDebouncedCallback } from 'use-debounce'; -import { useWatch } from 'react-hook-form'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -export const Subreddit: FC<{ - onChange: (event: { - target: { - name: string; - value: { - id: string; - subreddit: string; - title: string; - name: string; - url: string; - body: string; - media: any[]; - }; - }; - }) => void; - name: string; -}> = (props) => { - const { onChange, name } = props; - const state = useSettings(); - const split = name.split('.'); - const [loading, setLoading] = useState(false); - // @ts-ignore - const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value; - const [results, setResults] = useState([]); - const func = useCustomProviderFunction(); - const value = useWatch({ - name, - }); - const [searchValue, setSearchValue] = useState(''); - const setResult = (result: { id: string; name: string }) => async () => { - setLoading(true); - setSearchValue(''); - onChange({ - target: { - name, - value: { - id: String(result.id), - subreddit: result.name, - title: '', - name: '', - url: '', - body: '', - media: [], - }, - }, - }); - setLoading(false); - }; - const setTitle = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - title: e.target.value, - }, - }, - }); - }, - [value] - ); - const setURL = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - url: e.target.value, - }, - }, - }); - }, - [value] - ); - const search = useDebouncedCallback( - useCallback(async (e: FormEvent) => { - // @ts-ignore - setResults([]); - // @ts-ignore - if (!e.target.value) { - return; - } - // @ts-ignore - const results = await func.get('subreddits', { word: e.target.value }); - // @ts-ignore - setResults(results); - }, []), - 500 - ); - return ( -
- {value?.subreddit ? ( - <> - - - - - ) : ( -
- { - // @ts-ignore - setSearchValue(e.target.value); - await search(e); - }} - /> - {!!results.length && !loading && ( -
- {results.map((r: { id: string; name: string }) => ( -
- {r.name} -
- ))} -
- )} -
- )} -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx b/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx deleted file mode 100644 index 39a42e59..00000000 --- a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { Checkbox } from '@gitroom/react/form/checkbox'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto'; - -const LinkedInSettings = () => { - const t = useT(); - const { watch, register, formState, control } = useSettings(); - - return ( -
- -
- ); -}; -export default withProvider( - LinkedInSettings, - undefined, - LinkedinDto, - async (posts, vals) => { - const [firstPost, ...restPosts] = posts; - - if ( - vals.post_as_images_carousel && - (firstPost.length < 2 || - firstPost.some((p) => p.path.indexOf('mp4') > -1)) - ) { - return 'LinkedIn carousel can only be created with 2 or more images and no videos.'; - } - - if ( - firstPost.length > 1 && - firstPost.some((p) => p.path.indexOf('mp4') > -1) - ) { - return 'LinkedIn can have maximum 1 media when selecting a video.'; - } - if (restPosts.some((p) => p.length > 0)) { - return 'LinkedIn comments can only contain text.'; - } - return true; - }, - 3000 -); diff --git a/apps/frontend/src/components/launches/providers/mastodon/mastodon.provider.tsx b/apps/frontend/src/components/launches/providers/mastodon/mastodon.provider.tsx deleted file mode 100644 index 538c72c9..00000000 --- a/apps/frontend/src/components/launches/providers/mastodon/mastodon.provider.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -export default withProvider(null, undefined, undefined, undefined, 500); diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold Italic.ttf b/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold Italic.ttf deleted file mode 100755 index 8bf24218..00000000 Binary files a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold Italic.ttf and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold.ttf b/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold.ttf deleted file mode 100755 index c8f7e857..00000000 Binary files a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Bold.ttf and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Italic.ttf b/apps/frontend/src/components/launches/providers/medium/fonts/Charter Italic.ttf deleted file mode 100755 index a6501214..00000000 Binary files a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Italic.ttf and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Regular.ttf b/apps/frontend/src/components/launches/providers/medium/fonts/Charter Regular.ttf deleted file mode 100755 index 33c6d7cb..00000000 Binary files a/apps/frontend/src/components/launches/providers/medium/fonts/Charter Regular.ttf and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/stylesheet.css b/apps/frontend/src/components/launches/providers/medium/fonts/stylesheet.css deleted file mode 100755 index 2c0106c2..00000000 --- a/apps/frontend/src/components/launches/providers/medium/fonts/stylesheet.css +++ /dev/null @@ -1,37 +0,0 @@ -/* Generated by Font Squirrel (http://www.fontsquirrel.com) on July 10, 2013 */ - -@font-face { - font-family: 'charterbold_italic'; - src: url('charter_bold_italic-webfont.eot'); - src: url('charter_bold_italic-webfont.eot?#iefix') format('embedded-opentype'), - url('charter_bold_italic-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'charterbold'; - src: url('charter_bold-webfont.eot'); - src: url('charter_bold-webfont.eot?#iefix') format('embedded-opentype'), - url('charter_bold-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'charteritalic'; - src: url('charter_italic-webfont.eot'); - src: url('charter_italic-webfont.eot?#iefix') format('embedded-opentype'), - url('charter_italic-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'charterregular'; - src: url('charter_regular-webfont.eot'); - src: url('charter_regular-webfont.eot?#iefix') format('embedded-opentype'), - url('charter_regular-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} diff --git a/apps/frontend/src/components/launches/providers/medium/medium.provider.tsx b/apps/frontend/src/components/launches/providers/medium/medium.provider.tsx deleted file mode 100644 index bc085e91..00000000 --- a/apps/frontend/src/components/launches/providers/medium/medium.provider.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Input } from '@gitroom/react/form/input'; -import { MediumPublications } from '@gitroom/frontend/components/launches/providers/medium/medium.publications'; -import { MediumTags } from '@gitroom/frontend/components/launches/providers/medium/medium.tags'; -import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import clsx from 'clsx'; -import MDEditor from '@uiw/react-md-editor'; -import localFont from 'next/font/local'; -import { Canonical } from '@gitroom/react/form/canonical'; -import interClass from '@gitroom/react/helpers/inter.font'; -const charter = localFont({ - src: [ - { - path: './fonts/Charter Regular.ttf', - weight: 'normal', - style: 'normal', - }, - { - path: './fonts/Charter Italic.ttf', - weight: 'normal', - style: 'italic', - }, - { - path: './fonts/Charter Bold.ttf', - weight: '700', - style: 'normal', - }, - { - path: './fonts/Charter Bold Italic.ttf', - weight: '700', - style: 'italic', - }, - ], -}); -const MediumPreview: FC = () => { - const { value } = useIntegration(); - const settings = useSettings(); - const [title, subtitle] = settings.watch(['title', 'subtitle']); - return ( -
-
-
{title}
-
- {subtitle} -
-
-
- p.content).join('\n')} - /> -
-
- ); -}; -const MediumSettings: FC = () => { - const form = useSettings(); - const { date } = useIntegration(); - return ( - <> - - - -
- -
-
- -
- - ); -}; -export default withProvider(MediumSettings, MediumPreview, MediumSettingsDto); diff --git a/apps/frontend/src/components/launches/providers/medium/medium.publications.tsx b/apps/frontend/src/components/launches/providers/medium/medium.publications.tsx deleted file mode 100644 index 9ae07e55..00000000 --- a/apps/frontend/src/components/launches/providers/medium/medium.publications.tsx +++ /dev/null @@ -1,56 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const MediumPublications: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - - const customFunc = useCustomProviderFunction(); - const [publications, 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('publications').then((data) => setOrgs(data)); - const settings = getValues()[props.name]; - if (settings) { - setCurrentMedia(settings); - } - }, []); - if (!publications.length) { - return null; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/medium/medium.tags.tsx b/apps/frontend/src/components/launches/providers/medium/medium.tags.tsx deleted file mode 100644 index b8c3fab2..00000000 --- a/apps/frontend/src/components/launches/providers/medium/medium.tags.tsx +++ /dev/null @@ -1,80 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; - -export const MediumTags: 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([]); - const [suggestions, setSuggestions] = useState(''); - const t = useT(); - - const onDelete = useCallback( - (tagIndex: number) => { - const modify = tagValue.filter((_, i) => i !== tagIndex); - 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] - ); - useEffect(() => { - const settings = getValues()[props.name]; - if (settings) { - setTagValue(settings); - } - }, []); - const suggestionsArray = useMemo(() => { - return [ - ...tagValue, - { - label: suggestions, - value: suggestions, - }, - ].filter((f) => f.label); - }, [suggestions, tagValue]); - return ( -
-
{label}
- -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/nostr/nostr.provider.tsx b/apps/frontend/src/components/launches/providers/nostr/nostr.provider.tsx deleted file mode 100644 index e4b05986..00000000 --- a/apps/frontend/src/components/launches/providers/nostr/nostr.provider.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -export default withProvider( - null, - undefined, - undefined, - async () => { - return true; - }, - undefined -); diff --git a/apps/frontend/src/components/launches/providers/pinterest/pinterest.board.tsx b/apps/frontend/src/components/launches/providers/pinterest/pinterest.board.tsx deleted file mode 100644 index 1618e2cf..00000000 --- a/apps/frontend/src/components/launches/providers/pinterest/pinterest.board.tsx +++ /dev/null @@ -1,59 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const PinterestBoard: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - - 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('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 ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx b/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx deleted file mode 100644 index ee97a378..00000000 --- a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -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 ( -
- - - - -
- ); -}; -export default withProvider( - PinterestSettings, - undefined, - PinterestSettingsDto, - 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; - }, - 500 -); diff --git a/apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx b/apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx deleted file mode 100644 index 287ffe14..00000000 --- a/apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import { FC, useCallback } 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 { Subreddit } from '@gitroom/frontend/components/launches/providers/reddit/subreddit'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { useFieldArray, useWatch } from 'react-hook-form'; -import { Button } from '@gitroom/react/form/button'; -import { - RedditSettingsDto, - RedditSettingsValueDto, -} from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto'; -import clsx from 'clsx'; -import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import MDEditor from '@uiw/react-md-editor'; -import interClass from '@gitroom/react/helpers/inter.font'; -import Image from 'next/image'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -const RenderRedditComponent: FC<{ - type: string; - images?: Array<{ - id: string; - path: string; - }>; -}> = (props) => { - const { value: topValue } = useIntegration(); - const showMedia = useMediaDirectory(); - const t = useT(); - - const { type, images } = props; - const [firstPost] = topValue; - switch (type) { - case 'self': - return ( - - ); - case 'link': - return ( -
- {t('link', 'Link')} -
- ); - case 'media': - return ( -
- {!!images?.length && - images.map((image, index) => ( - - - - ))} -
- ); - } - return <>; -}; -const RedditPreview: FC = (props) => { - const { value: topValue, integration } = useIntegration(); - const settings = useWatch({ - name: 'subreddit', - }) as Array; - const [, ...restOfPosts] = useFormatting(topValue, { - removeMarkdown: true, - saveBreaklines: true, - specialFunc: (text: string) => { - return text.slice(0, 280); - }, - }); - if (!settings || !settings.length) { - return <>Please add at least one Subreddit from the settings; - } - return ( -
- {settings - .filter(({ value }) => value?.subreddit) - .map(({ value }, index) => ( -
-
-
-
-
-
- {value.subreddit} -
-
{integration?.name}
-
-
-
- {value.title} -
- -
- {restOfPosts.map((p, index) => ( -
-
- x - x -
-
-
- {integration?.name} -
- -
-
- ))} -
-
-
- ))} -
- ); -}; -const RedditSettings: FC = () => { - const { register, control } = useSettings(); - const { fields, append, remove } = useFieldArray({ - control, - // control props comes from useForm (optional: if you are using FormContext) - name: 'subreddit', // unique name for your Field Array - }); - const t = useT(); - - const addField = useCallback(() => { - append({}); - }, [fields, append]); - const deleteField = useCallback( - (index: number) => async () => { - if ( - !(await deleteDialog( - t( - 'are_you_sure_you_want_to_delete_this_subreddit', - 'Are you sure you want to delete this Subreddit?' - ) - )) - ) - return; - remove(index); - }, - [fields, remove] - ); - return ( - <> -
- {fields.map((field, index) => ( -
-
- x -
- -
- ))} -
- - {fields.length === 0 && ( -
- {t( - 'please_add_at_least_one_subreddit', - 'Please add at least one Subreddit' - )} -
- )} - - ); -}; -export default withProvider( - RedditSettings, - RedditPreview, - RedditSettingsDto, - undefined, - 10000 -); diff --git a/apps/frontend/src/components/launches/providers/reddit/subreddit.tsx b/apps/frontend/src/components/launches/providers/reddit/subreddit.tsx deleted file mode 100644 index 4eb104e2..00000000 --- a/apps/frontend/src/components/launches/providers/reddit/subreddit.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import { FC, FormEvent, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import { Input } from '@gitroom/react/form/input'; -import { useDebouncedCallback } from 'use-debounce'; -import { Button } from '@gitroom/react/form/button'; -import clsx from 'clsx'; -import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { useWatch } from 'react-hook-form'; -import { Select } from '@gitroom/react/form/select'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Canonical } from '@gitroom/react/form/canonical'; -import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const RenderOptions: FC<{ - options: Array<'self' | 'link' | 'media'>; - onClick: (current: 'self' | 'link' | 'media') => void; - value: 'self' | 'link' | 'media'; -}> = (props) => { - const { options, onClick, value } = props; - const mapValues = useMemo(() => { - return options.map((p) => ({ - children: ( - <> - {p === 'self' - ? 'Post' - : p === 'link' - ? 'Link' - : p === 'media' - ? 'Media' - : ''} - - ), - id: p, - onClick: () => onClick(p), - })); - }, [options]); - return ( -
- {mapValues.map((p) => ( -
- ); -}; -export const Subreddit: FC<{ - onChange: (event: { - target: { - name: string; - value: { - id: string; - name: string; - }; - }; - }) => void; - name: string; -}> = (props) => { - const { onChange, name } = props; - const state = useSettings(); - const t = useT(); - - const { date } = useIntegration(); - const split = name.split('.'); - const [loading, setLoading] = useState(false); - // @ts-ignore - const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value; - const [results, setResults] = useState([]); - const func = useCustomProviderFunction(); - const value = useWatch({ - name, - }); - const [searchValue, setSearchValue] = useState(''); - const setResult = (result: { id: string; name: string }) => async () => { - setLoading(true); - setSearchValue(''); - const restrictions = await func.get('restrictions', { - subreddit: result.name, - }); - onChange({ - target: { - name, - value: { - ...restrictions, - type: restrictions.allow[0], - media: [], - }, - }, - }); - setLoading(false); - }; - const setTitle = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - title: e.target.value, - }, - }, - }); - }, - [value] - ); - const setType = useCallback( - (e: string) => { - onChange({ - target: { - name, - value: { - ...value, - type: e, - }, - }, - }); - }, - [value] - ); - const setMedia = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - media: e.target.value.map((p: any) => p), - }, - }, - }); - }, - [value] - ); - const setURL = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - url: e.target.value, - }, - }, - }); - }, - [value] - ); - const setFlair = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - flair: value.flairs.find((p: any) => p.id === e.target.value), - }, - }, - }); - }, - [value] - ); - const search = useDebouncedCallback( - useCallback(async (e: FormEvent) => { - // @ts-ignore - setResults([]); - // @ts-ignore - if (!e.target.value) { - return; - } - // @ts-ignore - const results = await func.get('subreddits', { word: e.target.value }); - // @ts-ignore - setResults(results); - }, []), - 500 - ); - return ( -
- {value?.subreddit ? ( - <> - -
- -
- - - {value.type === 'link' && ( - - )} - {value.type === 'media' && ( -
-
-
- -
-
- )} - - ) : ( -
- { - // @ts-ignore - setSearchValue(e.target.value); - await search(e); - }} - /> - {!!results.length && !loading && ( -
- {results.map((r: { id: string; name: string }) => ( -
- {r.name} -
- ))} -
- )} -
- )} -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/show.all.providers.tsx b/apps/frontend/src/components/launches/providers/show.all.providers.tsx deleted file mode 100644 index 445f496e..00000000 --- a/apps/frontend/src/components/launches/providers/show.all.providers.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { FC } from 'react'; -import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; -import DevtoProvider from '@gitroom/frontend/components/launches/providers/devto/devto.provider'; -import XProvider from '@gitroom/frontend/components/launches/providers/x/x.provider'; -import LinkedinProvider from '@gitroom/frontend/components/launches/providers/linkedin/linkedin.provider'; -import RedditProvider from '@gitroom/frontend/components/launches/providers/reddit/reddit.provider'; -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.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'; -import DribbbleProvider from '@gitroom/frontend/components/launches/providers/dribbble/dribbble.provider'; -import ThreadsProvider from '@gitroom/frontend/components/launches/providers/threads/threads.provider'; -import DiscordProvider from '@gitroom/frontend/components/launches/providers/discord/discord.provider'; -import SlackProvider from '@gitroom/frontend/components/launches/providers/slack/slack.provider'; -import MastodonProvider from '@gitroom/frontend/components/launches/providers/mastodon/mastodon.provider'; -import BlueskyProvider from '@gitroom/frontend/components/launches/providers/bluesky/bluesky.provider'; -import LemmyProvider from '@gitroom/frontend/components/launches/providers/lemmy/lemmy.provider'; -import WarpcastProvider from '@gitroom/frontend/components/launches/providers/warpcast/warpcast.provider'; -import TelegramProvider from '@gitroom/frontend/components/launches/providers/telegram/telegram.provider'; -import NostrProvider from '@gitroom/frontend/components/launches/providers/nostr/nostr.provider'; -import VkProvider from '@gitroom/frontend/components/launches/providers/vk/vk.provider'; -export const Providers = [ - { - identifier: 'devto', - component: DevtoProvider, - }, - { - identifier: 'x', - component: XProvider, - }, - { - identifier: 'linkedin', - component: LinkedinProvider, - }, - { - identifier: 'linkedin-page', - component: LinkedinProvider, - }, - { - identifier: 'reddit', - component: RedditProvider, - }, - { - identifier: 'medium', - component: MediumProvider, - }, - { - identifier: 'hashnode', - component: HashnodeProvider, - }, - { - identifier: 'facebook', - component: FacebookProvider, - }, - { - identifier: 'instagram', - component: InstagramProvider, - }, - { - identifier: 'instagram-standalone', - component: InstagramProvider, - }, - { - identifier: 'youtube', - component: YoutubeProvider, - }, - { - identifier: 'tiktok', - component: TiktokProvider, - }, - { - identifier: 'pinterest', - component: PinterestProvider, - }, - { - identifier: 'dribbble', - component: DribbbleProvider, - }, - { - identifier: 'threads', - component: ThreadsProvider, - }, - { - identifier: 'discord', - component: DiscordProvider, - }, - { - identifier: 'slack', - component: SlackProvider, - }, - { - identifier: 'mastodon', - component: MastodonProvider, - }, - { - identifier: 'bluesky', - component: BlueskyProvider, - }, - { - identifier: 'lemmy', - component: LemmyProvider, - }, - { - identifier: 'wrapcast', - component: WarpcastProvider, - }, - { - identifier: 'telegram', - component: TelegramProvider, - }, - { - identifier: 'nostr', - component: NostrProvider, - }, - { - identifier: 'vk', - component: VkProvider, - }, -]; -export const ShowAllProviders: FC<{ - integrations: Integrations[]; - hideEditOnlyThis: boolean; - value: Array<{ - content: string; - id?: string; - }>; - selectedProvider?: Integrations; -}> = (props) => { - const { integrations, value, selectedProvider, hideEditOnlyThis } = props; - return ( - <> - {integrations.map((integration) => { - const { component: ProviderComponent } = Providers.find( - (provider) => provider.identifier === integration.identifier - ) || { - component: null, - }; - if ( - !ProviderComponent || - integrations.map((p) => p.id).indexOf(selectedProvider?.id!) === -1 - ) { - return null; - } - return ( - - ); - })} - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx b/apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx deleted file mode 100644 index 33207c60..00000000 --- a/apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx +++ /dev/null @@ -1,55 +0,0 @@ -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'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const SlackChannelSelect: FC<{ - name: string; - onChange: (event: { - target: { - value: string; - name: string; - }; - }) => void; -}> = (props) => { - const { onChange, name } = props; - const t = useT(); - const customFunc = useCustomProviderFunction(); - const [publications, 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('channels').then((data) => setOrgs(data)); - const settings = getValues()[props.name]; - if (settings) { - setCurrentMedia(settings); - } - }, []); - if (!publications.length) { - return null; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/providers/slack/slack.provider.tsx b/apps/frontend/src/components/launches/providers/slack/slack.provider.tsx deleted file mode 100644 index 19e4d2ce..00000000 --- a/apps/frontend/src/components/launches/providers/slack/slack.provider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { FC } from 'react'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { SlackChannelSelect } from '@gitroom/frontend/components/launches/providers/slack/slack.channel.select'; -import { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto'; -const SlackComponent: FC = () => { - const form = useSettings(); - return ( -
- -
- ); -}; -export default withProvider( - SlackComponent, - undefined, - SlackDto, - undefined, - 280 -); diff --git a/apps/frontend/src/components/launches/providers/telegram/telegram.provider.tsx b/apps/frontend/src/components/launches/providers/telegram/telegram.provider.tsx deleted file mode 100644 index 8b10b657..00000000 --- a/apps/frontend/src/components/launches/providers/telegram/telegram.provider.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -export default withProvider( - null, - undefined, - undefined, - async () => { - return true; - }, - 4096 -); diff --git a/apps/frontend/src/components/launches/providers/threads/threads.provider.tsx b/apps/frontend/src/components/launches/providers/threads/threads.provider.tsx deleted file mode 100644 index cde5fbd6..00000000 --- a/apps/frontend/src/components/launches/providers/threads/threads.provider.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { ThreadFinisher } from '@gitroom/frontend/components/launches/finisher/thread.finisher'; -const SettingsComponent = () => { - return ; -}; - -export default withProvider( - SettingsComponent, - undefined, - undefined, - async () => { - return true; - }, - 500 -); diff --git a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx deleted file mode 100644 index ff8908c5..00000000 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ /dev/null @@ -1,370 +0,0 @@ -import { - FC, - ReactEventHandler, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Select } from '@gitroom/react/form/select'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import { Checkbox } from '@gitroom/react/form/checkbox'; -import clsx from 'clsx'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; - -const CheckTikTokValidity: FC<{ - picture: string; -}> = (props) => { - const { register } = useSettings(); - const t = useT(); - - const func = useCustomProviderFunction(); - const [maxVideoLength, setMaxVideoLength] = useState(0); - const [isValidVideo, setIsValidVideo] = useState( - undefined - ); - const registerVideo = register('isValidVideo'); - const video = useMemo(() => { - return props.picture; - }, [props.picture]); - useEffect(() => { - loadStats(); - }, []); - const loadStats = useCallback(async () => { - const { maxDurationSeconds } = await func.get('maxVideoLength'); - // setMaxVideoLength(5); - setMaxVideoLength(maxDurationSeconds); - }, []); - const loadVideo: ReactEventHandler = useCallback( - (e) => { - // @ts-ignore - setIsValidVideo(e.target.duration <= maxVideoLength); - registerVideo.onChange({ - target: { - name: 'isValidVideo', - // @ts-ignore - value: String(e.target.duration <= maxVideoLength), - }, - }); - }, - [maxVideoLength, registerVideo] - ); - if (!maxVideoLength || !video || video.indexOf('mp4') === -1) { - return null; - } - return ( - <> - {isValidVideo === false && ( -
- {t( - 'video_length_is_invalid_must_be_up_to', - 'Video length is invalid, must be up to' - )} - {maxVideoLength} - {t('seconds', 'seconds')} -
- )} - - - ); -}; -const TikTokSettings: FC<{ - values?: any; -}> = (props) => { - const { watch, register, formState, control } = useSettings(); - const t = useT(); - - const disclose = watch('disclose'); - const brand_organic_toggle = watch('brand_organic_toggle'); - const brand_content_toggle = watch('brand_content_toggle'); - const content_posting_method = watch('content_posting_method'); - const isUploadMode = content_posting_method === 'UPLOAD'; - - const privacyLevel = [ - { - value: 'PUBLIC_TO_EVERYONE', - label: t('public_to_everyone', 'Public to everyone'), - }, - { - value: 'MUTUAL_FOLLOW_FRIENDS', - label: t('mutual_follow_friends', 'Mutual follow friends'), - }, - { - value: 'FOLLOWER_OF_CREATOR', - label: t('follower_of_creator', 'Follower of creator'), - }, - { - value: 'SELF_ONLY', - label: t('self_only', 'Self only'), - }, - ]; - const contentPostingMethod = [ - { - value: 'DIRECT_POST', - label: t( - 'post_content_directly_to_tiktok', - 'Post content directly to TikTok' - ), - }, - { - value: 'UPLOAD', - label: t( - 'upload_content_to_tiktok_without_posting', - 'Upload content to TikTok without posting it' - ), - }, - ]; - const yesNo = [ - { - value: 'yes', - label: t('yes', 'Yes'), - }, - { - value: 'no', - label: t('no', 'No'), - }, - ]; - - return ( -
- - -
- {t( - 'choose_upload_without_posting_description', - `Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. - This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.` - )} -
- - -
- {t( - 'this_feature_available_only_for_photos', - 'This feature available only for photos, it will add a default music that\n you can change later.' - )} -
-
-
- {t('allow_user_to', 'Allow User To:')} -
-
- - - -
-
-
- - {disclose && ( -
-
- - - -
-
- {t( - 'your_video_will_be_labeled_promotional', - 'Your video will be labeled "Promotional Content".' - )} -
- {t( - 'this_cannot_be_changed_once_posted', - 'This cannot be changed once your video is posted.' - )} -
-
- )} -
- {t( - 'turn_on_to_disclose_video_promotes', - 'Turn on to disclose that this video promotes goods or services in\n exchange for something of value. You video could promote yourself, a\n third party, or both.' - )} -
-
-
- -
- {t( - 'you_are_promoting_yourself', - 'You are promoting yourself or your own brand.' - )} -
- {t( - 'this_video_will_be_classified_brand_organic', - 'This video will be classified as Brand Organic.' - )} -
- -
- {t( - 'you_are_promoting_another_brand', - 'You are promoting another brand or a third party.' - )} -
- {t( - 'this_video_will_be_classified_branded_content', - 'This video will be classified as Branded Content.' - )} -
- {(brand_organic_toggle || brand_content_toggle) && ( -
- {t( - 'by_posting_you_agree_to_tiktoks', - "By posting, you agree to TikTok's" - )} - {[ - brand_organic_toggle || brand_content_toggle ? ( - - {t('music_usage_confirmation', 'Music Usage Confirmation')} - - ) : undefined, - brand_content_toggle ? <> {t('and', 'and')} : undefined, - brand_content_toggle ? ( - - {t('branded_content_policy', 'Branded Content Policy')} - - ) : undefined, - ].filter((f) => f)} -
- )} -
-
- ); -}; -export default withProvider( - TikTokSettings, - undefined, - TikTokDto, - async (items) => { - const [firstItems] = items; - if (items.length !== 1) { - return 'Tiktok items should be one'; - } - if (firstItems.length === 0) { - return 'No video / images selected'; - } - if ( - firstItems.length > 1 && - firstItems?.some((p) => p?.path?.indexOf('mp4') > -1) - ) { - return 'Only pictures are supported when selecting multiple items'; - } else if ( - firstItems?.length !== 1 && - firstItems?.[0]?.path?.indexOf('mp4') > -1 - ) { - return 'You need one media'; - } - return true; - }, - 2000 -); diff --git a/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx b/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx deleted file mode 100644 index dc3939fd..00000000 --- a/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -export default withProvider( - null, - undefined, - undefined, - async (posts) => { - return true; - }, - 2048 -); diff --git a/apps/frontend/src/components/launches/providers/warpcast/subreddit.tsx b/apps/frontend/src/components/launches/providers/warpcast/subreddit.tsx deleted file mode 100644 index bbacbe2c..00000000 --- a/apps/frontend/src/components/launches/providers/warpcast/subreddit.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { FC, FormEvent, useCallback, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; -import { Input } from '@gitroom/react/form/input'; -import { useDebouncedCallback } from 'use-debounce'; -import { useWatch } from 'react-hook-form'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -export const Subreddit: FC<{ - onChange: (event: { - target: { - name: string; - value: { - id: string; - subreddit: string; - title: string; - name: string; - url: string; - body: string; - media: any[]; - }; - }; - }) => void; - name: string; -}> = (props) => { - const { onChange, name } = props; - const state = useSettings(); - const split = name.split('.'); - const [loading, setLoading] = useState(false); - // @ts-ignore - const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value; - const [results, setResults] = useState([]); - const func = useCustomProviderFunction(); - const value = useWatch({ - name, - }); - const [searchValue, setSearchValue] = useState(''); - const setResult = (result: { id: string; name: string }) => async () => { - setLoading(true); - setSearchValue(''); - onChange({ - target: { - name, - value: { - id: String(result.id), - subreddit: result.name, - title: '', - name: '', - url: '', - body: '', - media: [], - }, - }, - }); - setLoading(false); - }; - const setTitle = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - title: e.target.value, - }, - }, - }); - }, - [value] - ); - const setURL = useCallback( - (e: any) => { - onChange({ - target: { - name, - value: { - ...value, - url: e.target.value, - }, - }, - }); - }, - [value] - ); - const search = useDebouncedCallback( - useCallback(async (e: FormEvent) => { - // @ts-ignore - setResults([]); - // @ts-ignore - if (!e.target.value) { - return; - } - // @ts-ignore - const results = await func.get('subreddits', { word: e.target.value }); - // @ts-ignore - setResults(results); - }, []), - 500 - ); - return ( -
- {value?.subreddit ? ( - <> - - - ) : ( -
- { - // @ts-ignore - setSearchValue(e.target.value); - await search(e); - }} - /> - {!!results.length && !loading && ( -
- {results.map((r: { id: string; name: string }) => ( -
- {r.name} -
- ))} -
- )} -
- )} -
- ); -}; diff --git a/apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx b/apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx deleted file mode 100644 index f2714669..00000000 --- a/apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { FC, useCallback } from 'react'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { useFieldArray } from 'react-hook-form'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import { Button } from '@gitroom/react/form/button'; -import { Subreddit } from './subreddit'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -const WrapcastProvider: FC = () => { - const { register, control } = useSettings(); - const { fields, append, remove } = useFieldArray({ - control, - // control props comes from useForm (optional: if you are using FormContext) - name: 'subreddit', // unique name for your Field Array - }); - const t = useT(); - - const addField = useCallback(() => { - append({}); - }, [fields, append]); - const deleteField = useCallback( - (index: number) => async () => { - if ( - !(await deleteDialog( - t( - 'are_you_sure_you_want_to_delete_this_subreddit', - 'Are you sure you want to delete this Subreddit?' - ) - )) - ) - return; - remove(index); - }, - [fields, remove] - ); - return ( - <> -
- {fields.map((field, index) => ( -
-
- x -
- -
- ))} -
- - - ); -}; -export default withProvider( - WrapcastProvider, - undefined, - undefined, - async (list) => { - if ( - list.some((item) => item.some((field) => field.path.indexOf('mp4') > -1)) - ) { - return 'Warpcast can only accept images'; - } - return true; - }, - 800 -); diff --git a/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Bold.woff2 b/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Bold.woff2 deleted file mode 100644 index ed3a03d9..00000000 Binary files a/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Bold.woff2 and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Regular.woff2 b/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Regular.woff2 deleted file mode 100644 index 53ada57d..00000000 Binary files a/apps/frontend/src/components/launches/providers/x/fonts/Chirp-Regular.woff2 and /dev/null differ diff --git a/apps/frontend/src/components/launches/providers/x/x.provider.tsx b/apps/frontend/src/components/launches/providers/x/x.provider.tsx deleted file mode 100644 index 9573fc3a..00000000 --- a/apps/frontend/src/components/launches/providers/x/x.provider.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { ThreadFinisher } from '@gitroom/frontend/components/launches/finisher/thread.finisher'; -import { Select } from '@gitroom/react/form/select'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { XDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/x.dto'; -import { Input } from '@gitroom/react/form/input'; - -const whoCanReply = [ - { - label: 'Everyone', - value: 'everyone', - }, - { - label: 'Accounts you follow', - value: 'following', - }, - { - label: 'Mentioned accounts', - value: 'mentionedUsers', - }, - { - label: 'Subscribers', - value: 'subscribers', - }, - { - label: 'Verified accounts', - value: 'verified', - } -] - -const SettingsComponent = () => { - const t = useT(); - const { register, watch, setValue } = useSettings(); - - return ( - <> - - - - - - - ); -}; - -export default withProvider( - SettingsComponent, - undefined, - XDto, - async (posts, settings, additionalSettings: any) => { - const premium = additionalSettings?.find((p: any) => p?.title === 'Verified')?.value || false; - if (posts.some((p) => p.length > 4)) { - return 'There can be maximum 4 pictures in a post.'; - } - if ( - posts.some( - (p) => p.some((m) => m.path.indexOf('mp4') > -1) && p.length > 1 - ) - ) { - return 'There can be maximum 1 video in a post.'; - } - for (const load of posts.flatMap((p) => p.flatMap((a) => a.path))) { - if (load.indexOf('mp4') > -1) { - const isValid = await checkVideoDuration(load, premium); - if (!isValid) { - return 'Video duration must be less than or equal to 140 seconds.'; - } - } - } - return true; - }, - (settings) => { - if (settings?.[0]?.value) { - return 4000; - } - return 280; - } -); -const checkVideoDuration = async (url: string, isPremium = false): Promise => { - return new Promise((resolve, reject) => { - const video = document.createElement('video'); - video.src = url; - video.preload = 'metadata'; - video.onloadedmetadata = () => { - // Check if the duration is less than or equal to 140 seconds - const duration = video.duration; - if ((!isPremium && duration <= 140) || isPremium) { - resolve(true); // Video duration is acceptable - } else { - resolve(false); // Video duration exceeds 140 seconds - } - }; - video.onerror = () => { - reject(new Error('Failed to load video metadata.')); - }; - }); -}; diff --git a/apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx b/apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx deleted file mode 100644 index e636c93c..00000000 --- a/apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto'; -import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; -import { Input } from '@gitroom/react/form/input'; -import { MediumTags } from '@gitroom/frontend/components/launches/providers/medium/medium.tags'; -import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; -import { Select } from '@gitroom/react/form/select'; -const type = [ - { - label: 'Public', - value: 'public', - }, - { - label: 'Private', - value: 'private', - }, - { - label: 'Unlisted', - value: 'unlisted', - }, -]; -const YoutubeSettings: FC = () => { - const { register, control } = useSettings(); - return ( -
- - - -
- -
-
- ); -}; -export default withProvider( - YoutubeSettings, - undefined, - YoutubeSettingsDto, - async (items) => { - const [firstItems] = items; - if (items.length !== 1) { - return 'Youtube items should be one'; - } - if (items[0].length !== 1) { - return 'You need one media'; - } - if (firstItems[0].path.indexOf('mp4') === -1) { - return 'Item must be a video'; - } - return true; - }, - 5000 -); diff --git a/apps/frontend/src/components/launches/submitted.tsx b/apps/frontend/src/components/launches/submitted.tsx deleted file mode 100644 index daf899e7..00000000 --- a/apps/frontend/src/components/launches/submitted.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { FC, ReactNode, useCallback } from 'react'; -import { Button } from '@gitroom/react/form/button'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const Submitted: FC<{ - children: ReactNode; - postId: string; - status: 'YES' | 'NO' | 'WAITING_CONFIRMATION'; - updateOrder: () => void; -}> = (props) => { - const { postId, updateOrder, status, children } = props; - const fetch = useFetch(); - const t = useT(); - const cancel = useCallback(async () => { - if ( - !(await deleteDialog( - 'Are you sure you want to cancel this publication?', - 'Yes' - )) - ) { - return; - } - await fetch(`/marketplace/posts/${postId}/cancel`, { - method: 'POST', - }); - updateOrder(); - }, [postId]); - if (!status || status === 'NO') { - return <>{children}; - } - return ( - - ); -}; diff --git a/apps/frontend/src/components/launches/u.text.tsx b/apps/frontend/src/components/launches/u.text.tsx deleted file mode 100644 index aa268a15..00000000 --- a/apps/frontend/src/components/launches/u.text.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { FC, useCallback } from 'react'; -import { Editor, Transforms } from 'slate'; -import { ReactEditor } from 'slate-react'; -const underlineMap = { - a: 'a̲', - b: 'b̲', - c: 'c̲', - d: 'd̲', - e: 'e̲', - f: 'f̲', - g: 'g̲', - h: 'h̲', - i: 'i̲', - j: 'j̲', - k: 'k̲', - l: 'l̲', - m: 'm̲', - n: 'n̲', - o: 'o̲', - p: 'p̲', - q: 'q̲', - r: 'r̲', - s: 's̲', - t: 't̲', - u: 'u̲', - v: 'v̲', - w: 'w̲', - x: 'x̲', - y: 'y̲', - z: 'z̲', - A: 'A̲', - B: 'B̲', - C: 'C̲', - D: 'D̲', - E: 'E̲', - F: 'F̲', - G: 'G̲', - H: 'H̲', - I: 'I̲', - J: 'J̲', - K: 'K̲', - L: 'L̲', - M: 'M̲', - N: 'N̲', - O: 'O̲', - P: 'P̲', - Q: 'Q̲', - R: 'R̲', - S: 'S̲', - T: 'T̲', - U: 'U̲', - V: 'V̲', - W: 'W̲', - X: 'X̲', - Y: 'Y̲', - Z: 'Z̲', - '1': '1̲', - '2': '2̲', - '3': '3̲', - '4': '4̲', - '5': '5̲', - '6': '6̲', - '7': '7̲', - '8': '8̲', - '9': '9̲', - '0': '0̲', -}; -const reverseMap = Object.fromEntries( - Object.entries(underlineMap).map(([key, value]) => [value, key]) -); -export const UText: FC<{ - editor: any; - currentValue: string; -}> = ({ editor }) => { - const mark = () => { - const selectedText = Editor.string(editor, editor.selection); - const setUnderline = selectedText.indexOf('̲') === -1; - const newText = Array.from( - !selectedText - ? prompt('What do you want to write?') || '' - : selectedText.replace(/̲/g, '') - ) - .map((char) => { - // @ts-ignore - return ((setUnderline ? underlineMap?.[char] : reverseMap?.[char]) || char); - }) - .join(''); - Transforms.insertText(editor, newText); - ReactEditor.focus(editor); - }; - return ( -
- - - - - - - - - - - -
- ); -}; diff --git a/apps/frontend/src/components/layout/continue.provider.tsx b/apps/frontend/src/components/layout/continue.provider.tsx index 4bed9d6c..a5ef400c 100644 --- a/apps/frontend/src/components/layout/continue.provider.tsx +++ b/apps/frontend/src/components/layout/continue.provider.tsx @@ -1,11 +1,11 @@ import React, { FC, useCallback, useMemo } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; -import { continueProviderList } from '@gitroom/frontend/components/launches/providers/continue-provider/list'; import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration'; import dayjs from 'dayjs'; import useSWR, { useSWRConfig } from 'swr'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import { continueProviderList } from '@gitroom/frontend/components/new-launch/providers/continue-provider/list'; export const Null: FC<{ closeModal: () => void; existingId: string[]; diff --git a/apps/frontend/src/components/marketplace/preview.popup.dynamic.tsx b/apps/frontend/src/components/marketplace/preview.popup.dynamic.tsx index 92322b0b..4b445458 100644 --- a/apps/frontend/src/components/marketplace/preview.popup.dynamic.tsx +++ b/apps/frontend/src/components/marketplace/preview.popup.dynamic.tsx @@ -1,7 +1,7 @@ import 'reflect-metadata'; import { FC } from 'react'; import { Post as PrismaPost } from '.prisma/client'; -import { Providers } from '@gitroom/frontend/components/launches/providers/show.all.providers'; +import { Providers } from '@gitroom/frontend/components/new-launch/providers/show.all.providers'; export const PreviewPopupDynamic: FC<{ postId: string; providerId: string; @@ -15,17 +15,5 @@ export const PreviewPopupDynamic: FC<{ const { component: ProviderComponent } = Providers.find( (p) => p.identifier === props.providerId )!; - return ( - ({ - id: p.id, - content: p.content, - image: p.image, - }))} - /> - ); + return null; }; diff --git a/apps/frontend/src/components/new-launch/add.edit.modal.inner.tsx b/apps/frontend/src/components/new-launch/add.edit.modal.inner.tsx deleted file mode 100644 index c7f53d08..00000000 --- a/apps/frontend/src/components/new-launch/add.edit.modal.inner.tsx +++ /dev/null @@ -1,99 +0,0 @@ -'use client'; - -import React, { FC, Fragment, useRef } from 'react'; -import { AddEditModalProps } from '@gitroom/frontend/components/new-launch/add.edit.modal'; -import clsx from 'clsx'; -import { TopTitle } from '@gitroom/frontend/components/new-launch/helpers/top.title.component'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { PicksSocialsComponent } from '@gitroom/frontend/components/new-launch/picks.socials.component'; -import { - Editor, - EditorWrapper, -} from '@gitroom/frontend/components/new-launch/editor'; -import { SelectCurrent } from '@gitroom/frontend/components/new-launch/select.current'; -import { ShowAllProviders } from '@gitroom/frontend/components/new-launch/providers/show.all.providers'; - -export const AddEditModalInnerInner: FC = () => { - const t = useT(); - const ref = useRef(null); - - return ( -
-
-
- -
asd
-
- - -
- -
-
- -
-
media
-
-
-
-
-
-
-
-
-
-
-
{ - console.log(await ref.current.checkAllValid()); - }} - > - test -
-
-
-
-
-
-
-
- - - - - -
-
- -
-
-
- ); -}; diff --git a/apps/frontend/src/components/new-launch/add.edit.modal.tsx b/apps/frontend/src/components/new-launch/add.edit.modal.tsx index 64b24c1b..d679a6be 100644 --- a/apps/frontend/src/components/new-launch/add.edit.modal.tsx +++ b/apps/frontend/src/components/new-launch/add.edit.modal.tsx @@ -3,11 +3,12 @@ import 'reflect-metadata'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import dayjs from 'dayjs'; import type { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; -import { FC, useEffect, useState } from 'react'; -import { useExistingData } from '@gitroom/frontend/components/new-launch/helpers/use.existing.data'; +import { FC, useEffect } from 'react'; import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; -import { AddEditModalInnerInner } from '@gitroom/frontend/components/new-launch/add.edit.modal.inner'; +import { ManageModal } from '@gitroom/frontend/components/new-launch/manage.modal'; import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; +import { useShallow } from 'zustand/react/shallow'; +import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; export interface AddEditModalProps { date: dayjs.Dayjs; @@ -30,14 +31,22 @@ export interface AddEditModalProps { } export const AddEditModal: FC = (props) => { - const setAllIntegrations = useLaunchStore( - (state) => state.setAllIntegrations + const { setAllIntegrations, setDate, setIsCreateSet } = useLaunchStore( + useShallow((state) => ({ + setAllIntegrations: state.setAllIntegrations, + setDate: state.setDate, + setIsCreateSet: state.setIsCreateSet, + })) ); const integrations = useLaunchStore((state) => state.integrations); useEffect(() => { - setAllIntegrations(props.integrations || []); + setDate(props.date || dayjs()); + setAllIntegrations( + (props.integrations || []).filter((f) => !f.inBetweenSteps && !f.disabled) + ); + setIsCreateSet(!!props.addEditSets); }, []); if (!integrations.length) { @@ -49,48 +58,118 @@ export const AddEditModal: FC = (props) => { export const AddEditModalInner: FC = (props) => { const existingData = useExistingData(); - const reset = useLaunchStore((state) => state.reset); - - const addOrRemoveSelectedIntegration = useLaunchStore( - (state) => state.addOrRemoveSelectedIntegration - ); - - const addGlobalValue = useLaunchStore((state) => state.addGlobalValue); - const addInternalValue = useLaunchStore((state) => state.addInternalValue); - const selectedIntegrations = useLaunchStore((state) => state.selectedIntegrations); - const global = useLaunchStore((state) => state.global); - const internal = useLaunchStore((state) => state.internal); + const { addOrRemoveSelectedIntegration, selectedIntegrations, integrations } = + useLaunchStore( + useShallow((state) => ({ + integrations: state.integrations, + selectedIntegrations: state.selectedIntegrations, + addOrRemoveSelectedIntegration: state.addOrRemoveSelectedIntegration, + })) + ); useEffect(() => { + if (props?.set?.posts?.length) { + for (const post of props?.set?.posts) { + if (post.integration) { + const integration = integrations.find( + (i) => i.id === post.integration.id + ); + addOrRemoveSelectedIntegration(integration, post.settings); + } + } + } + if (existingData.integration) { - const integration = props.integrations.find( + const integration = integrations.find( (i) => i.id === existingData.integration ); addOrRemoveSelectedIntegration(integration, existingData.settings); + } + }, []); - addInternalValue(0, existingData.settings, existingData.posts.map((post) => ({ - content: post.content, - id: post.id, + if (existingData.integration && selectedIntegrations.length === 0) { + return null; + } + + return ; +}; + +export const AddEditModalInnerInner: FC = (props) => { + const existingData = useExistingData(); + const { + reset, + addGlobalValue, + addInternalValue, + global, + setCurrent, + internal, + setTags, + } = useLaunchStore( + useShallow((state) => ({ + reset: state.reset, + addGlobalValue: state.addGlobalValue, + addInternalValue: state.addInternalValue, + setCurrent: state.setCurrent, + global: state.global, + internal: state.internal, + setTags: state.setTags, + })) + ); + + useEffect(() => { + if (existingData.integration) { + setTags( // @ts-ignore - media: post.image as any[], - }))); - } - else { - addGlobalValue(0, [{ - content: '', - id: makeId(10), - media: [], - }]); + existingData?.posts?.[0]?.tags?.map((p: any) => ({ + label: p.tag.name, + value: p.tag.name, + })) || [] + ); + addInternalValue( + 0, + existingData.integration, + existingData.posts.map((post) => ({ + content: post.content, + id: post.id, + // @ts-ignore + media: post.image as any[], + })) + ); + setCurrent(existingData.integration); } + addGlobalValue( + 0, + props.onlyValues?.length + ? props.onlyValues.map((p) => ({ + content: p.content, + id: makeId(10), + media: p.image || [], + })) + : props.set?.posts?.length + ? props.set.posts[0].value.map((p) => ({ + id: makeId(10), + content: p.content, + // @ts-ignore + media: p.media, + })) + : [ + { + content: '', + id: makeId(10), + media: [], + }, + ] + ); + return () => { reset(); }; }, []); - if (!selectedIntegrations.length && !global.length && !internal.length) { + if (!global.length && !internal.length) { return null; } - return ; + return ; }; diff --git a/apps/frontend/src/components/new-launch/bold.text.tsx b/apps/frontend/src/components/new-launch/bold.text.tsx index 06dd7db0..14617649 100644 --- a/apps/frontend/src/components/new-launch/bold.text.tsx +++ b/apps/frontend/src/components/new-launch/bold.text.tsx @@ -90,7 +90,7 @@ export const BoldText: FC<{ return (
({ internal: state.internal.find((p) => p.integration.id === state.current), + internalFromAll: state.integrations.find(p => p.id === state.current), global: state.global, current: state.current, addRemoveInternal: state.addRemoveInternal, @@ -38,9 +66,29 @@ export const EditorWrapper: FC<{ setGlobalValueText: state.setGlobalValueText, addInternalValue: state.addInternalValue, addGlobalValue: state.addGlobalValue, + setGlobalValueMedia: state.setGlobalValueMedia, + setInternalValueMedia: state.setInternalValueMedia, + changeOrderGlobal: state.changeOrderGlobal, + changeOrderInternal: state.changeOrderInternal, + isCreateSet: state.isCreateSet, + deleteGlobalValue: state.deleteGlobalValue, + deleteInternalValue: state.deleteInternalValue, + setGlobalValue: state.setGlobalValue, + setInternalValue: state.setInternalValue, })) ); + const existingData = useExistingData(); + const [loaded, setLoaded] = useState(true); + + useEffect(() => { + if (loaded) { + return; + } + + setLoaded(true); + }, [loaded]); + const canEdit = useMemo(() => { return current === 'global' || !!internal; }, [current, internal]); @@ -53,6 +101,46 @@ export const EditorWrapper: FC<{ return global; }, [current, internal, global]); + const setValue = useCallback( + (value: string[]) => { + const newValue = value.map((p, index) => { + return { + id: makeId(10), + ...(items?.[index]?.media + ? { media: items[index].media } + : { media: [] }), + content: p, + }; + }); + if (internal) { + return setInternalValue(current, newValue); + } + + return setGlobalValue(newValue); + }, + [internal, items] + ); + + useCopilotReadable({ + description: 'Current content of posts', + value: items.map((p) => p.content), + }); + + useCopilotAction({ + name: 'setPosts', + description: 'a thread of posts', + parameters: [ + { + name: 'content', + type: 'string[]', + description: 'a thread of posts', + }, + ], + handler: async ({ content }) => { + setValue(content); + }, + }); + const changeValue = useCallback( (index: number) => (value: string) => { if (internal) { @@ -64,6 +152,41 @@ export const EditorWrapper: FC<{ [current, global, internal] ); + const changeImages = useCallback( + (index: number) => (value: any[]) => { + if (internal) { + return setInternalValueMedia(current, index, value); + } + + return setGlobalValueMedia(index, value); + }, + [current, global, internal] + ); + + const changeOrder = useCallback( + (index: number) => (direction: 'up' | 'down') => { + if (internal) { + changeOrderInternal(current, index, direction); + return setLoaded(false); + } + + changeOrderGlobal(index, direction); + setLoaded(false); + }, + [changeOrderInternal, changeOrderGlobal, current, global, internal] + ); + + const goBackToGlobal = useCallback(async () => { + if ( + await deleteDialog( + 'This action is irreversible. Are you sure you want to go back to global mode?' + ) + ) { + setLoaded(false); + addRemoveInternal(current); + } + }, []); + const addValue = useCallback( (index: number) => () => { if (internal) { @@ -87,25 +210,115 @@ export const EditorWrapper: FC<{ [current, global, internal] ); + const deletePost = useCallback( + (index: number) => async () => { + if ( + !(await deleteDialog( + 'Are you sure you want to delete this post?', + 'Yes, delete it!' + )) + ) { + return; + } + + if (internal) { + deleteInternalValue(current, index); + return setLoaded(false); + } + + deleteGlobalValue(index); + setLoaded(false); + }, + [current, global, internal] + ); + + if (!loaded) { + return null; + } + return items.map((g, index) => (
- {!canEdit && ( + {!canEdit && !isCreateSet && (
addRemoveInternal(current)} + onClick={() => { + if (index !== 0) { + return; + } + + setLoaded(false); + addRemoveInternal(current); + }} className="select-none cursor-pointer absolute w-full h-full left-0 top-0 bg-red-600/10 z-[100]" > -
- Edit -
+ {index === 0 && ( +
+ Edit +
+ )}
)} -
- +
+
+ +
+
+ + {index === 0 && + current !== 'global' && + canEdit && + !existingData.integration && ( + + + + )} + {items.length > 1 && ( + + + + )} +
{canEdit && } @@ -116,8 +329,24 @@ export const EditorWrapper: FC<{ export const Editor: FC<{ totalPosts: number; value: string; + num?: number; + pictures?: any[]; + allValues?: any[]; onChange: (value: string) => void; + setImages?: (value: any[]) => void; + autoComplete?: boolean; + validateChars?: boolean; + identifier?: string; }> = (props) => { + const { + allValues, + pictures, + setImages, + num, + autoComplete, + validateChars, + identifier, + } = props; const user = useUser(); const [id] = useState(makeId(10)); const newRef = useRef(null); @@ -136,6 +365,38 @@ export const Editor: FC<{ return ( <>
+
+ + + +
setEmojiPickerOpen(!emojiPickerOpen)} + > + {'\uD83D\uDE00'} +
+ {identifier === 'linkedin' || identifier === 'linkedin-page' ? ( + + ) : null} +
+
+ { + addText(e.emoji); + setEmojiPickerOpen(false); + }} + open={emojiPickerOpen} + /> +
+
+
+ {validateChars && props.value.length < 6 && ( +
+ {t( + 'the_post_should_be_at_least_6_characters_long', + 'The post should be at least 6 characters long' + )} +
+ )}
-
- - - -
setEmojiPickerOpen(!emojiPickerOpen)} - > - {t('', '\uD83D\uDE00')} -
-
-
- { - addText(e.emoji); - setEmojiPickerOpen(false); - }} - open={emojiPickerOpen} - /> +
+ {setImages && ( + { + setImages(value.target.value); + }} + onOpen={() => {}} + onClose={() => {}} + /> + )}
); diff --git a/apps/frontend/src/components/new-launch/finisher/thread.finisher.tsx b/apps/frontend/src/components/new-launch/finisher/thread.finisher.tsx index e659fa1a..77c7f252 100644 --- a/apps/frontend/src/components/new-launch/finisher/thread.finisher.tsx +++ b/apps/frontend/src/components/new-launch/finisher/thread.finisher.tsx @@ -3,9 +3,9 @@ import { Slider } from '@gitroom/react/form/slider'; import clsx from 'clsx'; import { Editor } from '@gitroom/frontend/components/new-launch/editor'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; export const ThreadFinisher = () => { const integration = useIntegration(); @@ -50,10 +50,7 @@ export const ThreadFinisher = () => { setValue('thread_finisher', val)} value={value} - height={150} totalPosts={1} - order={1} - preview="edit" />
diff --git a/apps/frontend/src/components/new-launch/helpers/date.picker.tsx b/apps/frontend/src/components/new-launch/helpers/date.picker.tsx deleted file mode 100644 index 6d3a812c..00000000 --- a/apps/frontend/src/components/new-launch/helpers/date.picker.tsx +++ /dev/null @@ -1,102 +0,0 @@ -'use client'; - -import { FC, useCallback, useState } from 'react'; -import dayjs from 'dayjs'; -import { Calendar, TimeInput } from '@mantine/dates'; -import { useClickOutside } from '@mantine/hooks'; -import { Button } from '@gitroom/react/form/button'; -import { isUSCitizen } from './isuscitizen.utils'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const DatePicker: FC<{ - date: dayjs.Dayjs; - onChange: (day: dayjs.Dayjs) => void; -}> = (props) => { - const { date, onChange } = props; - const [open, setOpen] = useState(false); - const t = useT(); - - const changeShow = useCallback(() => { - setOpen((prev) => !prev); - }, []); - const ref = useClickOutside(() => { - setOpen(false); - }); - const changeDate = useCallback( - (type: 'date' | 'time') => (day: Date) => { - onChange( - dayjs( - type === 'time' - ? date.format('YYYY-MM-DD') + ' ' + dayjs(day).format('HH:mm:ss') - : dayjs(day).format('YYYY-MM-DD') + ' ' + date.format('HH:mm:ss') - ) - ); - }, - [date] - ); - return ( -
-
- {date.format(isUSCitizen() ? 'MM/DD/YYYY hh:mm A' : 'DD/MM/YYYY HH:mm')} -
-
- - - -
- {open && ( -
e.stopPropagation()} - className="animate-normalFadeDown absolute top-[100%] mt-[16px] end-0 bg-sixth border border-tableBorder text-textColor rounded-[16px] z-[300] p-[16px] flex flex-col" - > - { - if (modifiers.weekend) { - return '!text-customColor28'; - } - if (modifiers.outside) { - return '!text-gray'; - } - if (modifiers.selected) { - return '!text-white !bg-seventh !outline-none'; - } - return '!text-textColor'; - }} - classNames={{ - day: 'hover:bg-seventh', - calendarHeaderControl: 'text-textColor hover:bg-third', - calendarHeaderLevel: 'text-textColor hover:bg-third', // cell: 'child:!text-textColor' - }} - /> - - -
- )} -
- ); -}; diff --git a/apps/frontend/src/components/new-launch/helpers/dnd.provider.tsx b/apps/frontend/src/components/new-launch/helpers/dnd.provider.tsx deleted file mode 100644 index 82ff70c9..00000000 --- a/apps/frontend/src/components/new-launch/helpers/dnd.provider.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client'; - -import { FC, ReactNode } from 'react'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { DndProvider } from 'react-dnd'; -export const DNDProvider: FC<{ - children: ReactNode; -}> = ({ children }) => { - return {children}; -}; diff --git a/apps/frontend/src/components/new-launch/helpers/isuscitizen.utils.tsx b/apps/frontend/src/components/new-launch/helpers/isuscitizen.utils.tsx deleted file mode 100644 index 3490b57d..00000000 --- a/apps/frontend/src/components/new-launch/helpers/isuscitizen.utils.tsx +++ /dev/null @@ -1,6 +0,0 @@ -'use client'; - -export const isUSCitizen = () => { - const userLanguage = navigator.language || navigator.languages[0]; - return userLanguage.startsWith('en-US'); -}; diff --git a/apps/frontend/src/components/new-launch/helpers/linkedin.component.tsx b/apps/frontend/src/components/new-launch/helpers/linkedin.component.tsx deleted file mode 100644 index ef5883a8..00000000 --- a/apps/frontend/src/components/new-launch/helpers/linkedin.component.tsx +++ /dev/null @@ -1,189 +0,0 @@ -'use client'; - -import { EventEmitter } from 'events'; -import React, { FC, useCallback, useEffect, useState } from 'react'; -import { TopTitle } from '@gitroom/frontend/components/new-launch/helpers/top.title.component'; -import { - executeCommand, - ExecuteState, - ICommand, - selectWord, - TextAreaTextApi, -} from '@uiw/react-md-editor'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { Input } from '@gitroom/react/form/input'; -import { Button } from '@gitroom/react/form/button'; -import { useToaster } from '@gitroom/react/toaster/toaster'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -const postUrlEmitter = new EventEmitter(); -export const ShowLinkedinCompany = () => { - const [showPostSelector, setShowPostSelector] = useState(false); - const [id, setId] = useState(''); - const [callback, setCallback] = useState<{ - callback: (tag: string) => void; - // eslint-disable-next-line @typescript-eslint/no-empty-function - } | null>({ - callback: (tag: string) => {}, - } as any); - useEffect(() => { - postUrlEmitter.on( - 'show', - (params: { id: string; callback: (url: string) => void }) => { - setCallback(params); - setId(params.id); - setShowPostSelector(true); - } - ); - return () => { - setShowPostSelector(false); - setCallback(null); - setId(''); - postUrlEmitter.removeAllListeners(); - }; - }, []); - const close = useCallback(() => { - setShowPostSelector(false); - setCallback(null); - setId(''); - }, []); - if (!showPostSelector) { - return <>; - } - return ( - - ); -}; -export const showPostSelector = (id: string) => { - return new Promise((resolve) => { - postUrlEmitter.emit('show', { - id, - callback: (tag: string) => { - resolve(tag); - }, - }); - }); -}; -export const LinkedinCompany: FC<{ - onClose: () => void; - onSelect: (tag: string) => void; - id: string; -}> = (props) => { - const { onClose, onSelect, id } = props; - const fetch = useFetch(); - const [company, setCompany] = useState(null); - const toast = useToaster(); - const t = useT(); - const getCompany = async () => { - if (!company) { - return; - } - try { - const { options } = await ( - await fetch('/integrations/function', { - method: 'POST', - body: JSON.stringify({ - id, - name: 'company', - data: { - url: company, - }, - }), - }) - ).json(); - onSelect(options.value); - onClose(); - } catch (e) { - toast.show('Failed to load profile', 'warning'); - } - }; - return ( -
-
-
-
- -
- -
-
- setCompany(e.target.value)} - placeholder="https://www.linkedin.com/company/gitroom" - /> - -
-
-
- ); -}; -export const linkedinCompany = (identifier: string, id: string): ICommand[] => { - if (identifier !== 'linkedin' && identifier !== 'linkedin-page') { - return []; - } - return [ - { - name: 'linkedinCompany', - keyCommand: 'linkedinCompany', - shortcuts: 'ctrlcmd+p', - prefix: '', - suffix: '', - buttonProps: { - 'aria-label': 'Add Post Url', - title: 'Add Post Url', - }, - icon: ( - - - - ), - execute: async (state: ExecuteState, api: TextAreaTextApi) => { - const newSelectionRange = selectWord({ - text: state.text, - selection: state.selection, - prefix: state.command.prefix!, - suffix: state.command.suffix, - }); - const state1 = api.setSelectionRange(newSelectionRange); - const media = await showPostSelector(id); - executeCommand({ - api, - selectedText: state1.selectedText, - selection: state.selection, - prefix: media, - suffix: '', - }); - }, - }, - ]; -}; diff --git a/apps/frontend/src/components/new-launch/helpers/new.image.component.tsx b/apps/frontend/src/components/new-launch/helpers/new.image.component.tsx deleted file mode 100644 index d539b430..00000000 --- a/apps/frontend/src/components/new-launch/helpers/new.image.component.tsx +++ /dev/null @@ -1,91 +0,0 @@ -'use client'; - -import React from 'react'; -import { - executeCommand, - ExecuteState, - ICommand, - selectWord, - TextAreaTextApi, -} from '@uiw/react-md-editor'; -import { showMediaBox } from '@gitroom/frontend/components/media/media.component'; -import { loadVars } from '@gitroom/react/helpers/variable.context'; -export const newImage: ICommand = { - name: 'image', - keyCommand: 'image', - shortcuts: 'ctrlcmd+k', - prefix: '![image](', - suffix: ')', - buttonProps: { - 'aria-label': 'Add image (ctrl + k)', - title: 'Add image (ctrl + k)', - }, - icon: ( - - - - ), - execute: (state: ExecuteState, api: TextAreaTextApi) => { - const { uploadDirectory, backendUrl } = loadVars(); - let newSelectionRange = selectWord({ - text: state.text, - selection: state.selection, - prefix: state.command.prefix!, - suffix: state.command.suffix, - }); - let state1 = api.setSelectionRange(newSelectionRange); - if ( - state1.selectedText.includes('http') || - state1.selectedText.includes('www') || - state1.selectedText.includes('(post:') - ) { - executeCommand({ - api, - selectedText: state1.selectedText, - selection: state.selection, - prefix: state.command.prefix!, - suffix: state.command.suffix, - }); - return; - } - newSelectionRange = selectWord({ - text: state.text, - selection: state.selection, - prefix: '![', - suffix: ']()', - }); - state1 = api.setSelectionRange(newSelectionRange); - showMediaBox((media) => { - if (media) { - if (state1.selectedText.length > 0) { - executeCommand({ - api, - selectedText: state1.selectedText, - selection: state.selection, - prefix: '![', - suffix: `](${ - media.path.indexOf('http') === -1 - ? `${backendUrl}/${uploadDirectory}` - : `` - }${media.path})`, - }); - return; - } - executeCommand({ - api, - selectedText: state1.selectedText, - selection: state.selection, - prefix: '![image', - suffix: `](${ - media.path.indexOf('http') === -1 - ? `${backendUrl}/${uploadDirectory}` - : `` - }${media.path})`, - }); - } - }); - }, -}; diff --git a/apps/frontend/src/components/new-launch/helpers/top.title.component.tsx b/apps/frontend/src/components/new-launch/helpers/top.title.component.tsx deleted file mode 100644 index ee45eab5..00000000 --- a/apps/frontend/src/components/new-launch/helpers/top.title.component.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client'; - -import { FC, ReactNode } from 'react'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; - -export const TopTitle: FC<{ - title: string; - shouldExpend?: boolean; - removeTitle?: boolean; - expend?: () => void; - collapse?: () => void; - children?: ReactNode; -}> = (props) => { - const { title, removeTitle, children, shouldExpend, expend, collapse } = - props; - const t = useT(); - - // Translate the title using a key derived from the title itself - // This creates a consistent key pattern for each title - const translatedTitle = t( - // Convert to lowercase, replace spaces with underscores - `top_title_${title - .toLowerCase() - .replace(/\s+/g, '_') - .replace(/[^\w]/g, '')}`, - title - ); - - return ( -
- {!removeTitle &&
{translatedTitle}
} - {children} - {shouldExpend !== undefined && ( -
- {!shouldExpend ? ( - - - - ) : ( - - - - )} -
- )} -
- ); -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.custom.provider.function.ts b/apps/frontend/src/components/new-launch/helpers/use.custom.provider.function.ts deleted file mode 100644 index 0f012ac5..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.custom.provider.function.ts +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; -import { useCallback } from 'react'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -export const useCustomProviderFunction = () => { - const { integration } = useIntegration(); - const fetch = useFetch(); - const get = useCallback( - async (funcName: string, customData?: any) => { - const load = await fetch('/integrations/function', { - method: 'POST', - body: JSON.stringify({ - name: funcName, - id: integration?.id!, - data: customData, - }), - }); - if (load.status > 299 && load.status < 200) { - throw new Error('Failed to fetch'); - } - return load.json(); - }, - [integration] - ); - return { - get, - }; -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.existing.data.tsx b/apps/frontend/src/components/new-launch/helpers/use.existing.data.tsx deleted file mode 100644 index c2a3eae5..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.existing.data.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import { createContext, FC, ReactNode, useContext } from 'react'; -import { Post } from '@prisma/client'; -const ExistingDataContext = createContext({ - integration: '', - group: undefined as undefined | string, - posts: [] as Post[], - settings: {} as any, -}); -export const ExistingDataContextProvider: FC<{ - children: ReactNode; - value: any; -}> = ({ children, value }) => { - return ( - - {children} - - ); -}; -export const useExistingData = () => useContext(ExistingDataContext); diff --git a/apps/frontend/src/components/new-launch/helpers/use.expend.tsx b/apps/frontend/src/components/new-launch/helpers/use.expend.tsx deleted file mode 100644 index 3b392b29..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.expend.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import EventEmitter from 'events'; -import { useEffect, useState } from 'react'; -const emitter = new EventEmitter(); -export const useExpend = () => { - const [expend, setExpend] = useState(false); - useEffect(() => { - const hide = () => { - setExpend(false); - }; - const show = () => { - setExpend(true); - }; - emitter.on('hide', hide); - emitter.on('show', show); - return () => { - emitter.off('hide', hide); - emitter.off('show', show); - }; - }, []); - return { - expend, - hide: () => { - emitter.emit('hide'); - }, - show: () => { - emitter.emit('show'); - }, - }; -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.formatting.ts b/apps/frontend/src/components/new-launch/helpers/use.formatting.ts deleted file mode 100644 index 2f6b13d7..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.formatting.ts +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -export const useFormatting = ( - text: Array<{ - content: string; - image?: Array<{ - id: string; - path: string; - }>; - id?: string; - }>, - params: { - removeMarkdown?: boolean; - saveBreaklines?: boolean; - specialFunc?: (text: string) => any; - beforeSpecialFunc?: (text: string) => string; - } -) => { - return useMemo(() => { - return text.map((value) => { - let newText = value.content; - if (params.beforeSpecialFunc) { - newText = params.beforeSpecialFunc(newText); - } - if (params.saveBreaklines) { - newText = newText.replace('\n', '𝔫𝔢𝔴𝔩𝔦𝔫𝔢'); - } - newText = newText.replace(/@\w{1,15}/g, function (match) { - return `${match}`; - }); - if (params.saveBreaklines) { - newText = newText.replace('𝔫𝔢𝔴𝔩𝔦𝔫𝔢', '\n'); - } - if (params.specialFunc) { - newText = params.specialFunc(newText); - } - return { - id: value.id, - text: newText, - images: value.image, - count: - params.removeMarkdown && params.saveBreaklines - ? newText.replace(/\n/g, ' ').length - : newText.length, - }; - }); - }, [text]); -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.hide.top.editor.tsx b/apps/frontend/src/components/new-launch/helpers/use.hide.top.editor.tsx deleted file mode 100644 index 5ba28014..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.hide.top.editor.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import EventEmitter from 'events'; -import { useEffect, useState } from 'react'; -const emitter = new EventEmitter(); -export const useHideTopEditor = () => { - const [hideTopEditor, setHideTopEditor] = useState(false); - useEffect(() => { - const hide = () => { - setHideTopEditor(true); - }; - const show = () => { - setHideTopEditor(false); - }; - emitter.on('hide', hide); - emitter.on('show', show); - return () => { - emitter.off('hide', hide); - emitter.off('show', show); - }; - }, []); - return { - hideTopEditor, - hide: () => { - emitter.emit('hide'); - }, - show: () => { - emitter.emit('show'); - }, - }; -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.integration.tsx b/apps/frontend/src/components/new-launch/helpers/use.integration.tsx deleted file mode 100644 index edbc22df..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.integration.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; - -import { createContext, useContext } from 'react'; -import dayjs from 'dayjs'; -import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; - -const IntegrationContext = createContext<{ - date: dayjs.Dayjs; - integration: Integrations | undefined; - allIntegrations: Integrations[]; - value: Array<{ - content: string; - id?: string; - image?: Array<{ - path: string; - id: string; - }>; - }>; -}>({ - integration: undefined, - value: [], - date: dayjs(), - allIntegrations: [], -}); - -const useIntegration = () => useContext(IntegrationContext); -export {IntegrationContext, useIntegration}; \ No newline at end of file diff --git a/apps/frontend/src/components/new-launch/helpers/use.move.to.integration.tsx b/apps/frontend/src/components/new-launch/helpers/use.move.to.integration.tsx deleted file mode 100644 index 3aa00952..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.move.to.integration.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; - -import EventEmitter from 'events'; -import { useCallback, useEffect } from 'react'; -const emitter = new EventEmitter(); -export const useMoveToIntegration = () => { - return useCallback( - ({ - identifier, - toPreview, - }: { - identifier: string; - toPreview?: boolean; - }) => { - emitter.emit('moveToIntegration', { - identifier, - toPreview, - }); - }, - [] - ); -}; -export const useMoveToIntegrationListener = ( - useEffectParams: any[], - enabled: boolean, - callback: ({ - identifier, - toPreview, - }: { - identifier: string; - toPreview: boolean; - }) => void -) => { - useEffect(() => { - if (!enabled) { - return; - } - return load(); - }, useEffectParams); - const load = useCallback(() => { - emitter.off('moveToIntegration', callback); - emitter.on('moveToIntegration', callback); - return () => { - emitter.off('moveToIntegration', callback); - }; - }, useEffectParams); -}; diff --git a/apps/frontend/src/components/new-launch/helpers/use.values.ts b/apps/frontend/src/components/new-launch/helpers/use.values.ts deleted file mode 100644 index 209c160b..00000000 --- a/apps/frontend/src/components/new-launch/helpers/use.values.ts +++ /dev/null @@ -1,96 +0,0 @@ -'use client'; - -import { useEffect, useMemo } from 'react'; -import { useForm, useFormContext } from 'react-hook-form'; -import { classValidatorResolver } from '@hookform/resolvers/class-validator'; -import { IsOptional } from 'class-validator'; - -class Empty { - @IsOptional() - empty: string; -} - -const finalInformation = {} as { - [key: string]: { - posts: Array<{ - id?: string; - content: string; - media?: Array; - }>; - settings: () => object; - trigger: () => Promise; - isValid: boolean; - checkValidity?: ( - value: Array< - Array<{ - path: string; - }> - >, - settings: any, - additionalSettings: any, - ) => Promise; - maximumCharacters?: number; - }; -}; -export const useValues = ( - initialValues: object, - integration: string, - identifier: string, - value: Array<{ - id?: string; - content: string; - media?: Array; - }>, - dto: any, - checkValidity?: ( - value: Array< - Array<{ - path: string; - }> - >, - settings: any, - additionalSettings: any, - ) => Promise, - maximumCharacters?: number -) => { - - const form = useForm({ - resolver: classValidatorResolver(dto || Empty), - values: initialValues, - mode: 'onChange', - criteriaMode: 'all', - }); - - const getValues = useMemo(() => { - return () => ({ - ...form.getValues(), - __type: identifier, - }); - }, [form, integration]); - - // @ts-ignore - finalInformation[integration] = finalInformation[integration] || {}; - finalInformation[integration].posts = value; - finalInformation[integration].isValid = form.formState.isValid; - finalInformation[integration].settings = getValues; - finalInformation[integration].trigger = form.trigger; - if (checkValidity) { - finalInformation[integration].checkValidity = checkValidity; - } - if (maximumCharacters) { - finalInformation[integration].maximumCharacters = maximumCharacters; - } - useEffect(() => { - return () => { - delete finalInformation[integration]; - }; - }, []); - return form; -}; -export const useSettings = () => useFormContext(); -export const getValues = () => finalInformation; -export const resetValues = () => { - Object.keys(finalInformation).forEach((key) => { - delete finalInformation[key]; - }); -}; diff --git a/apps/frontend/src/components/new-launch/manage.modal.tsx b/apps/frontend/src/components/new-launch/manage.modal.tsx new file mode 100644 index 00000000..076fa334 --- /dev/null +++ b/apps/frontend/src/components/new-launch/manage.modal.tsx @@ -0,0 +1,445 @@ +'use client'; + +import React, { FC, useCallback, useRef, useState } from 'react'; +import { AddEditModalProps } from '@gitroom/frontend/components/new-launch/add.edit.modal'; +import clsx from 'clsx'; +import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { PicksSocialsComponent } from '@gitroom/frontend/components/new-launch/picks.socials.component'; +import { EditorWrapper } from '@gitroom/frontend/components/new-launch/editor'; +import { SelectCurrent } from '@gitroom/frontend/components/new-launch/select.current'; +import { ShowAllProviders } from '@gitroom/frontend/components/new-launch/providers/show.all.providers'; +import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; +import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; +import { DatePicker } from '@gitroom/frontend/components/launches/helpers/date.picker'; +import { useShallow } from 'zustand/react/shallow'; +import { RepeatComponent } from '@gitroom/frontend/components/launches/repeat.component'; +import { TagsComponent } from '@gitroom/frontend/components/launches/tags.component'; +import { Button } from '@gitroom/react/form/button'; +import { useToaster } from '@gitroom/react/toaster/toaster'; +import { weightedLength } from '@gitroom/helpers/utils/count.length'; +import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; +import { useModals } from '@mantine/modals'; +import { capitalize } from 'lodash'; +import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload'; +// @ts-ignore +import useKeypress from 'react-use-keypress'; +import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; +import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer'; +import { CopilotPopup } from '@copilotkit/react-ui'; + +function countCharacters(text: string, type: string): number { + if (type !== 'x') { + return text.length; + } + return weightedLength(text); +} + +export const ManageModal: FC = (props) => { + const t = useT(); + const fetch = useFetch(); + const ref = useRef(null); + const existingData = useExistingData(); + const [loading, setLoading] = useState(false); + const toaster = useToaster(); + const modal = useModals(); + usePreventWindowUnload(true); + + const { addEditSets, mutate, customClose } = props; + + const { + selectedIntegrations, + hide, + date, + setDate, + repeater, + setRepeater, + tags, + setTags, + integrations, + setSelectedIntegrations, + } = useLaunchStore( + useShallow((state) => ({ + hide: state.hide, + date: state.date, + setDate: state.setDate, + repeater: state.repeater, + setRepeater: state.setRepeater, + tags: state.tags, + setTags: state.setTags, + selectedIntegrations: state.selectedIntegrations, + integrations: state.integrations, + setSelectedIntegrations: state.setSelectedIntegrations, + })) + ); + + const deletePost = useCallback(async () => { + if ( + !(await deleteDialog( + 'Are you sure you want to delete this post?', + 'Yes, delete it!' + )) + ) { + setLoading(false); + return; + } + await fetch(`/posts/${existingData.group}`, { + method: 'DELETE', + }); + mutate(); + modal.closeAll(); + return; + }, [existingData, mutate, modal]); + + const askClose = useCallback(async () => { + if ( + await deleteDialog( + t( + 'are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost', + 'Are you sure you want to close this modal? (all data will be lost)' + ), + t('yes_close_it', 'Yes, close it!') + ) + ) { + if (customClose) { + customClose(); + return; + } + modal.closeAll(); + } + }, []); + + const changeCustomer = useCallback( + (customer: string) => { + const neededIntegrations = integrations.filter( + (p) => p?.customer?.id === customer + ); + setSelectedIntegrations( + neededIntegrations.map((p) => ({ + settings: {}, + selectedIntegrations: p, + })) + ); + }, + [integrations] + ); + + useKeypress('Escape', askClose); + + const schedule = useCallback( + (type: 'draft' | 'now' | 'schedule') => async () => { + const checkAllValid = await ref.current.checkAllValid(); + if (type !== 'draft') { + const notEnoughChars = checkAllValid.filter((p: any) => { + return p.values.some((a: any) => { + return ( + countCharacters(a.content, p?.integration?.identifier || '') < 6 + ); + }); + }); + + for (const item of notEnoughChars) { + console.log('no enough'); + toaster.show( + '' + + item.integration.name + + ' post is too short, it must be at least 6 characters', + 'warning' + ); + item.preview(); + return; + } + + for (const item of checkAllValid) { + if (item.valid === false) { + toaster.show('Some fields are not valid', 'warning'); + item.fix(); + return; + } + + if (item.errors !== true) { + toaster.show( + `${capitalize(item.integration.identifier.split('-')[0])} (${ + item.integration.name + }): ${item.errors}`, + 'warning' + ); + item.preview(); + return; + } + } + + const sliceNeeded = checkAllValid.filter((p: any) => { + return p.values.some((a: any) => { + return ( + countCharacters(a.content, p?.integration?.identifier || '') > + (p.maximumCharacters || 1000000) + ); + }); + }); + + for (const item of sliceNeeded) { + if ( + !(await deleteDialog( + `${item?.integration?.name} (${item?.integration?.identifier}) post is too long, it will be cropped, do you want to continue?`, + 'Yes, continue' + )) + ) { + item.preview(); + return; + } + } + } + + const shortLinkUrl = await ( + await fetch('/posts/should-shortlink', { + method: 'POST', + body: JSON.stringify({ + messages: checkAllValid.flatMap((p: any) => + p.values.flatMap((a: any) => a.content) + ), + }), + }) + ).json(); + + const shortLink = !shortLinkUrl.ask + ? false + : await deleteDialog( + 'Do you want to shortlink the URLs? it will let you get statistics over clicks', + 'Yes, shortlink it!' + ); + + const group = existingData.group || makeId(10); + const data = { + type, + inter: repeater || undefined, + tags, + shortLink, + date: date.utc().format('YYYY-MM-DDTHH:mm:ss'), + posts: checkAllValid.map((p: any) => ({ + integration: p.integration, + group, + settings: p.settings, + value: p.values.map((a: any) => ({ + ...a, + image: a.media || [], + content: a.content.slice(0, p.maximumCharacters || 1000000), + })), + })), + }; + + addEditSets + ? addEditSets(data) + : await fetch('/posts', { + method: 'POST', + body: JSON.stringify(data), + }); + + if (!addEditSets) { + mutate(); + toaster.show( + !existingData.integration + ? 'Added successfully' + : 'Updated successfully' + ); + } + if (customClose) { + setTimeout(() => { + customClose(); + }, 2000); + } + + if (!addEditSets) { + modal.closeAll(); + } + }, + [ref, repeater, tags, date, addEditSets] + ); + + return ( + <> +
+
+
+ +
+ + +
+
+ + +
+ {!existingData.integration && } +
+
+ {!hide && } +
+
+
+
+
+
+
+
+ {!!existingData.integration && ( + + )} + + {!addEditSets && ( + + )} + + {addEditSets && ( + + )} + {!addEditSets && ( + + )} +
+
+
+
+
+
+
+ +
+
+ setTags(e.target.value)} + /> +
+ +
+ + + +
+
+
+ +
+
+
+ + + ); +}; diff --git a/apps/frontend/src/components/new-launch/picks.socials.component.tsx b/apps/frontend/src/components/new-launch/picks.socials.component.tsx index eb17c34d..e33b8010 100644 --- a/apps/frontend/src/components/new-launch/picks.socials.component.tsx +++ b/apps/frontend/src/components/new-launch/picks.socials.component.tsx @@ -5,38 +5,46 @@ import clsx from 'clsx'; import Image from 'next/image'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import { useShallow } from 'zustand/react/shallow'; +import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data'; export const PicksSocialsComponent: FC<{ toolTip?: boolean }> = ({ toolTip, }) => { - const { addOrRemoveSelectedIntegration, integrations, selectedIntegrations } = + const exising = useExistingData(); + + const { locked, addOrRemoveSelectedIntegration, integrations, selectedIntegrations } = useLaunchStore( useShallow((state) => ({ integrations: state.integrations, selectedIntegrations: state.selectedIntegrations, addOrRemoveSelectedIntegration: state.addOrRemoveSelectedIntegration, + locked: state.locked, })) ); + return ( -
+
-
+
{integrations .filter((f) => !f.inBetweenSteps) .map((integration) => (
- addOrRemoveSelectedIntegration(integration, {}) - } + onClick={() => { + if (exising.integration) { + return; + } + addOrRemoveSelectedIntegration(integration, {}); + }} className={clsx( 'cursor-pointer relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth filter transition-all duration-500', selectedIntegrations.findIndex( diff --git a/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx b/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx index 6cf97f1d..6ef5e119 100644 --- a/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx @@ -2,10 +2,8 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher'; -import { useFormContext } from 'react-hook-form'; const SettingsComponent = () => { - const form = useFormContext(); return ; }; diff --git a/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx index a7738e73..a1c9d72c 100644 --- a/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx @@ -1,13 +1,13 @@ 'use client'; import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; import useSWR from 'swr'; import clsx from 'clsx'; import { Button } from '@gitroom/react/form/button'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; export const FacebookContinue: FC<{ closeModal: () => void; existingId: string[]; diff --git a/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx index 2ce5def6..4798d85c 100644 --- a/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx @@ -1,13 +1,13 @@ 'use client'; import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; import useSWR from 'swr'; import clsx from 'clsx'; import { Button } from '@gitroom/react/form/button'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; export const InstagramContinue: FC<{ closeModal: () => void; existingId: string[]; diff --git a/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx index f1bb48a0..e66b0ff6 100644 --- a/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx @@ -1,13 +1,13 @@ 'use client'; import { FC, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; import useSWR from 'swr'; import clsx from 'clsx'; import { Button } from '@gitroom/react/form/button'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; export const LinkedinContinue: FC<{ closeModal: () => void; existingId: string[]; diff --git a/apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx b/apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx index 886d612b..b4320897 100644 --- a/apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx @@ -3,7 +3,6 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; import { SelectOrganization } from '@gitroom/frontend/components/new-launch/providers/devto/select.organization'; @@ -12,8 +11,10 @@ import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; import clsx from 'clsx'; import localFont from 'next/font/local'; import MDEditor from '@uiw/react-md-editor'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; import { Canonical } from '@gitroom/react/form/canonical'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; + const font = localFont({ src: [ { diff --git a/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx b/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx index f61a875a..131cb9e9 100644 --- a/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx @@ -1,10 +1,10 @@ 'use client'; import { FC, useCallback, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; import { ReactTags } from 'react-tag-autocomplete'; import interClass from '@gitroom/react/helpers/inter.font'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; export const DevtoTags: FC<{ name: string; label: string; diff --git a/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx b/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx index 8451fa23..49cbbb80 100644 --- a/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx @@ -1,10 +1,10 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; import { Select } from '@gitroom/react/form/select'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; export const SelectOrganization: FC<{ name: string; onChange: (event: { diff --git a/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx b/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx index ce282790..a9a8a28a 100644 --- a/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx +++ b/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const DiscordChannelSelect: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx b/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx index 1fdf9f60..904fd0a2 100644 --- a/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx @@ -4,7 +4,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/ import { FC } from 'react'; import { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto'; import { DiscordChannelSelect } from '@gitroom/frontend/components/new-launch/providers/discord/discord.channel.select'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; const DiscordComponent: FC = () => { const form = useSettings(); return ( diff --git a/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx index a2dd6024..ce76011e 100644 --- a/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; import { DribbbleTeams } from '@gitroom/frontend/components/new-launch/providers/dribbble/dribbble.teams'; import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto'; diff --git a/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx index d6457372..a948b76c 100644 --- a/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx +++ b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const DribbbleTeams: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx index c1614075..36050b7c 100644 --- a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx @@ -2,12 +2,12 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; import { HashnodePublications } from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.publications'; import { HashnodeTags } from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.tags'; import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory'; import clsx from 'clsx'; import MDEditor from '@uiw/react-md-editor'; diff --git a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx index 9e6b8139..d6052f15 100644 --- a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const HashnodePublications: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx index 2bcebcce..352b76dd 100644 --- a/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx @@ -1,8 +1,8 @@ 'use client'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +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 HashnodeTags: FC<{ diff --git a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx index 83188e33..f7885dcd 100644 --- a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx @@ -1,27 +1,31 @@ 'use client'; -import React, { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; +import React, { + FC, + forwardRef, + useCallback, + useImperativeHandle, + useMemo, +} from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { IsOptional } from 'class-validator'; import { classValidatorResolver } from '@hookform/resolvers/class-validator'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; -import { IntegrationContext } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; import { useShallow } from 'zustand/react/shallow'; -import { timer } from '@gitroom/helpers/utils/timer'; +import { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component'; +import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { Button } from '@gitroom/react/form/button'; +import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import useSWR from 'swr'; +import { InternalChannels } from '@gitroom/frontend/components/launches/internal.channels'; +import { capitalize } from 'lodash'; class Empty { @IsOptional() empty: string; } -export const TriggerComponent: FC<{form: any}> = ({ form }) => { - useEffect(() => { - form.trigger(); - }, []); - - return null; -} - export const withProvider = function ( SettingsComponent: FC<{ values?: any; @@ -42,52 +46,122 @@ export const withProvider = function ( maximumCharacters?: number | ((settings: any) => number) ) { return forwardRef((props: { id: string }, ref) => { + const t = useT(); + const fetch = useFetch(); const { current, integrations, selectedIntegration, + setCurrent, internal, global, date, + isGlobal, + tab, + setTab, } = useLaunchStore( useShallow((state) => ({ date: state.date, + tab: state.tab, + setTab: state.setTab, global: state.global, internal: state.internal.find((p) => p.integration.id === props.id), integrations: state.selectedIntegrations, current: state.current === props.id, + isGlobal: state.current === 'global', + setCurrent: state.setCurrent, selectedIntegration: state.selectedIntegrations.find( (p) => p.integration.id === props.id ), })) ); + const getInternalPlugs = useCallback(async () => { + return ( + await fetch( + `/integrations/${selectedIntegration.integration.identifier}/internal-plugs` + ) + ).json(); + }, [selectedIntegration.integration.identifier]); + const { data, isLoading } = useSWR( + `internal-${selectedIntegration.integration.identifier}`, + getInternalPlugs, + { + revalidateOnReconnect: true, + } + ); + const value = useMemo(() => { - if (internal) { + if (internal?.integrationValue?.length) { return internal.integrationValue; } return global; - }, []); + }, [internal, global, isGlobal]); const form = useForm({ resolver: classValidatorResolver(dto || Empty), - values: {...selectedIntegration.settings}, + ...(Object.keys(selectedIntegration.settings).length > 0 + ? { values: { ...selectedIntegration.settings } } + : {}), mode: 'all', criteriaMode: 'all', + reValidateMode: 'onChange', }); - useImperativeHandle(ref, () => ({ - isValid: async () => { - return { - id: props.id, - identifier: selectedIntegration.integration.identifier, - valid: form.formState.isValid, - values: form.getValues(), - errors: form.formState.errors, - }; - }, - }), [form, props.id, selectedIntegration]); + useImperativeHandle( + ref, + () => ({ + isValid: async () => { + const settings = form.getValues(); + return { + id: props.id, + identifier: selectedIntegration.integration.identifier, + integration: selectedIntegration.integration, + valid: await form.trigger(), + errors: checkValidity + ? await checkValidity( + value.map((p) => p.media || []), + settings, + JSON.parse( + selectedIntegration.integration.additionalSettings || '[]' + ) + ) + : true, + settings, + values: value, + maximumCharacters: + typeof maximumCharacters === 'number' + ? maximumCharacters + : maximumCharacters( + JSON.parse( + selectedIntegration.integration.additionalSettings || '[]' + ) + ), + fix: () => { + setTab(1); + setCurrent(props.id); + }, + preview: () => { + setTab(0); + setCurrent(props.id); + }, + }; + }, + getValues: () => { + return { + id: props.id, + identifier: selectedIntegration.integration.identifier, + values: value, + settings: form.getValues(), + }; + }, + trigger: () => { + return form.trigger(); + }, + }), + [value] + ); return ( ( date, integration: selectedIntegration.integration, allIntegrations: integrations.map((p) => p.integration), - value: value, + value: value.map((p) => ({ + id: p.id, + content: p.content, + image: p.media, + })), }} > -
+
+
+
+ +
+ {!!SettingsComponent && ( +
+ +
+ )} +
+ + {(tab === 0 || !SettingsComponent) && + !value?.[0]?.content?.length && ( +
{t('start_writing_your_post', 'Start writing your post for a preview')}
+ )} + {(tab === 0 || !SettingsComponent) && + !!value?.[0]?.content?.length && + (CustomPreviewComponent ? ( + + ) : ( + + ))} {SettingsComponent && ( -
+
+ {!!data?.internalPlugs?.length && ( + + )}
)} - +
); diff --git a/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx b/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx index d36abc8d..d4c2bdac 100644 --- a/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx +++ b/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx @@ -3,7 +3,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { FC } from 'react'; import { Select } from '@gitroom/react/form/select'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +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/new-launch/providers/instagram/instagram.tags'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; diff --git a/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx b/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx index e9d65f1a..8b7f9572 100644 --- a/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx @@ -1,10 +1,10 @@ 'use client'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { ReactTags } from 'react-tag-autocomplete'; import interClass from '@gitroom/react/helpers/inter.font'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; import clsx from 'clsx'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; diff --git a/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx b/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx index 172e2f24..cee55028 100644 --- a/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx @@ -2,7 +2,7 @@ import { FC, useCallback } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useFieldArray } from 'react-hook-form'; import { Button } from '@gitroom/react/form/button'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; diff --git a/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx index e4b3a473..0e4c170c 100644 --- a/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx @@ -1,11 +1,11 @@ 'use client'; import { FC, FormEvent, useCallback, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; import { Input } from '@gitroom/react/form/input'; import { useDebouncedCallback } from 'use-debounce'; import { useWatch } from 'react-hook-form'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; export const Subreddit: FC<{ onChange: (event: { target: { diff --git a/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx b/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx index ecdd7940..379285b3 100644 --- a/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx @@ -3,7 +3,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { Checkbox } from '@gitroom/react/form/checkbox'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto'; const LinkedInSettings = () => { diff --git a/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx index 5b552d96..d35a4b69 100644 --- a/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx @@ -2,12 +2,12 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; import { MediumPublications } from '@gitroom/frontend/components/new-launch/providers/medium/medium.publications'; import { MediumTags } from '@gitroom/frontend/components/new-launch/providers/medium/medium.tags'; import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; import clsx from 'clsx'; import MDEditor from '@uiw/react-md-editor'; import localFont from 'next/font/local'; diff --git a/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx index c12c456b..12e7f856 100644 --- a/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const MediumPublications: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx index 0363170a..53d0d165 100644 --- a/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx @@ -1,7 +1,7 @@ 'use client'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { ReactTags } from 'react-tag-autocomplete'; import interClass from '@gitroom/react/helpers/inter.font'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; diff --git a/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx index 30e9ab89..d48570de 100644 --- a/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx +++ b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const PinterestBoard: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx index cfb2371e..a43ccb2b 100644 --- a/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { PinterestBoard } from '@gitroom/frontend/components/new-launch/providers/pinterest/pinterest.board'; import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto'; import { Input } from '@gitroom/react/form/input'; diff --git a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx index f0427d97..dca11b33 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx @@ -2,10 +2,9 @@ import { FC, useCallback } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; -import { useFormatting } from '@gitroom/frontend/components/new-launch/helpers/use.formatting'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; import { Subreddit } from '@gitroom/frontend/components/new-launch/providers/reddit/subreddit'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useFieldArray, useWatch } from 'react-hook-form'; import { Button } from '@gitroom/react/form/button'; import { @@ -19,6 +18,7 @@ import MDEditor from '@uiw/react-md-editor'; import interClass from '@gitroom/react/helpers/inter.font'; import Image from 'next/image'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting'; const RenderRedditComponent: FC<{ type: string; images?: Array<{ diff --git a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx index 07704150..8d14bc24 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx @@ -1,7 +1,7 @@ 'use client'; import { FC, FormEvent, useCallback, useMemo, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; import { Input } from '@gitroom/react/form/input'; import { useDebouncedCallback } from 'use-debounce'; import { Button } from '@gitroom/react/form/button'; @@ -9,9 +9,9 @@ import clsx from 'clsx'; import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; import { useWatch } from 'react-hook-form'; import { Select } from '@gitroom/react/form/select'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Canonical } from '@gitroom/react/form/canonical'; -import { useIntegration } from '@gitroom/frontend/components/new-launch/helpers/use.integration'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const RenderOptions: FC<{ options: Array<'self' | 'link' | 'media'>; diff --git a/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx b/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx index 62d7ea20..292d01cd 100644 --- a/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx +++ b/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx @@ -24,7 +24,12 @@ import NostrProvider from '@gitroom/frontend/components/new-launch/providers/nos import VkProvider from '@gitroom/frontend/components/new-launch/providers/vk/vk.provider'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import { useShallow } from 'zustand/react/shallow'; -import { createRef, FC, forwardRef, useImperativeHandle } from 'react'; +import React, { createRef, FC, forwardRef, useImperativeHandle } from 'react'; +import { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component'; +import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { Button } from '@gitroom/react/form/button'; +import { useT } from '@gitroom/react/translation/get.transation.service.client'; + export const Providers = [ { identifier: 'devto', @@ -124,12 +129,18 @@ export const Providers = [ }, ]; export const ShowAllProviders = forwardRef((props, ref) => { - const { current, selectedIntegrations } = useLaunchStore( - useShallow((state) => ({ - selectedIntegrations: state.selectedIntegrations, - current: state.current, - })) - ); + const { date, current, global, selectedIntegrations, allIntegrations } = + useLaunchStore( + useShallow((state) => ({ + date: state.date, + selectedIntegrations: state.selectedIntegrations, + allIntegrations: state.integrations, + current: state.current, + global: state.global, + })) + ); + + const t = useT(); useImperativeHandle(ref, () => ({ checkAllValid: async () => { @@ -137,10 +148,48 @@ export const ShowAllProviders = forwardRef((props, ref) => { selectedIntegrations.map(async (p) => await p.ref?.current.isValid()) ); }, + getAllValues: async () => { + return Promise.all( + selectedIntegrations.map(async (p) => await p.ref?.current.getValues()) + ); + }, + triggerAll: () => { + return selectedIntegrations.map(async (p) => await p.ref?.current.trigger()); + } })); return ( - <> +
+ {current === 'global' && ( + p.integration), + value: global.map((p) => ({ + id: p.id, + content: p.content, + image: p.media, + })), + }} + > +
+
+ +
+
+ {global?.[0]?.content?.length === 0 ? ( +
{t('start_writing_your_post', 'Start writing your post for a preview')}
+ ) : ( + + )} +
+ )} {selectedIntegrations.map((integration) => { const { component: ProviderComponent } = Providers.find( (provider) => @@ -157,7 +206,7 @@ export const ShowAllProviders = forwardRef((props, ref) => { /> ); })} - +
); }); diff --git a/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx b/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx index 510bf891..6de498e3 100644 --- a/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx +++ b/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx @@ -1,9 +1,9 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +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/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const SlackChannelSelect: FC<{ name: string; diff --git a/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx b/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx index 3393f5f5..1ff82a6f 100644 --- a/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx @@ -2,7 +2,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { FC } from 'react'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { SlackChannelSelect } from '@gitroom/frontend/components/new-launch/providers/slack/slack.channel.select'; import { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto'; const SlackComponent: FC = () => { diff --git a/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx index d0cea233..879afdef 100644 --- a/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx @@ -10,9 +10,9 @@ import { } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Select } from '@gitroom/react/form/select'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; import { Checkbox } from '@gitroom/react/form/checkbox'; import clsx from 'clsx'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; diff --git a/apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx index 56239784..3bcf2e02 100644 --- a/apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx @@ -1,11 +1,11 @@ 'use client'; import { FC, FormEvent, useCallback, useState } from 'react'; -import { useCustomProviderFunction } from '@gitroom/frontend/components/new-launch/helpers/use.custom.provider.function'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; import { Input } from '@gitroom/react/form/input'; import { useDebouncedCallback } from 'use-debounce'; import { useWatch } from 'react-hook-form'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; export const Subreddit: FC<{ onChange: (event: { target: { diff --git a/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx b/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx index 27c12622..9f23cdae 100644 --- a/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx @@ -2,7 +2,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { FC, useCallback } from 'react'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { useFieldArray } from 'react-hook-form'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import { Button } from '@gitroom/react/form/button'; diff --git a/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx b/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx index d8b824b4..b3297879 100644 --- a/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx @@ -4,7 +4,7 @@ import { withProvider } from '@gitroom/frontend/components/new-launch/providers/ import { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher'; import { Select } from '@gitroom/react/form/select'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { XDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/x.dto'; import { Input } from '@gitroom/react/form/input'; diff --git a/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx b/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx index f790a9bd..32975129 100644 --- a/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx @@ -3,7 +3,7 @@ import { FC } from 'react'; import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto'; -import { useSettings } from '@gitroom/frontend/components/new-launch/helpers/use.values'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; import { MediumTags } from '@gitroom/frontend/components/new-launch/providers/medium/medium.tags'; import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; diff --git a/apps/frontend/src/components/new-launch/select.current.tsx b/apps/frontend/src/components/new-launch/select.current.tsx index befc7ad3..a18596b0 100644 --- a/apps/frontend/src/components/new-launch/select.current.tsx +++ b/apps/frontend/src/components/new-launch/select.current.tsx @@ -1,70 +1,151 @@ 'use client'; -import { FC } from 'react'; +import { + FC, + RefObject, + useEffect, + useRef, + useState, +} from 'react'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import clsx from 'clsx'; import Image from 'next/image'; import { useShallow } from 'zustand/react/shallow'; +export function useHasScroll(ref: RefObject): boolean { + const [hasHorizontalScroll, setHasHorizontalScroll] = useState(false); + + useEffect(() => { + if (!ref.current) return; + + const checkScroll = () => { + const el = ref.current; + if (el) { + setHasHorizontalScroll(el.scrollWidth > el.clientWidth); + } + }; + + checkScroll(); // initial check + + const resizeObserver = new ResizeObserver(checkScroll); + resizeObserver.observe(ref.current); + + const mutationObserver = new MutationObserver(checkScroll); + mutationObserver.observe(ref.current, { + childList: true, + subtree: true, + characterData: true, + }); + + return () => { + resizeObserver.disconnect(); + mutationObserver.disconnect(); + }; + }, [ref]); + + return hasHorizontalScroll; +} + export const SelectCurrent: FC = () => { - const { selectedIntegrations, current, setCurrent } = useLaunchStore( - useShallow((state) => ({ - selectedIntegrations: state.selectedIntegrations, - current: state.current, - setCurrent: state.setCurrent, - })) - ); + const { selectedIntegrations, current, setCurrent, locked, setHide, hide } = + useLaunchStore( + useShallow((state) => ({ + selectedIntegrations: state.selectedIntegrations, + current: state.current, + setCurrent: state.setCurrent, + locked: state.locked, + hide: state.hide, + setHide: state.setHide, + })) + ); + + const contentRef = useRef(null); + const hasScroll = useHasScroll(contentRef); + console.log(hasScroll); + + useEffect(() => { + if (!hide) { + return; + } + + setHide(false); + }, [hide]); return ( -
-
setCurrent('global')} - className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" - > + <> +
setCurrent('global')} - className={clsx(current !== 'global' ? 'opacity-40' : '')} - > - T -
-
- {selectedIntegrations.map(({ integration }) => ( -
setCurrent(integration.id)} - key={integration.id} - className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + ref={contentRef} + className={clsx( + 'flex gap-[3px] w-full overflow-x-auto scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary', + locked && 'opacity-50 pointer-events-none' + )} >
{ + setHide(true); + setCurrent('global'); + }} + className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" > - {integration.identifier} - {integration.identifier === 'youtube' ? ( - - ) : ( - {integration.identifier} - )} +
+ + + +
+ {selectedIntegrations.map(({ integration }) => ( +
{ + setHide(true); + setCurrent(integration.id); + }} + key={integration.id} + className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + > +
+ {integration.identifier} + {integration.identifier === 'youtube' ? ( + + ) : ( + {integration.identifier} + )} +
+
+ ))}
- ))} -
+
+
+ ); }; diff --git a/apps/frontend/src/components/new-launch/store.ts b/apps/frontend/src/components/new-launch/store.ts index 86403e8d..a66c897f 100644 --- a/apps/frontend/src/components/new-launch/store.ts +++ b/apps/frontend/src/components/new-launch/store.ts @@ -4,6 +4,7 @@ import { create } from 'zustand'; import dayjs from 'dayjs'; import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; import { createRef, RefObject } from 'react'; +import { arrayMoveImmutable } from 'array-move'; interface Values { id: string; @@ -24,7 +25,14 @@ interface SelectedIntegrations { interface StoreState { date: dayjs.Dayjs; + repeater?: number; + isCreateSet: boolean; + tags: { label: string; value: string }[]; + tab: 0 | 1; current: string; + locked: boolean; + hide: boolean; + setLocked: (locked: boolean) => void; integrations: Integrations[]; selectedIntegrations: SelectedIntegrations[]; global: Values[]; @@ -35,16 +43,27 @@ interface StoreState { integrationId: string, value: Values[] ) => void; + setGlobalValue: (value: Values[]) => void; + setInternalValue: (integrationId: string, value: Values[]) => void; deleteGlobalValue: (index: number) => void; deleteInternalValue: (integrationId: string, index: number) => void; addRemoveInternal: (integrationId: string) => void; - changeOrderGlobal: (fromIndex: number, toIndex: number) => void; + changeOrderGlobal: (index: number, direction: 'up' | 'down') => void; changeOrderInternal: ( integrationId: string, - fromIndex: number, - toIndex: number + index: number, + direction: 'up' | 'down' ) => void; setGlobalValueText: (index: number, content: string) => void; + setGlobalValueMedia: ( + index: number, + media: { id: string; path: string }[] + ) => void; + setInternalValueMedia: ( + integrationId: string, + index: number, + media: { id: string; path: string }[] + ) => void; addGlobalValueMedia: ( index: number, media: { id: string; path: string }[] @@ -72,11 +91,25 @@ interface StoreState { settings: any ) => void; reset: () => void; + setSelectedIntegrations: ( + params: { selectedIntegrations: Integrations; settings: any }[] + ) => void; + setTab: (tab: 0 | 1) => void; + setHide: (hide: boolean) => void; + setDate: (date: dayjs.Dayjs) => void; + setRepeater: (repeater: number) => void; + setTags: (tags: { label: string; value: string }[]) => void; + setIsCreateSet: (isCreateSet: boolean) => void; } const initialState = { date: dayjs(), + tags: [] as { label: string; value: string }[], + tab: 0 as 0, + isCreateSet: false, current: 'global', + locked: false, + hide: false, integrations: [] as Integrations[], selectedIntegrations: [] as SelectedIntegrations[], global: [] as Values[], @@ -122,7 +155,6 @@ export const useLaunchStore = create()((set) => ({ return { global: state.global.reduce((acc, item, i) => { - console.log(i, index); acc.push(item); if (i === index) { acc.push(...value); @@ -131,18 +163,44 @@ export const useLaunchStore = create()((set) => ({ }, []), }; }), - // Add value after index + // Add value after index, similar to addGlobalValue, but for a speciic integration (index starts from 0) addInternalValue: (index: number, integrationId: string, value: Values[]) => set((state) => { - const newInternal = state.internal.map((i) => { - if (i.integration.id === integrationId) { - const newIntegrationValue = [...i.integrationValue]; - newIntegrationValue.splice(index + 1, 0, ...value); - return { ...i, integrationValue: newIntegrationValue }; - } - return i; - }); - return { internal: newInternal }; + const integrationIndex = state.internal.findIndex( + (i) => i.integration.id === integrationId + ); + + if (integrationIndex === -1) { + return { + internal: [ + ...state.internal, + { + integration: state.selectedIntegrations.find( + (i) => i.integration.id === integrationId + )!.integration, + integrationValue: value, + }, + ], + }; + } + + const updatedIntegration = state.internal[integrationIndex]; + const newValues = updatedIntegration.integrationValue.reduce( + (acc, item, i) => { + acc.push(item); + if (i === index) { + acc.push(...value); + } + return acc; + }, + [] as Values[] + ); + + return { + internal: state.internal.map((i, idx) => + idx === integrationIndex ? { ...i, integrationValue: newValues } : i + ), + }; }), deleteGlobalValue: (index: number) => set((state) => ({ @@ -191,29 +249,38 @@ export const useLaunchStore = create()((set) => ({ ], }; }), - changeOrderGlobal: (fromIndex: number, toIndex: number) => + changeOrderGlobal: (index: number, direction: 'up' | 'down') => set((state) => { - const updatedGlobal = [...state.global]; - const [movedItem] = updatedGlobal.splice(fromIndex, 1); - updatedGlobal.splice(toIndex, 0, movedItem); - return { global: updatedGlobal }; + return { + global: arrayMoveImmutable( + state.global, + index, + direction === 'up' ? index - 1 : index + 1 + ), + }; }), changeOrderInternal: ( integrationId: string, - fromIndex: number, - toIndex: number + index: number, + direction: 'up' | 'down' ) => set((state) => { - const updatedInternal = state.internal.map((i) => { - if (i.integration.id === integrationId) { - const updatedValues = [...i.integrationValue]; - const [movedItem] = updatedValues.splice(fromIndex, 1); - updatedValues.splice(toIndex, 0, movedItem); - return { ...i, integrationValue: updatedValues }; - } - return i; - }); - return { internal: updatedInternal }; + return { + internal: state.internal.map((item) => { + if (item.integration.id === integrationId) { + return { + ...item, + integrationValue: arrayMoveImmutable( + item.integrationValue, + index, + direction === 'up' ? index - 1 : index + 1 + ), + }; + } + + return item; + }), + }; }), setGlobalValueText: (index: number, content: string) => set((state) => ({ @@ -221,6 +288,30 @@ export const useLaunchStore = create()((set) => ({ i === index ? { ...item, content } : item ), })), + setInternalValueMedia: ( + integrationId: string, + index: number, + media: { id: string; path: string }[] + ) => { + return set((state) => ({ + internal: state.internal.map((item) => + item.integration.id === integrationId + ? { + ...item, + integrationValue: item.integrationValue.map((v, i) => + i === index ? { ...v, media } : v + ), + } + : item + ), + })); + }, + setGlobalValueMedia: (index: number, media: { id: string; path: string }[]) => + set((state) => ({ + global: state.global.map((item, i) => + i === index ? { ...item, media } : item + ), + })), addGlobalValueMedia: (index: number, media: { id: string; path: string }[]) => set((state) => ({ global: state.global.map((item, i) => @@ -242,7 +333,7 @@ export const useLaunchStore = create()((set) => ({ integrationId: string, index: number, content: string - ) => + ) => { set((state) => ({ internal: state.internal.map((item) => item.integration.id === integrationId @@ -254,7 +345,8 @@ export const useLaunchStore = create()((set) => ({ } : item ), - })), + })); + }, addInternalValueMedia: ( integrationId: string, index: number, @@ -303,4 +395,54 @@ export const useLaunchStore = create()((set) => ({ set((state) => ({ integrations: integrations, })), + setTab: (tab: 0 | 1) => + set((state) => ({ + tab: tab, + })), + setLocked: (locked: boolean) => + set((state) => ({ + locked: locked, + })), + setHide: (hide: boolean) => + set((state) => ({ + hide: hide, + })), + setDate: (date: dayjs.Dayjs) => + set((state) => ({ + date, + })), + setRepeater: (repeater: number) => + set((state) => ({ + repeater, + })), + setTags: (tags: { label: string; value: string }[]) => + set((state) => ({ + tags, + })), + setIsCreateSet: (isCreateSet: boolean) => + set((state) => ({ + isCreateSet, + })), + setSelectedIntegrations: ( + params: { selectedIntegrations: Integrations; settings: any }[] + ) => + set((state) => ({ + selectedIntegrations: params.map((p) => ({ + integration: p.selectedIntegrations, + settings: p.settings, + ref: createRef(), + })), + })), + setGlobalValue: (value: Values[]) => + set((state) => ({ + global: value, + })), + setInternalValue: (integrationId: string, value: Values[]) => + set((state) => ({ + internal: state.internal.map((item) => + item.integration.id === integrationId + ? { ...item, integrationValue: value } + : item + ), + })), })); diff --git a/apps/frontend/src/components/new-launch/u.text.tsx b/apps/frontend/src/components/new-launch/u.text.tsx index b014ecba..6ea6acae 100644 --- a/apps/frontend/src/components/new-launch/u.text.tsx +++ b/apps/frontend/src/components/new-launch/u.text.tsx @@ -93,7 +93,7 @@ export const UText: FC<{ return (
{ const fetch = useFetch(); const params = usePathname(); diff --git a/i18n.lock b/i18n.lock index 7d13037a..66a384a2 100644 --- a/i18n.lock +++ b/i18n.lock @@ -165,6 +165,7 @@ checksums: repeat_post_every: 0969cb627a580ec4afc19efd11958f4e use_this_media: 6aeb1e22711eafe7f3dca7e496b52931 create_new_post: d0eba99343fb56e2baf90486eb68e70f + update_post: f24fceedd97a09c9b26bd7e1f8ce202c merge_comments_into_one_post: 2f672bd0b317afb250671ad97ca881f6 accounts_that_will_engage: adb190c89e1a6f75cc883d904791581d day: 47648cd60fc313bc3f05b70357a1d675 @@ -190,6 +191,8 @@ checksums: please_add_the_following_command_in_your_chat: 91cf09291659b1293786f0cf5ac50408 copy: 627c00d2c850b9b45f7341a6ac01b6bb settings: 8df6777277469c1fd88cc18dde2f1cc3 + integrations: 0ccce343287704cd90150c32e2fcad36 + add_integration: 1b83c3002a8e84ba9449055de0f7e5ee you_are_now_editing_only: 92478fd454abceccc5e47ddb543d9fec tag_a_company: 6f8a005436febb76a4270a0ea6db432c video_length_is_invalid_must_be_up_to: d5ab83e0f9d8c0f6e9c9ffdf7ad80c1d @@ -425,7 +428,7 @@ checksums: enable_color_picker: 299af76a76f4bf348425e16cbef55cfa cancel_the_color_picker: bb5a774d8367a5154e29ba7ee0615a12 no_content_yet: bdde7a6b531b27bd921bdbd26a0fada7 - write_your_reply: b77b3540c5eb362704ec103120943d8a + write_your_reply: 0f4d124dbef4685590d5a95af3b5df64 add_a_tag: 1e529e5b2e62a9b521ded77f2f2cd53e add_to_calendar: 40f6e2b51467f0abf819e69dcb3df2a1 select_channels_from_circles: ecde8aa1d62c31366127aa7b7a69b028 @@ -488,3 +491,8 @@ checksums: change_language: c798f65b78e23b2cf8fc29a1a24a182f that_a_wrap: 0ecf5b5a1fbac9c2653f2642baf5d4a5 post_as_images_carousel: 2f82f0f6adbf03abfeec3389800d7232 + save_set: e9c633bf57da897086a7bfd5108e445b + separate_post: 8c4bbcbabb5200898d1dec0fbdbc96a0 + label_who_can_reply_to_this_post: 4d8913296a1fc3f197cb0aead34af73d + delete_integration: ccc879ccfcf7f85bcfe09f2bc3fa0dd3 + start_writing_your_post: 471efc4f2a7e2cf02a065a2de34e7213 diff --git a/libraries/react-shared-libraries/src/translation/locales/ar/translation.json b/libraries/react-shared-libraries/src/translation/locales/ar/translation.json index b80ad6d3..b7ecc267 100644 --- a/libraries/react-shared-libraries/src/translation/locales/ar/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/ar/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "تكرار النشر كل...", "use_this_media": "استخدم هذه الوسائط", "create_new_post": "إنشاء منشور جديد", + "update_post": "تحديث المنشور الحالي", "merge_comments_into_one_post": "دمج التعليقات في منشور واحد", "accounts_that_will_engage": "الحسابات التي ستتفاعل:", "day": "يوم", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "يرجى إضافة الأمر التالي في الدردشة الخاصة بك:", "copy": "نسخ", "settings": "الإعدادات", + "integrations": "التكاملات", + "add_integration": "إضافة تكامل", "you_are_now_editing_only": "أنت الآن تقوم بالتحرير فقط", "tag_a_company": "الإشارة إلى شركة", "video_length_is_invalid_must_be_up_to": "مدة الفيديو غير صالحة، يجب أن تكون حتى", @@ -421,7 +424,7 @@ "enable_color_picker": "تفعيل منتقي الألوان", "cancel_the_color_picker": "إلغاء منتقي الألوان", "no_content_yet": "لا يوجد محتوى بعد", - "write_your_reply": "اكتب ردك...", + "write_your_reply": "اكتب منشورك...", "add_a_tag": "أضف وسمًا", "add_to_calendar": "أضف إلى التقويم", "select_channels_from_circles": "اختر القنوات من الدوائر أعلاه", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "ابدأ تجربة مجانية لمدة 7 أيام", "change_language": "تغيير اللغة", "that_a_wrap": "انتهينا!\n\nإذا أعجبك هذا التسلسل:\n\n1. تابعني على @{{username}} للمزيد من هذه المواضيع\n2. أعد تغريد التغريدة أدناه لمشاركة هذا التسلسل مع جمهورك\n", - "post_as_images_carousel": "انشر كعرض شرائح للصور" + "post_as_images_carousel": "انشر كعرض شرائح للصور", + "save_set": "حفظ المجموعة", + "separate_post": "فصل المنشور إلى عدة منشورات", + "label_who_can_reply_to_this_post": "من يمكنه الرد على هذا المنشور؟", + "delete_integration": "حذف التكامل", + "start_writing_your_post": "ابدأ بكتابة منشورك لمعاينة" } diff --git a/libraries/react-shared-libraries/src/translation/locales/bn/translation.json b/libraries/react-shared-libraries/src/translation/locales/bn/translation.json index 423c74db..16c4004f 100644 --- a/libraries/react-shared-libraries/src/translation/locales/bn/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/bn/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "প্রতি পোস্ট পুনরাবৃত্তি করুন...", "use_this_media": "এই মিডিয়া ব্যবহার করুন", "create_new_post": "নতুন পোস্ট তৈরি করুন", + "update_post": "বিদ্যমান পোস্ট আপডেট করুন", "merge_comments_into_one_post": "মন্তব্যগুলি একটি পোস্টে একত্রিত করুন", "accounts_that_will_engage": "যে অ্যাকাউন্টগুলি এনগেজ করবে:", "day": "দিন", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "অনুগ্রহ করে আপনার চ্যাটে নিম্নলিখিত কমান্ড যোগ করুন:", "copy": "কপি করুন", "settings": "সেটিংস", + "integrations": "ইন্টিগ্রেশনসমূহ", + "add_integration": "ইন্টিগ্রেশন যোগ করুন", "you_are_now_editing_only": "আপনি এখন শুধুমাত্র সম্পাদনা করছেন", "tag_a_company": "একটি কোম্পানি ট্যাগ করুন", "video_length_is_invalid_must_be_up_to": "ভিডিওর দৈর্ঘ্য অবৈধ, সর্বোচ্চ হতে হবে", @@ -421,7 +424,7 @@ "enable_color_picker": "কলর পিকার সক্রিয় করা", "cancel_the_color_picker": "কলর পিকার বাতিল করা", "no_content_yet": "কন্টেন্ট নেই", - "write_your_reply": "আপনার উত্তর লিখুন", + "write_your_reply": "আপনার পোস্ট লিখুন...", "add_a_tag": "ট্যাগ যোগ করুন", "add_to_calendar": "ক্যালেন্ডারে যোগ করুন", "select_channels_from_circles": "বলার মাধ্যমে চ্যানেল নির্বাচন করুন", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "৭ দিনের বিনামূল্যে ট্রায়াল শুরু করুন", "change_language": "ভাষা পরিবর্তন করুন", "that_a_wrap": "এটাই শেষ!\n\nযদি আপনি এই থ্রেডটি উপভোগ করে থাকেন:\n\n১. আরও এমন পোস্টের জন্য আমাকে @{{username}} ফলো করুন\n২. আপনার অডিয়েন্সের সাথে এই থ্রেডটি শেয়ার করতে নিচের টুইটটি রিটুইট করুন\n", - "post_as_images_carousel": "ছবির ক্যারোসেল হিসেবে পোস্ট করুন" + "post_as_images_carousel": "ছবির ক্যারোসেল হিসেবে পোস্ট করুন", + "save_set": "সেট সংরক্ষণ করুন", + "separate_post": "একটি পোস্টকে একাধিক পোস্টে ভাগ করুন", + "label_who_can_reply_to_this_post": "এই পোস্টে কে উত্তর দিতে পারবে?", + "delete_integration": "ইন্টিগ্রেশন মুছে ফেলুন", + "start_writing_your_post": "প্রিভিউর জন্য আপনার পোস্ট লেখা শুরু করুন" } diff --git a/libraries/react-shared-libraries/src/translation/locales/de/translation.json b/libraries/react-shared-libraries/src/translation/locales/de/translation.json index 63f68358..99dd66ad 100644 --- a/libraries/react-shared-libraries/src/translation/locales/de/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/de/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Beitrag wiederholen alle...", "use_this_media": "Dieses Medium verwenden", "create_new_post": "Neuen Beitrag erstellen", + "update_post": "Vorhandenen Beitrag aktualisieren", "merge_comments_into_one_post": "Kommentare zu einem Beitrag zusammenfassen", "accounts_that_will_engage": "Konten, die interagieren werden:", "day": "Tag", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Bitte fügen Sie den folgenden Befehl in Ihrem Chat hinzu:", "copy": "Kopieren", "settings": "Einstellungen", + "integrations": "Integrationen", + "add_integration": "Integration hinzufügen", "you_are_now_editing_only": "Sie bearbeiten jetzt nur", "tag_a_company": "Unternehmen markieren", "video_length_is_invalid_must_be_up_to": "Videolänge ist ungültig, sie darf maximal", @@ -421,7 +424,7 @@ "enable_color_picker": "Farbwähler aktivieren", "cancel_the_color_picker": "Farbwähler abbrechen", "no_content_yet": "Noch kein Inhalt", - "write_your_reply": "Schreibe deine Antwort...", + "write_your_reply": "Schreibe deinen Beitrag...", "add_a_tag": "Tag hinzufügen", "add_to_calendar": "Zum Kalender hinzufügen", "select_channels_from_circles": "Wähle Kanäle aus den obigen Kreisen aus", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "7-tägige kostenlose Testversion starten", "change_language": "Sprache ändern", "that_a_wrap": "Das war's!\n\nWenn dir dieser Thread gefallen hat:\n\n1. Folge mir @{{username}} für mehr davon\n2. Retweete den untenstehenden Tweet, um diesen Thread mit deinem Publikum zu teilen\n", - "post_as_images_carousel": "Als Bilderkarussell posten" + "post_as_images_carousel": "Als Bilderkarussell posten", + "save_set": "Set speichern", + "separate_post": "Beitrag in mehrere Beiträge aufteilen", + "label_who_can_reply_to_this_post": "Wer kann auf diesen Beitrag antworten?", + "delete_integration": "Integration löschen", + "start_writing_your_post": "Beginne, deinen Beitrag für eine Vorschau zu schreiben" } diff --git a/libraries/react-shared-libraries/src/translation/locales/en/translation.json b/libraries/react-shared-libraries/src/translation/locales/en/translation.json index a085748a..92c27898 100644 --- a/libraries/react-shared-libraries/src/translation/locales/en/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/en/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Repeat Post Every...", "use_this_media": "Use this media", "create_new_post": "Create New Post", + "update_post": "Update Existing Post", "merge_comments_into_one_post": "Merge comments into one post", "accounts_that_will_engage": "Accounts that will engage:", "day": "Day", @@ -423,7 +424,7 @@ "enable_color_picker": "Enable color picker", "cancel_the_color_picker": "Cancel the color picker", "no_content_yet": "No Content Yet", - "write_your_reply": "Write your reply...", + "write_your_reply": "Write your post...", "add_a_tag": "Add a tag", "add_to_calendar": "Add to Calendar", "select_channels_from_circles": "Select channels from the circles above", @@ -489,5 +490,6 @@ "save_set": "Save Set", "separate_post": "Separate post to multiple posts", "label_who_can_reply_to_this_post": "Who can reply to this post?", - "delete_integration": "Delete Integration" + "delete_integration": "Delete Integration", + "start_writing_your_post": "Start writing your post for a preview" } diff --git a/libraries/react-shared-libraries/src/translation/locales/es/translation.json b/libraries/react-shared-libraries/src/translation/locales/es/translation.json index c293f1f5..8b25d338 100644 --- a/libraries/react-shared-libraries/src/translation/locales/es/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/es/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Repetir publicación cada...", "use_this_media": "Usar este medio", "create_new_post": "Crear nueva publicación", + "update_post": "Actualizar publicación existente", "merge_comments_into_one_post": "Unir comentarios en una sola publicación", "accounts_that_will_engage": "Cuentas que interactuarán:", "day": "Día", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Por favor agrega el siguiente comando en tu chat:", "copy": "Copiar", "settings": "Configuración", + "integrations": "Integraciones", + "add_integration": "Agregar integración", "you_are_now_editing_only": "Ahora solo estás editando", "tag_a_company": "Etiquetar una empresa", "video_length_is_invalid_must_be_up_to": "La duración del video no es válida, debe ser de hasta", @@ -421,7 +424,7 @@ "enable_color_picker": "Habilitar selector de color", "cancel_the_color_picker": "Cancelar el selector de color", "no_content_yet": "Aún no hay contenido", - "write_your_reply": "Escribe tu respuesta...", + "write_your_reply": "Escribe tu publicación...", "add_a_tag": "Agregar una etiqueta", "add_to_calendar": "Agregar al calendario", "select_channels_from_circles": "Selecciona canales de los círculos de arriba", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Comienza la prueba gratuita de 7 días", "change_language": "Cambiar idioma", "that_a_wrap": "¡Eso es todo!\n\nSi te gustó este hilo:\n\n1. Sígueme en @{{username}} para más contenido como este\n2. Haz RT al tuit de abajo para compartir este hilo con tu audiencia\n", - "post_as_images_carousel": "Publicar como carrusel de imágenes" + "post_as_images_carousel": "Publicar como carrusel de imágenes", + "save_set": "Guardar conjunto", + "separate_post": "Separar publicación en varias publicaciones", + "label_who_can_reply_to_this_post": "¿Quién puede responder a esta publicación?", + "delete_integration": "Eliminar integración", + "start_writing_your_post": "Comienza a escribir tu publicación para obtener una vista previa" } diff --git a/libraries/react-shared-libraries/src/translation/locales/fr/translation.json b/libraries/react-shared-libraries/src/translation/locales/fr/translation.json index 54b1d1c4..bde57da5 100644 --- a/libraries/react-shared-libraries/src/translation/locales/fr/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/fr/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Répéter la publication tous les...", "use_this_media": "Utiliser ce média", "create_new_post": "Créer une nouvelle publication", + "update_post": "Mettre à jour le post existant", "merge_comments_into_one_post": "Fusionner les commentaires en une seule publication", "accounts_that_will_engage": "Comptes qui vont interagir :", "day": "Jour", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Veuillez ajouter la commande suivante dans votre chat :", "copy": "Copier", "settings": "Paramètres", + "integrations": "Intégrations", + "add_integration": "Ajouter une intégration", "you_are_now_editing_only": "Vous modifiez maintenant uniquement", "tag_a_company": "Taguer une entreprise", "video_length_is_invalid_must_be_up_to": "La durée de la vidéo est invalide, elle doit être au maximum de", @@ -421,7 +424,7 @@ "enable_color_picker": "Activer le sélecteur de couleurs", "cancel_the_color_picker": "Annuler le sélecteur de couleurs", "no_content_yet": "Pas encore de contenu", - "write_your_reply": "Écrivez votre réponse...", + "write_your_reply": "Écrivez votre post...", "add_a_tag": "Ajouter une étiquette", "add_to_calendar": "Ajouter au calendrier", "select_channels_from_circles": "Sélectionnez des canaux à partir des cercles ci-dessus", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Commencez l’essai gratuit de 7 jours", "change_language": "Changer de langue", "that_a_wrap": "C'est terminé !\n\nSi vous avez aimé ce fil :\n\n1. Suivez-moi @{{username}} pour en voir d'autres\n2. Retweetez le tweet ci-dessous pour partager ce fil avec votre audience\n", - "post_as_images_carousel": "Publier en carrousel d’images" + "post_as_images_carousel": "Publier en carrousel d’images", + "save_set": "Enregistrer l'ensemble", + "separate_post": "Séparer le post en plusieurs publications", + "label_who_can_reply_to_this_post": "Qui peut répondre à ce post ?", + "delete_integration": "Supprimer l'intégration", + "start_writing_your_post": "Commencez à écrire votre post pour un aperçu" } diff --git a/libraries/react-shared-libraries/src/translation/locales/he/translation.json b/libraries/react-shared-libraries/src/translation/locales/he/translation.json index a4b41ccd..a5d61c01 100644 --- a/libraries/react-shared-libraries/src/translation/locales/he/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/he/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "חזור על הפוסט כל...", "use_this_media": "השתמש במדיה זו", "create_new_post": "צור פוסט חדש", + "update_post": "עדכן פוסט קיים", "merge_comments_into_one_post": "מזג תגובות לפוסט אחד", "accounts_that_will_engage": "חשבונות שיתקשרו:", "day": "יום", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "אנא הוסף את הפקודה הבאה בצ'אט שלך:", "copy": "העתק", "settings": "הגדרות", + "integrations": "אינטגרציות", + "add_integration": "הוסף אינטגרציה", "you_are_now_editing_only": "אתה עורך כעת רק", "tag_a_company": "תייג חברה", "video_length_is_invalid_must_be_up_to": "אורך הווידאו לא תקין, חייב להיות עד", @@ -421,7 +424,7 @@ "enable_color_picker": "הפעל בורר צבעים", "cancel_the_color_picker": "בטל את בורר הצבעים", "no_content_yet": "אין תוכן עדיין", - "write_your_reply": "כתוב את התשובה שלך...", + "write_your_reply": "כתוב את הפוסט שלך...", "add_a_tag": "הוסף תגית", "add_to_calendar": "הוסף ליומן", "select_channels_from_circles": "בחר ערוצים מהעיגולים למעלה", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "התחל תקופת ניסיון חינם ל-7 ימים", "change_language": "שנה שפה", "that_a_wrap": "זה הסוף!\n\nאם נהנית מהשרשור הזה:\n\n1. עקוב אחרי @{{username}} לעוד תכנים כאלה\n2. רטווט את הציוץ למטה כדי לשתף את השרשור עם הקהל שלך\n", - "post_as_images_carousel": "פרסם כתמונות בגלריה" + "post_as_images_carousel": "פרסם כתמונות בגלריה", + "save_set": "שמור סט", + "separate_post": "פצל פוסט למספר פוסטים", + "label_who_can_reply_to_this_post": "מי יכול להגיב לפוסט הזה?", + "delete_integration": "מחק אינטגרציה", + "start_writing_your_post": "התחל לכתוב את הפוסט שלך לתצוגה מקדימה" } diff --git a/libraries/react-shared-libraries/src/translation/locales/it/translation.json b/libraries/react-shared-libraries/src/translation/locales/it/translation.json index 65e18d2e..90629388 100644 --- a/libraries/react-shared-libraries/src/translation/locales/it/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/it/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Ripeti post ogni...", "use_this_media": "Usa questo media", "create_new_post": "Crea nuovo post", + "update_post": "Aggiorna post esistente", "merge_comments_into_one_post": "Unisci i commenti in un unico post", "accounts_that_will_engage": "Account che interagiranno:", "day": "Giorno", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Per favore aggiungi il seguente comando nella tua chat:", "copy": "Copia", "settings": "Impostazioni", + "integrations": "Integrazioni", + "add_integration": "Aggiungi integrazione", "you_are_now_editing_only": "Ora stai modificando solo", "tag_a_company": "Tagga un'azienda", "video_length_is_invalid_must_be_up_to": "La durata del video non è valida, deve essere fino a", @@ -421,7 +424,7 @@ "enable_color_picker": "Abilita selettore colore", "cancel_the_color_picker": "Annulla il selettore colore", "no_content_yet": "Nessun contenuto ancora", - "write_your_reply": "Scrivi la tua risposta...", + "write_your_reply": "Scrivi il tuo post...", "add_a_tag": "Aggiungi un'etichetta", "add_to_calendar": "Aggiungi al calendario", "select_channels_from_circles": "Seleziona i canali dai cerchi sopra", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Inizia la prova gratuita di 7 giorni", "change_language": "Cambia lingua", "that_a_wrap": "È tutto!\n\nSe ti è piaciuto questo thread:\n\n1. Seguimi su @{{username}} per altri contenuti come questo\n2. Ritwitta il tweet qui sotto per condividere questo thread con il tuo pubblico\n", - "post_as_images_carousel": "Pubblica come carosello di immagini" + "post_as_images_carousel": "Pubblica come carosello di immagini", + "save_set": "Salva set", + "separate_post": "Separa il post in più post", + "label_who_can_reply_to_this_post": "Chi può rispondere a questo post?", + "delete_integration": "Elimina integrazione", + "start_writing_your_post": "Inizia a scrivere il tuo post per un'anteprima" } diff --git a/libraries/react-shared-libraries/src/translation/locales/ja/translation.json b/libraries/react-shared-libraries/src/translation/locales/ja/translation.json index 93ea8f94..666fae61 100644 --- a/libraries/react-shared-libraries/src/translation/locales/ja/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/ja/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "投稿を繰り返す間隔...", "use_this_media": "このメディアを使用", "create_new_post": "新しい投稿を作成", + "update_post": "既存の投稿を更新", "merge_comments_into_one_post": "コメントを1つの投稿にまとめる", "accounts_that_will_engage": "エンゲージするアカウント:", "day": "日", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "チャットに次のコマンドを追加してください:", "copy": "コピー", "settings": "設定", + "integrations": "連携", + "add_integration": "連携を追加", "you_are_now_editing_only": "現在、次のみを編集しています", "tag_a_company": "会社をタグ付け", "video_length_is_invalid_must_be_up_to": "動画の長さが無効です。最大", @@ -421,7 +424,7 @@ "enable_color_picker": "カラーピッカーを有効にする", "cancel_the_color_picker": "カラーピッカーをキャンセル", "no_content_yet": "まだコンテンツがありません", - "write_your_reply": "返信を書いてください...", + "write_your_reply": "投稿内容を入力してください...", "add_a_tag": "タグを追加", "add_to_calendar": "カレンダーに追加", "select_channels_from_circles": "上のサークルからチャンネルを選択してください", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "7日間の無料トライアルを開始", "change_language": "言語を変更", "that_a_wrap": "以上で終了です!\n\nこのスレッドを楽しんでいただけたなら:\n\n1. @{{username}} をフォローして、さらに多くの投稿をご覧ください\n2. 下のツイートをリツイートして、このスレッドをあなたのフォロワーと共有してください\n", - "post_as_images_carousel": "画像カルーセルとして投稿" + "post_as_images_carousel": "画像カルーセルとして投稿", + "save_set": "セットを保存", + "separate_post": "投稿を複数に分割", + "label_who_can_reply_to_this_post": "この投稿に返信できる人", + "delete_integration": "連携を削除", + "start_writing_your_post": "プレビュー用に投稿を書き始めてください" } diff --git a/libraries/react-shared-libraries/src/translation/locales/ko/translation.json b/libraries/react-shared-libraries/src/translation/locales/ko/translation.json index 99fb60b7..6038987b 100644 --- a/libraries/react-shared-libraries/src/translation/locales/ko/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/ko/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "게시 반복 주기...", "use_this_media": "이 미디어 사용", "create_new_post": "새 게시물 만들기", + "update_post": "기존 게시물 업데이트", "merge_comments_into_one_post": "댓글을 하나의 게시물로 합치기", "accounts_that_will_engage": "참여할 계정:", "day": "일", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "채팅에 다음 명령어를 추가해 주세요:", "copy": "복사", "settings": "설정", + "integrations": "통합", + "add_integration": "통합 추가", "you_are_now_editing_only": "현재 이 항목만 편집 중입니다", "tag_a_company": "회사 태그하기", "video_length_is_invalid_must_be_up_to": "영상 길이가 잘못되었습니다. 최대", @@ -421,7 +424,7 @@ "enable_color_picker": "색상 선택기 활성화", "cancel_the_color_picker": "색상 선택기 취소", "no_content_yet": "아직 콘텐츠가 없습니다", - "write_your_reply": "답글을 작성하세요...", + "write_your_reply": "게시글을 작성하세요...", "add_a_tag": "태그 추가", "add_to_calendar": "캘린더에 추가", "select_channels_from_circles": "위의 원에서 채널을 선택하세요", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "7일 무료 체험 시작하기", "change_language": "언어 변경", "that_a_wrap": "여기까지입니다!\n\n이 스레드가 유익하셨다면:\n\n1. 더 많은 정보를 원하시면 @{{username}}를 팔로우하세요\n2. 아래 트윗을 리트윗해서 이 스레드를 여러분의 팔로워들과 공유하세요\n", - "post_as_images_carousel": "이미지 캐러셀로 게시" + "post_as_images_carousel": "이미지 캐러셀로 게시", + "save_set": "세트 저장", + "separate_post": "게시물을 여러 개로 분리", + "label_who_can_reply_to_this_post": "이 게시물에 누가 답글을 달 수 있나요?", + "delete_integration": "통합 삭제", + "start_writing_your_post": "미리보기를 위해 게시글 작성을 시작하세요" } diff --git a/libraries/react-shared-libraries/src/translation/locales/pt/translation.json b/libraries/react-shared-libraries/src/translation/locales/pt/translation.json index add488d8..0435ae61 100644 --- a/libraries/react-shared-libraries/src/translation/locales/pt/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/pt/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Repetir postagem a cada...", "use_this_media": "Usar esta mídia", "create_new_post": "Criar nova postagem", + "update_post": "Atualizar postagem existente", "merge_comments_into_one_post": "Mesclar comentários em uma postagem", "accounts_that_will_engage": "Contas que irão interagir:", "day": "Dia", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Por favor, adicione o seguinte comando no seu chat:", "copy": "Copiar", "settings": "Configurações", + "integrations": "Integrações", + "add_integration": "Adicionar integração", "you_are_now_editing_only": "Agora você está editando apenas", "tag_a_company": "Marcar uma empresa", "video_length_is_invalid_must_be_up_to": "A duração do vídeo é inválida, deve ser de até", @@ -421,7 +424,7 @@ "enable_color_picker": "Ativar seletor de cores", "cancel_the_color_picker": "Cancelar o seletor de cores", "no_content_yet": "Ainda não há conteúdo", - "write_your_reply": "Escreva sua resposta...", + "write_your_reply": "Escreva sua postagem...", "add_a_tag": "Adicionar uma tag", "add_to_calendar": "Adicionar ao calendário", "select_channels_from_circles": "Selecione os canais dos círculos acima", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Comece o teste gratuito de 7 dias", "change_language": "Mudar idioma", "that_a_wrap": "É isso aí!\n\nSe você gostou deste fio:\n\n1. Siga-me @{{username}} para ver mais conteúdos como este\n2. Dê RT no tweet abaixo para compartilhar este fio com seu público\n", - "post_as_images_carousel": "Publicar como carrossel de imagens" + "post_as_images_carousel": "Publicar como carrossel de imagens", + "save_set": "Salvar conjunto", + "separate_post": "Separar postagem em várias postagens", + "label_who_can_reply_to_this_post": "Quem pode responder a esta postagem?", + "delete_integration": "Excluir integração", + "start_writing_your_post": "Comece a escrever sua postagem para visualizar" } diff --git a/libraries/react-shared-libraries/src/translation/locales/ru/translation.json b/libraries/react-shared-libraries/src/translation/locales/ru/translation.json index 1547ddd7..5ed57368 100644 --- a/libraries/react-shared-libraries/src/translation/locales/ru/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/ru/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Повторять публикацию каждые...", "use_this_media": "Использовать этот медиафайл", "create_new_post": "Создать новый пост", + "update_post": "Обновить существующий пост", "merge_comments_into_one_post": "Объединить комментарии в один пост", "accounts_that_will_engage": "Аккаунты, которые будут взаимодействовать:", "day": "День", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Пожалуйста, добавьте следующую команду в ваш чат:", "copy": "Копировать", "settings": "Настройки", + "integrations": "Интеграции", + "add_integration": "Добавить интеграцию", "you_are_now_editing_only": "Сейчас вы редактируете только", "tag_a_company": "Отметить компанию", "video_length_is_invalid_must_be_up_to": "Недопустимая длина видео, должно быть не более", @@ -421,7 +424,7 @@ "enable_color_picker": "Включить выбор цвета", "cancel_the_color_picker": "Отменить выбор цвета", "no_content_yet": "Пока нет контента", - "write_your_reply": "Напишите свой ответ...", + "write_your_reply": "Напишите свой пост...", "add_a_tag": "Добавить тег", "add_to_calendar": "Добавить в календарь", "select_channels_from_circles": "Выберите каналы из кругов выше", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Начать 7-дневную бесплатную пробную версию", "change_language": "Сменить язык", "that_a_wrap": "На этом всё!\n\nЕсли вам понравилась эта серия:\n\n1. Подпишитесь на меня @{{username}}, чтобы не пропустить новые посты\n2. Ретвитните твит ниже, чтобы поделиться этой серией со своей аудиторией\n", - "post_as_images_carousel": "Опубликовать как карусель изображений" + "post_as_images_carousel": "Опубликовать как карусель изображений", + "save_set": "Сохранить набор", + "separate_post": "Разделить пост на несколько постов", + "label_who_can_reply_to_this_post": "Кто может отвечать на этот пост?", + "delete_integration": "Удалить интеграцию", + "start_writing_your_post": "Начните писать свой пост для предварительного просмотра" } diff --git a/libraries/react-shared-libraries/src/translation/locales/tr/translation.json b/libraries/react-shared-libraries/src/translation/locales/tr/translation.json index 054f97e5..24b495cc 100644 --- a/libraries/react-shared-libraries/src/translation/locales/tr/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/tr/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Gönderiyi Her ... Tekrarla", "use_this_media": "Bu medyayı kullan", "create_new_post": "Yeni Gönderi Oluştur", + "update_post": "Mevcut Gönderiyi Güncelle", "merge_comments_into_one_post": "Yorumları tek gönderide birleştir", "accounts_that_will_engage": "Etkileşime Geçecek Hesaplar:", "day": "Gün", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Lütfen sohbetinize aşağıdaki komutu ekleyin:", "copy": "Kopyala", "settings": "Ayarlar", + "integrations": "Entegrasyonlar", + "add_integration": "Entegrasyon Ekle", "you_are_now_editing_only": "Şu anda yalnızca düzenliyorsunuz", "tag_a_company": "Bir şirket etiketle", "video_length_is_invalid_must_be_up_to": "Video uzunluğu geçersiz, en fazla", @@ -421,7 +424,7 @@ "enable_color_picker": "Renk seçiciyi etkinleştir", "cancel_the_color_picker": "Renk seçiciyi iptal et", "no_content_yet": "Henüz İçerik Yok", - "write_your_reply": "Yanıtınızı yazın...", + "write_your_reply": "Gönderinizi yazın...", "add_a_tag": "Etiket ekle", "add_to_calendar": "Takvime Ekle", "select_channels_from_circles": "Yukarıdaki dairelerden kanalları seçin", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "7 gün ücretsiz denemeyi başlat", "change_language": "Dili Değiştir", "that_a_wrap": "Bu iş burada bitti!\n\nEğer bu diziyi beğendiyseniz:\n\n1. Daha fazlası için beni @{{username}} hesabından takip edin\n2. Aşağıdaki tweet'i RT'leyerek bu diziyi kendi kitlenizle paylaşın\n", - "post_as_images_carousel": "Görselleri kaydırmalı gönder olarak paylaş" + "post_as_images_carousel": "Görselleri kaydırmalı gönder olarak paylaş", + "save_set": "Seti Kaydet", + "separate_post": "Gönderiyi birden fazla gönderiye ayır", + "label_who_can_reply_to_this_post": "Bu gönderiye kim yanıt verebilir?", + "delete_integration": "Entegrasyonu Sil", + "start_writing_your_post": "Önizleme için gönderinizi yazmaya başlayın" } diff --git a/libraries/react-shared-libraries/src/translation/locales/vi/translation.json b/libraries/react-shared-libraries/src/translation/locales/vi/translation.json index 8e601f7f..82c54d82 100644 --- a/libraries/react-shared-libraries/src/translation/locales/vi/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/vi/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "Lặp lại bài đăng mỗi...", "use_this_media": "Sử dụng phương tiện này", "create_new_post": "Tạo bài đăng mới", + "update_post": "Cập nhật bài viết hiện có", "merge_comments_into_one_post": "Gộp bình luận vào một bài đăng", "accounts_that_will_engage": "Các tài khoản sẽ tương tác:", "day": "Ngày", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "Vui lòng thêm lệnh sau vào cuộc trò chuyện của bạn:", "copy": "Sao chép", "settings": "Cài đặt", + "integrations": "Tích hợp", + "add_integration": "Thêm tích hợp", "you_are_now_editing_only": "Bạn hiện chỉ đang chỉnh sửa", "tag_a_company": "Gắn thẻ công ty", "video_length_is_invalid_must_be_up_to": "Độ dài video không hợp lệ, phải tối đa", @@ -421,7 +424,7 @@ "enable_color_picker": "Bật bộ chọn màu", "cancel_the_color_picker": "Hủy bộ chọn màu", "no_content_yet": "Chưa có nội dung", - "write_your_reply": "Viết phản hồi của bạn...", + "write_your_reply": "Viết bài của bạn...", "add_a_tag": "Thêm thẻ", "add_to_calendar": "Thêm vào lịch", "select_channels_from_circles": "Chọn kênh từ các vòng tròn phía trên", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "Bắt đầu dùng thử miễn phí 7 ngày", "change_language": "Thay đổi ngôn ngữ", "that_a_wrap": "Kết thúc rồi!\n\nNếu bạn thích chuỗi bài này:\n\n1. Hãy theo dõi tôi @{{username}} để xem thêm nhiều nội dung như vậy\n2. Retweet bài bên dưới để chia sẻ chuỗi này với mọi người\n", - "post_as_images_carousel": "Đăng dưới dạng băng chuyền hình ảnh" + "post_as_images_carousel": "Đăng dưới dạng băng chuyền hình ảnh", + "save_set": "Lưu bộ", + "separate_post": "Tách bài viết thành nhiều bài", + "label_who_can_reply_to_this_post": "Ai có thể trả lời bài viết này?", + "delete_integration": "Xóa tích hợp", + "start_writing_your_post": "Bắt đầu viết bài của bạn để xem trước" } diff --git a/libraries/react-shared-libraries/src/translation/locales/zh/translation.json b/libraries/react-shared-libraries/src/translation/locales/zh/translation.json index 852765ab..dd0f9169 100644 --- a/libraries/react-shared-libraries/src/translation/locales/zh/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/zh/translation.json @@ -161,6 +161,7 @@ "repeat_post_every": "每隔...重复发布", "use_this_media": "使用此媒体", "create_new_post": "创建新帖子", + "update_post": "更新已有帖子", "merge_comments_into_one_post": "将评论合并为一条帖子", "accounts_that_will_engage": "将参与的账号:", "day": "天", @@ -186,6 +187,8 @@ "please_add_the_following_command_in_your_chat": "请在您的聊天中添加以下命令:", "copy": "复制", "settings": "设置", + "integrations": "集成", + "add_integration": "添加集成", "you_are_now_editing_only": "您现在仅在编辑", "tag_a_company": "标记公司", "video_length_is_invalid_must_be_up_to": "视频时长无效,最长不得超过", @@ -421,7 +424,7 @@ "enable_color_picker": "启用取色器", "cancel_the_color_picker": "取消取色器", "no_content_yet": "暂无内容", - "write_your_reply": "写下你的回复...", + "write_your_reply": "写下你的帖子...", "add_a_tag": "添加标签", "add_to_calendar": "添加到日历", "select_channels_from_circles": "从上方的圈子中选择频道", @@ -483,5 +486,10 @@ "start_7_days_free_trial": "开始7天免费试用", "change_language": "切换语言", "that_a_wrap": "本帖到此结束!\n\n如果你喜欢这个话题:\n\n1. 关注我 @{{username}},获取更多类似内容\n2. 转发下方推文,与更多人分享本帖\n", - "post_as_images_carousel": "以图片轮播的形式发布" + "post_as_images_carousel": "以图片轮播的形式发布", + "save_set": "保存设置", + "separate_post": "将帖子拆分为多个帖子", + "label_who_can_reply_to_this_post": "谁可以回复此帖子?", + "delete_integration": "删除集成", + "start_writing_your_post": "开始写你的帖子以预览" } diff --git a/package.json b/package.json index dc4d71b6..1e718cdb 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.3.1", "react-dropzone": "^14.3.5", - "react-hook-form": "^7.50.1", + "react-hook-form": "^7.58.1", "react-i18next": "^15.5.2", "react-loading": "^2.0.3", "react-tag-autocomplete": "^7.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d19d8d4..12d7d5f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: version: 1.8.9(@aws-crypto/sha256-js@5.2.0)(@aws-sdk/client-s3@3.802.0)(@aws-sdk/credential-provider-node@3.799.0)(@browserbasehq/sdk@2.5.0)(@browserbasehq/stagehand@1.14.0(@playwright/test@1.52.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@16.5.0)(openai@4.97.0(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.4))(utf-8-validate@5.0.10)(zod@3.24.4))(@ibm-cloud/watsonx-ai@1.6.4(@langchain/core@0.3.51(openai@4.97.0(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.4))))(@smithy/util-utf8@2.3.0)(axios@1.9.0)(cheerio@1.0.0)(crypto-js@4.2.0)(fast-xml-parser@4.5.3)(google-auth-library@9.15.1)(googleapis@137.1.0)(html-to-text@9.0.5)(ibm-cloud-sdk-core@5.3.2)(ignore@5.3.2)(ioredis@5.6.1)(jsdom@22.1.0(bufferutil@4.0.9)(canvas@2.11.2)(utf-8-validate@5.0.10))(jsonwebtoken@9.0.2)(lodash@4.17.21)(playwright@1.52.0)(redis@4.7.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@hookform/resolvers': specifier: ^3.3.4 - version: 3.10.0(react-hook-form@7.56.2(react@18.3.1)) + version: 3.10.0(react-hook-form@7.58.1(react@18.3.1)) '@langchain/community': specifier: ^0.3.40 version: 0.3.42(9a70403282cad7df9fe3853e78fee122) @@ -409,8 +409,8 @@ importers: specifier: ^14.3.5 version: 14.3.8(react@18.3.1) react-hook-form: - specifier: ^7.50.1 - version: 7.56.2(react@18.3.1) + specifier: ^7.58.1 + version: 7.58.1(react@18.3.1) react-i18next: specifier: ^15.5.2 version: 15.5.2(i18next@25.2.1(typescript@5.5.4))(react-dom@18.3.1(react@18.3.1))(react-native@0.79.2(@babel/core@7.27.1)(@types/react@18.3.1)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.5.4) @@ -13071,8 +13071,8 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-hook-form@7.56.2: - resolution: {integrity: sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==} + react-hook-form@7.58.1: + resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -18168,9 +18168,9 @@ snapshots: react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.5.0(react@18.3.1) - '@hookform/resolvers@3.10.0(react-hook-form@7.56.2(react@18.3.1))': + '@hookform/resolvers@3.10.0(react-hook-form@7.58.1(react@18.3.1))': dependencies: - react-hook-form: 7.56.2(react@18.3.1) + react-hook-form: 7.58.1(react@18.3.1) '@humanwhocodes/config-array@0.11.14': dependencies: @@ -19623,6 +19623,21 @@ snapshots: - typescript - verdaccio + '@nrwl/js@19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4)': + dependencies: + '@nx/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - debug + - nx + - supports-color + - typescript + - verdaccio + '@nrwl/nest@19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(chokidar@3.5.3)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(ts-node@10.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(typescript@5.5.4))(typescript@5.5.4)': dependencies: '@nx/nest': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(chokidar@3.5.3)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(ts-node@10.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(typescript@5.5.4))(typescript@5.5.4) @@ -19969,7 +19984,7 @@ snapshots: '@babel/preset-env': 7.27.1(@babel/core@7.27.1) '@babel/preset-typescript': 7.27.1(@babel/core@7.27.1) '@babel/runtime': 7.27.1 - '@nrwl/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.4.5) + '@nrwl/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4) '@nx/devkit': 19.7.2(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))) '@nx/workspace': 19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)) babel-plugin-const-enum: 1.2.0(@babel/core@7.27.1) @@ -32387,7 +32402,7 @@ snapshots: react-fast-compare@3.2.2: {} - react-hook-form@7.56.2(react@18.3.1): + react-hook-form@7.58.1(react@18.3.1): dependencies: react: 18.3.1