From 987caabac3bba63bf6874ec19d147d4b7288e319 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Fri, 16 May 2025 12:25:58 +0700 Subject: [PATCH] feat: urgent fix for react-hook-fork --- .../providers/high.order.provider.tsx | 1011 ++++++++--------- .../providers/tiktok/tiktok.provider.tsx | 4 + .../src/form/select.tsx | 7 +- 3 files changed, 510 insertions(+), 512 deletions(-) diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx index 6ac1982b..0f934d31 100644 --- a/apps/frontend/src/components/launches/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/launches/providers/high.order.provider.tsx @@ -88,555 +88,548 @@ export const withProvider = function ( ) => Promise, maximumCharacters?: number | ((settings: any) => number) ) { - return memo( - (props: { - identifier: string; - id: string; - value: Array<{ - content: string; - id?: string; - image?: Array<{ path: string; id: string }>; - }>; - hideMenu?: boolean; - show: boolean; - }) => { - const existingData = useExistingData(); - const { allIntegrations, integration, date } = useIntegration(); - const [showLinkedinPopUp, setShowLinkedinPopUp] = useState(false); - const [uploading, setUploading] = useState(false); - const fetch = useFetch(); + return (props: { + identifier: string; + id: string; + value: Array<{ + content: string; + id?: string; + image?: Array<{ path: string; id: string }>; + }>; + hideMenu?: boolean; + show: boolean; + }) => { + const existingData = useExistingData(); + const { allIntegrations, integration, date } = useIntegration(); + const [showLinkedinPopUp, setShowLinkedinPopUp] = useState(false); + const [uploading, setUploading] = useState(false); + const [showComponent, setShowComponent] = useState(false); + const fetch = useFetch(); - 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 + 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); + } + } + ); + + // this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation + const form = useValues( + 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 }[], + }, + ] + ) ); - const [InPlaceValue, setInPlaceValue] = useState< - Array<{ - id?: string; - content: string; - image?: Array<{ id: string; path: string }>; - }> - >( - // @ts-ignore - existingData.integration - ? existingData.posts.map((p) => ({ + }, [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, })) - : [{ content: '' }] ); - const [showTab, setShowTab] = useState(0); + }, [props.value, editInPlace]); - const Component = useMemo(() => { - return SettingsComponent ? SettingsComponent : () => <>; - }, [SettingsComponent]); + 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(); + }, + }); - // 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); - } - } - ); - - // this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation - const form = useValues( - 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 }[], - }, - ] - ) + const tagPersonOrCompany = useCallback( + (integration: string, editor: (value: string) => void) => () => { + setShowLinkedinPopUp( + { + editor(tag); + }} + id={integration} + onClose={() => setShowLinkedinPopUp(false)} + /> ); - }, [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] - ); + const uppy = useUppyUploader({ + onUploadSuccess: () => { + /**empty**/ + }, + allowedFileTypes: 'image/*,video/mp4', + }); - // 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!' - )) - ) { + 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; } - 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; - } + const files: File[] = []; - 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) { - console.log(item); - 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 - } + // @ts-ignore + for (const item of clipboardItems) { + console.log(item); + 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; + } + 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] + ); - 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 } = 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) { - return null; - } - + const getInternalPlugs = useCallback(async () => { return ( - - setShowTab(0)} /> - {showLinkedinPopUp ? showLinkedinPopUp : null} -
- {!props.hideMenu && ( -
+ 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) && (
- {(!!SettingsComponent || !!data?.internalPlugs?.length) && ( -
- + )} + {!existingData.integration && ( +
+ +
+ )} +
+ )} + {editInPlace && + createPortal( + + {uploading && ( +
+
)} - {!existingData.integration && ( -
- -
- )} -
- )} - {editInPlace && - createPortal( - - {uploading && ( -
- +
+ {!existingData?.integration && ( +
+ You are now editing only {integration?.name} ( + {capitalize(integration?.identifier.replace('-', ' '))})
)} -
- {!existingData?.integration && ( -
- You are now editing only {integration?.name} ( - {capitalize(integration?.identifier.replace('-', ' '))}) -
- )} - {InPlaceValue.map((val, index) => ( - -
-
-
- {(integration?.identifier === 'linkedin' || - integration?.identifier === - 'linkedin-page') && ( - - )} - ( + +
+
+
+ {(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) && ( +
+ The post should be at least 6 characters long +
+ )} +
+
+ - - {(!val.content || val.content.length < 6) && ( -
- The post should be at least 6 characters long -
- )} -
-
- -
-
- {InPlaceValue.length > 1 && ( -
-
- - - -
-
- Delete Post -
+
+
+ {InPlaceValue.length > 1 && ( +
+
+ + +
- )} -
+
+ Delete Post +
+
+ )}
-
- -
+
+
+
-
- -
- - ))} - {InPlaceValue.length > 1 && ( -
-
- )} -
- , - document.querySelector('#renderEditor')! - )} - {(showTab === 0 || showTab === 2) && ( -
- - {!!data?.internalPlugs?.length && ( - - )} -
- )} - {showTab === 0 && ( -
- - {(editInPlace ? InPlaceValue : props.value) - .map((p) => p.content) - .join('').length ? ( - CustomPreviewComponent ? ( - - ) : ( - - ) - ) : ( - <>No Content Yet +
+ +
+ + ))} + {InPlaceValue.length > 1 && ( +
+ +
)} -
-
+
+ , + document.querySelector('#renderEditor')! )} -
- - ); - } - ); + {(showTab === 0 || showTab === 2) && ( +
+ + {!!data?.internalPlugs?.length && ( + + )} +
+ )} + {showTab === 0 && ( +
+ + {(editInPlace ? InPlaceValue : props.value) + .map((p) => p.content) + .join('').length ? ( + CustomPreviewComponent ? ( + + ) : ( + + ) + ) : ( + <>No Content Yet + )} + +
+ )} +
+ + ); + }; }; diff --git a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx index c648d452..c7009527 100644 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx @@ -312,6 +312,10 @@ export default withProvider( 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) diff --git a/libraries/react-shared-libraries/src/form/select.tsx b/libraries/react-shared-libraries/src/form/select.tsx index 25d27d92..0e6beb86 100644 --- a/libraries/react-shared-libraries/src/form/select.tsx +++ b/libraries/react-shared-libraries/src/form/select.tsx @@ -1,6 +1,6 @@ 'use client'; -import { DetailedHTMLProps, FC, SelectHTMLAttributes, useMemo } from 'react'; +import { DetailedHTMLProps, FC, forwardRef, SelectHTMLAttributes, useMemo } from 'react'; import { clsx } from 'clsx'; import { useFormContext } from 'react-hook-form'; import interClass from '../helpers/inter.font'; @@ -18,7 +18,7 @@ export const Select: FC< name: string; hideErrors?: boolean; } -> = (props) => { +> = forwardRef((props, ref) => { const { label, className, @@ -39,6 +39,7 @@ export const Select: FC<
{label}