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/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/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index 434cc503..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 } 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'; @@ -53,6 +52,7 @@ import { useInterval } from '@mantine/hooks'; import { StatisticsModal } from '@gitroom/frontend/components/launches/statistics'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import i18next from 'i18next'; +import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal'; // Extend dayjs with necessary plugins extend(isSameOrAfter); 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/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/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/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/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/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/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/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/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/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/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/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/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/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.tsx b/apps/frontend/src/components/new-launch/add.edit.modal.tsx new file mode 100644 index 00000000..d679a6be --- /dev/null +++ b/apps/frontend/src/components/new-launch/add.edit.modal.tsx @@ -0,0 +1,175 @@ +'use client'; +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 } from 'react'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; +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; + 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; + }>; + }>; +} + +export const AddEditModal: FC = (props) => { + const { setAllIntegrations, setDate, setIsCreateSet } = useLaunchStore( + useShallow((state) => ({ + setAllIntegrations: state.setAllIntegrations, + setDate: state.setDate, + setIsCreateSet: state.setIsCreateSet, + })) + ); + + const integrations = useLaunchStore((state) => state.integrations); + + useEffect(() => { + setDate(props.date || dayjs()); + setAllIntegrations( + (props.integrations || []).filter((f) => !f.inBetweenSteps && !f.disabled) + ); + setIsCreateSet(!!props.addEditSets); + }, []); + + if (!integrations.length) { + return null; + } + + return ; +}; + +export const AddEditModalInner: FC = (props) => { + const existingData = useExistingData(); + 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 = integrations.find( + (i) => i.id === existingData.integration + ); + addOrRemoveSelectedIntegration(integration, existingData.settings); + } + }, []); + + 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 + 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 (!global.length && !internal.length) { + return null; + } + + return ; +}; diff --git a/apps/frontend/src/components/launches/add.post.button.tsx b/apps/frontend/src/components/new-launch/add.post.button.tsx similarity index 91% rename from apps/frontend/src/components/launches/add.post.button.tsx rename to apps/frontend/src/components/new-launch/add.post.button.tsx index 18c3e728..ae9cd85a 100644 --- a/apps/frontend/src/components/launches/add.post.button.tsx +++ b/apps/frontend/src/components/new-launch/add.post.button.tsx @@ -1,6 +1,7 @@ +'use client'; + 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; @@ -8,13 +9,7 @@ export const AddPostButton: FC<{ }> = (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/new-launch/manage.modal.tsx b/apps/frontend/src/components/new-launch/manage.modal.tsx new file mode 100644 index 00000000..66a77208 --- /dev/null +++ b/apps/frontend/src/components/new-launch/manage.modal.tsx @@ -0,0 +1,444 @@ +'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) { + 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 new file mode 100644 index 00000000..e33b8010 --- /dev/null +++ b/apps/frontend/src/components/new-launch/picks.socials.component.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { FC } from 'react'; +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 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) => ( +
+
{ + 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( + (p) => p.integration.id === integration.id + ) === -1 + ? 'opacity-40' + : '' + )} + > + {integration.identifier} + {integration.identifier === 'youtube' ? ( + + ) : ( + {integration.identifier} + )} +
+
+ ))} +
+
+
+
+ ); +}; diff --git a/apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx b/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx similarity index 69% rename from apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx rename to apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx index 5f63ccd5..6ef5e119 100644 --- a/apps/frontend/src/components/launches/providers/bluesky/bluesky.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx @@ -1,5 +1,7 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { ThreadFinisher } from '@gitroom/frontend/components/launches/finisher/thread.finisher'; +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; +import { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher'; const SettingsComponent = () => { return ; diff --git a/apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx rename to apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx index 97ae18bb..a1c9d72c 100644 --- a/apps/frontend/src/components/launches/providers/continue-provider/facebook/facebook.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx @@ -1,11 +1,13 @@ +'use client'; + 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'; +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/launches/providers/continue-provider/instagram/instagram.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/continue-provider/instagram/instagram.continue.tsx rename to apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx index ddf6c23f..4798d85c 100644 --- a/apps/frontend/src/components/launches/providers/continue-provider/instagram/instagram.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx @@ -1,11 +1,13 @@ +'use client'; + 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'; +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/launches/providers/continue-provider/linkedin/linkedin.continue.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/continue-provider/linkedin/linkedin.continue.tsx rename to apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx index 594fceb4..e66b0ff6 100644 --- a/apps/frontend/src/components/launches/providers/continue-provider/linkedin/linkedin.continue.tsx +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx @@ -1,11 +1,13 @@ +'use client'; + 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'; +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/continue-provider/list.tsx b/apps/frontend/src/components/new-launch/providers/continue-provider/list.tsx new file mode 100644 index 00000000..03b182e2 --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/continue-provider/list.tsx @@ -0,0 +1,10 @@ +'use client'; + +import { InstagramContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/instagram/instagram.continue'; +import { FacebookContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/facebook/facebook.continue'; +import { LinkedinContinue } from '@gitroom/frontend/components/new-launch/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/new-launch/providers/devto/devto.provider.tsx similarity index 86% rename from apps/frontend/src/components/launches/providers/devto/devto.provider.tsx rename to apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx index d457517e..649fa266 100644 --- a/apps/frontend/src/components/launches/providers/devto/devto.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx @@ -1,24 +1,19 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +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/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 { SelectOrganization } from '@gitroom/frontend/components/new-launch/providers/devto/select.organization'; +import { DevtoTags } from '@gitroom/frontend/components/new-launch/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', - }, - ], -}); +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; + const DevtoPreview: FC = () => { const { value } = useIntegration(); const settings = useSettings(); @@ -31,7 +26,6 @@ const DevtoPreview: FC = () => { return (
@@ -57,7 +51,6 @@ const DevtoPreview: FC = () => { style={{ whiteSpace: 'pre-wrap', }} - className={font.className} skipHtml={true} source={value.map((p) => p.content).join('\n')} /> diff --git a/apps/frontend/src/components/launches/providers/devto/devto.tags.tsx b/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/devto/devto.tags.tsx rename to apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx index 32ed4469..131cb9e9 100644 --- a/apps/frontend/src/components/launches/providers/devto/devto.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx @@ -1,8 +1,10 @@ +'use client'; + 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'; +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/launches/connect.with.wallet.tsx b/apps/frontend/src/components/new-launch/providers/devto/fonts/SFNS.woff2 similarity index 100% rename from apps/frontend/src/components/launches/connect.with.wallet.tsx rename to apps/frontend/src/components/new-launch/providers/devto/fonts/SFNS.woff2 diff --git a/apps/frontend/src/components/launches/providers/devto/select.organization.tsx b/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/devto/select.organization.tsx rename to apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx index d1aa52e8..49cbbb80 100644 --- a/apps/frontend/src/components/launches/providers/devto/select.organization.tsx +++ b/apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx @@ -1,8 +1,10 @@ +'use client'; + 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'; +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/launches/providers/discord/discord.channel.select.tsx b/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/discord/discord.channel.select.tsx rename to apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx index 51825ca8..a9a8a28a 100644 --- a/apps/frontend/src/components/launches/providers/discord/discord.channel.select.tsx +++ b/apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/discord/discord.provider.tsx b/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx similarity index 75% rename from apps/frontend/src/components/launches/providers/discord/discord.provider.tsx rename to apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx index 4b2eaf4f..904fd0a2 100644 --- a/apps/frontend/src/components/launches/providers/discord/discord.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx @@ -1,7 +1,9 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { DiscordChannelSelect } from '@gitroom/frontend/components/new-launch/providers/discord/discord.channel.select'; import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; const DiscordComponent: FC = () => { const form = useSettings(); diff --git a/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx similarity index 86% rename from apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx rename to apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx index 09d4d094..ce76011e 100644 --- a/apps/frontend/src/components/launches/providers/dribbble/dribbble.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx @@ -1,8 +1,10 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { DribbbleTeams } from '@gitroom/frontend/components/new-launch/providers/dribbble/dribbble.teams'; import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto'; const DribbbleSettings: FC = () => { const { register, control } = useSettings(); diff --git a/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx rename to apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx index da54e464..a948b76c 100644 --- a/apps/frontend/src/components/launches/providers/dribbble/dribbble.teams.tsx +++ b/apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/new-launch/providers/facebook/facebook.provider.tsx b/apps/frontend/src/components/new-launch/providers/facebook/facebook.provider.tsx new file mode 100644 index 00000000..26d3cd8b --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/facebook/facebook.provider.tsx @@ -0,0 +1,4 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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/new-launch/providers/hashnode/hashnode.provider.tsx similarity index 86% rename from apps/frontend/src/components/launches/providers/hashnode/hashnode.provider.tsx rename to apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx index 216fb34c..2eba8a4f 100644 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx @@ -1,20 +1,19 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { 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/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(); @@ -27,7 +26,6 @@ const HashnodePreview: FC = () => { return (
@@ -54,7 +52,6 @@ const HashnodePreview: FC = () => { whiteSpace: 'pre-wrap', color: 'black', }} - className={font.className} skipHtml={true} source={value.map((p) => p.content).join('\n')} /> diff --git a/apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx rename to apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx index fdaf25ef..d6052f15 100644 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.publications.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx rename to apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx index 32d22a52..352b76dd 100644 --- a/apps/frontend/src/components/launches/providers/hashnode/hashnode.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; 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 new file mode 100644 index 00000000..f7885dcd --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx @@ -0,0 +1,254 @@ +'use client'; + +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 { useShallow } from 'zustand/react/shallow'; +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 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 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?.integrationValue?.length) { + return internal.integrationValue; + } + + return global; + }, [internal, global, isGlobal]); + + const form = useForm({ + resolver: classValidatorResolver(dto || Empty), + ...(Object.keys(selectedIntegration.settings).length > 0 + ? { values: { ...selectedIntegration.settings } } + : {}), + mode: 'all', + criteriaMode: 'all', + reValidateMode: 'onChange', + }); + + 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 ( + p.integration), + 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/launches/providers/instagram/instagram.collaborators.tsx b/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx similarity index 93% rename from apps/frontend/src/components/launches/providers/instagram/instagram.collaborators.tsx rename to apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx index 4303108a..d4c2bdac 100644 --- a/apps/frontend/src/components/launches/providers/instagram/instagram.collaborators.tsx +++ b/apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx @@ -1,9 +1,11 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +'use client'; + +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/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 { InstagramCollaboratorsTags } from '@gitroom/frontend/components/new-launch/providers/instagram/instagram.tags'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; const postType = [ { diff --git a/apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx b/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx rename to apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx index a219bc59..8b7f9572 100644 --- a/apps/frontend/src/components/launches/providers/instagram/instagram.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx @@ -1,3 +1,5 @@ +'use client'; + import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { ReactTags } from 'react-tag-autocomplete'; diff --git a/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx b/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx similarity index 95% rename from apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx rename to apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx index b2f6ac09..cee55028 100644 --- a/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx @@ -1,5 +1,7 @@ +'use client'; + import { FC, useCallback } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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'; diff --git a/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx rename to apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx index d050ef72..0e4c170c 100644 --- a/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx b/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx similarity index 92% rename from apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx rename to apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx index 39a42e59..379285b3 100644 --- a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx @@ -1,4 +1,6 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +'use client'; + +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/launches/helpers/use.values'; diff --git a/apps/frontend/src/components/new-launch/providers/mastodon/mastodon.provider.tsx b/apps/frontend/src/components/new-launch/providers/mastodon/mastodon.provider.tsx new file mode 100644 index 00000000..77e3963a --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/mastodon/mastodon.provider.tsx @@ -0,0 +1,4 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; +export default withProvider(null, undefined, undefined, undefined, 500); diff --git a/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Bold Italic.ttf b/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Bold Italic.ttf new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Bold.ttf b/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Bold.ttf new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Italic.ttf b/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Italic.ttf new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Regular.ttf b/apps/frontend/src/components/new-launch/providers/medium/fonts/Charter Regular.ttf new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/launches/providers/medium/fonts/stylesheet.css b/apps/frontend/src/components/new-launch/providers/medium/fonts/stylesheet.css old mode 100755 new mode 100644 similarity index 100% rename from apps/frontend/src/components/launches/providers/medium/fonts/stylesheet.css rename to apps/frontend/src/components/new-launch/providers/medium/fonts/stylesheet.css diff --git a/apps/frontend/src/components/launches/providers/medium/medium.provider.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx similarity index 73% rename from apps/frontend/src/components/launches/providers/medium/medium.provider.tsx rename to apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx index bc085e91..c646f22c 100644 --- a/apps/frontend/src/components/launches/providers/medium/medium.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx @@ -1,40 +1,18 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { 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/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(); @@ -57,7 +35,6 @@ const MediumPreview: FC = () => { whiteSpace: 'pre-wrap', color: '#242424', }} - className={charter.className} skipHtml={true} source={value.map((p) => p.content).join('\n')} /> diff --git a/apps/frontend/src/components/launches/providers/medium/medium.publications.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/medium/medium.publications.tsx rename to apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx index 9ae07e55..12e7f856 100644 --- a/apps/frontend/src/components/launches/providers/medium/medium.publications.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/medium/medium.tags.tsx b/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/medium/medium.tags.tsx rename to apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx index b8c3fab2..53d0d165 100644 --- a/apps/frontend/src/components/launches/providers/medium/medium.tags.tsx +++ b/apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx @@ -1,3 +1,5 @@ +'use client'; + import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; import { ReactTags } from 'react-tag-autocomplete'; diff --git a/apps/frontend/src/components/new-launch/providers/nostr/nostr.provider.tsx b/apps/frontend/src/components/new-launch/providers/nostr/nostr.provider.tsx new file mode 100644 index 00000000..3ee13fe2 --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/nostr/nostr.provider.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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/new-launch/providers/pinterest/pinterest.board.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/pinterest/pinterest.board.tsx rename to apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx index 1618e2cf..d48570de 100644 --- a/apps/frontend/src/components/launches/providers/pinterest/pinterest.board.tsx +++ b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx similarity index 91% rename from apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx rename to apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx index ee97a378..a43ccb2b 100644 --- a/apps/frontend/src/components/launches/providers/pinterest/pinterest.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx @@ -1,7 +1,9 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { 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'; import { ColorPicker } from '@gitroom/react/form/color.picker'; diff --git a/apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx similarity index 97% rename from apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx rename to apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx index 287ffe14..dca11b33 100644 --- a/apps/frontend/src/components/launches/providers/reddit/reddit.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx @@ -1,8 +1,9 @@ +'use client'; + import { FC, useCallback } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { Subreddit } from '@gitroom/frontend/components/new-launch/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'; @@ -17,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/launches/providers/reddit/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/reddit/subreddit.tsx rename to apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx index 4eb104e2..8d14bc24 100644 --- a/apps/frontend/src/components/launches/providers/reddit/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; 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 new file mode 100644 index 00000000..292d01cd --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx @@ -0,0 +1,215 @@ +'use client'; + +import DevtoProvider from '@gitroom/frontend/components/new-launch/providers/devto/devto.provider'; +import XProvider from '@gitroom/frontend/components/new-launch/providers/x/x.provider'; +import LinkedinProvider from '@gitroom/frontend/components/new-launch/providers/linkedin/linkedin.provider'; +import RedditProvider from '@gitroom/frontend/components/new-launch/providers/reddit/reddit.provider'; +import MediumProvider from '@gitroom/frontend/components/new-launch/providers/medium/medium.provider'; +import HashnodeProvider from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.provider'; +import FacebookProvider from '@gitroom/frontend/components/new-launch/providers/facebook/facebook.provider'; +import InstagramProvider from '@gitroom/frontend/components/new-launch/providers/instagram/instagram.collaborators'; +import YoutubeProvider from '@gitroom/frontend/components/new-launch/providers/youtube/youtube.provider'; +import TiktokProvider from '@gitroom/frontend/components/new-launch/providers/tiktok/tiktok.provider'; +import PinterestProvider from '@gitroom/frontend/components/new-launch/providers/pinterest/pinterest.provider'; +import DribbbleProvider from '@gitroom/frontend/components/new-launch/providers/dribbble/dribbble.provider'; +import ThreadsProvider from '@gitroom/frontend/components/new-launch/providers/threads/threads.provider'; +import DiscordProvider from '@gitroom/frontend/components/new-launch/providers/discord/discord.provider'; +import SlackProvider from '@gitroom/frontend/components/new-launch/providers/slack/slack.provider'; +import MastodonProvider from '@gitroom/frontend/components/new-launch/providers/mastodon/mastodon.provider'; +import BlueskyProvider from '@gitroom/frontend/components/new-launch/providers/bluesky/bluesky.provider'; +import LemmyProvider from '@gitroom/frontend/components/new-launch/providers/lemmy/lemmy.provider'; +import WarpcastProvider from '@gitroom/frontend/components/new-launch/providers/warpcast/warpcast.provider'; +import TelegramProvider from '@gitroom/frontend/components/new-launch/providers/telegram/telegram.provider'; +import NostrProvider from '@gitroom/frontend/components/new-launch/providers/nostr/nostr.provider'; +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 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', + 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 = forwardRef((props, ref) => { + 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 () => { + return Promise.all( + 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) => + provider.identifier === integration.integration.identifier + ) || { + component: Empty, + }; + + return ( + + ); + })} +
+ ); +}); + +export const Empty: FC = () => { + return null; +}; diff --git a/apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx b/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx rename to apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx index 33207c60..6de498e3 100644 --- a/apps/frontend/src/components/launches/providers/slack/slack.channel.select.tsx +++ b/apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/slack/slack.provider.tsx b/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx similarity index 76% rename from apps/frontend/src/components/launches/providers/slack/slack.provider.tsx rename to apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx index 19e4d2ce..1ff82a6f 100644 --- a/apps/frontend/src/components/launches/providers/slack/slack.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx @@ -1,7 +1,9 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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 { 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 = () => { const form = useSettings(); diff --git a/apps/frontend/src/components/new-launch/providers/telegram/telegram.provider.tsx b/apps/frontend/src/components/new-launch/providers/telegram/telegram.provider.tsx new file mode 100644 index 00000000..2abe6db7 --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/telegram/telegram.provider.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; +export default withProvider( + null, + undefined, + undefined, + async () => { + return true; + }, + 4096 +); diff --git a/apps/frontend/src/components/new-launch/providers/threads/threads.provider.tsx b/apps/frontend/src/components/new-launch/providers/threads/threads.provider.tsx new file mode 100644 index 00000000..2fad94b6 --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/threads/threads.provider.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; +import { ThreadFinisher } from '@gitroom/frontend/components/new-launch/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/new-launch/providers/tiktok/tiktok.provider.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx rename to apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx index ff8908c5..879afdef 100644 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx @@ -1,3 +1,5 @@ +'use client'; + import { FC, ReactEventHandler, @@ -6,7 +8,7 @@ import { useMemo, useState, } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +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/launches/helpers/use.values'; import { Select } from '@gitroom/react/form/select'; diff --git a/apps/frontend/src/components/new-launch/providers/vk/vk.provider.tsx b/apps/frontend/src/components/new-launch/providers/vk/vk.provider.tsx new file mode 100644 index 00000000..3a2a4fd7 --- /dev/null +++ b/apps/frontend/src/components/new-launch/providers/vk/vk.provider.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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/new-launch/providers/warpcast/subreddit.tsx similarity index 99% rename from apps/frontend/src/components/launches/providers/warpcast/subreddit.tsx rename to apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx index bbacbe2c..3bcf2e02 100644 --- a/apps/frontend/src/components/launches/providers/warpcast/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx @@ -1,3 +1,5 @@ +'use client'; + 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'; diff --git a/apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx b/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx similarity index 94% rename from apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx rename to apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx index f2714669..9f23cdae 100644 --- a/apps/frontend/src/components/launches/providers/warpcast/warpcast.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx @@ -1,4 +1,6 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/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'; diff --git a/apps/frontend/src/components/new-launch/providers/x/fonts/Chirp-Bold.woff2 b/apps/frontend/src/components/new-launch/providers/x/fonts/Chirp-Bold.woff2 new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/new-launch/providers/x/fonts/Chirp-Regular.woff2 b/apps/frontend/src/components/new-launch/providers/x/fonts/Chirp-Regular.woff2 new file mode 100644 index 00000000..e69de29b diff --git a/apps/frontend/src/components/launches/providers/x/x.provider.tsx b/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx similarity index 93% rename from apps/frontend/src/components/launches/providers/x/x.provider.tsx rename to apps/frontend/src/components/new-launch/providers/x/x.provider.tsx index 9573fc3a..b3297879 100644 --- a/apps/frontend/src/components/launches/providers/x/x.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/x/x.provider.tsx @@ -1,5 +1,7 @@ -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; -import { ThreadFinisher } from '@gitroom/frontend/components/launches/finisher/thread.finisher'; +'use client'; + +import { withProvider } from '@gitroom/frontend/components/new-launch/providers/high.order.provider'; +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/launches/helpers/use.values'; diff --git a/apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx b/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx similarity index 89% rename from apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx rename to apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx index e636c93c..32975129 100644 --- a/apps/frontend/src/components/launches/providers/youtube/youtube.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx @@ -1,9 +1,11 @@ +'use client'; + import { FC } from 'react'; -import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +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/launches/helpers/use.values'; import { Input } from '@gitroom/react/form/input'; -import { MediumTags } from '@gitroom/frontend/components/launches/providers/medium/medium.tags'; +import { MediumTags } from '@gitroom/frontend/components/new-launch/providers/medium/medium.tags'; import { MediaComponent } from '@gitroom/frontend/components/media/media.component'; import { Select } from '@gitroom/react/form/select'; const type = [ diff --git a/apps/frontend/src/components/new-launch/select.current.tsx b/apps/frontend/src/components/new-launch/select.current.tsx new file mode 100644 index 00000000..0e4283a0 --- /dev/null +++ b/apps/frontend/src/components/new-launch/select.current.tsx @@ -0,0 +1,150 @@ +'use client'; + +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, 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); + + useEffect(() => { + if (!hide) { + return; + } + + setHide(false); + }, [hide]); + + return ( + <> +
+
+
{ + setHide(true); + setCurrent('global'); + }} + className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + > +
+ + + +
+
+ {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 new file mode 100644 index 00000000..a66c897f --- /dev/null +++ b/apps/frontend/src/components/new-launch/store.ts @@ -0,0 +1,448 @@ +'use client'; + +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; + content: string; + media: { id: string; path: string }[]; +} + +interface Internal { + integration: Integrations; + integrationValue: Values[]; +} + +interface SelectedIntegrations { + settings: any; + integration: Integrations; + ref?: RefObject; +} + +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[]; + internal: Internal[]; + addGlobalValue: (index: number, value: Values[]) => void; + addInternalValue: ( + index: number, + 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: (index: number, direction: 'up' | 'down') => void; + changeOrderInternal: ( + integrationId: string, + 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 }[] + ) => void; + removeGlobalValueMedia: (index: number, mediaIndex: number) => void; + setInternalValueText: ( + integrationId: string, + index: number, + content: string + ) => void; + addInternalValueMedia: ( + integrationId: string, + index: number, + media: { id: string; path: string }[] + ) => void; + removeInternalValueMedia: ( + integrationId: string, + index: number, + mediaIndex: number + ) => void; + setAllIntegrations: (integrations: Integrations[]) => void; + setCurrent: (current: string) => void; + addOrRemoveSelectedIntegration: ( + integration: Integrations, + 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[], + internal: [] as Internal[], +}; + +export const useLaunchStore = create()((set) => ({ + ...initialState, + setCurrent: (current: string) => + set((state) => ({ + current: current, + })), + addOrRemoveSelectedIntegration: ( + integration: Integrations, + settings: any + ) => { + set((state) => { + const existingIndex = state.selectedIntegrations.findIndex( + (i) => i.integration.id === integration.id + ); + + if (existingIndex > -1) { + return { + selectedIntegrations: state.selectedIntegrations.filter( + (_, index) => index !== existingIndex + ), + }; + } + + return { + selectedIntegrations: [ + ...state.selectedIntegrations, + { integration, settings, ref: createRef() }, + ], + }; + }); + }, + addGlobalValue: (index: number, value: Values[]) => + set((state) => { + if (!state.global.length) { + return { global: value }; + } + + return { + global: state.global.reduce((acc, item, i) => { + acc.push(item); + if (i === index) { + acc.push(...value); + } + return acc; + }, []), + }; + }), + // 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 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) => ({ + global: state.global.filter((_, i) => i !== index), + })), + deleteInternalValue: (integrationId: string, index: number) => + set((state) => { + return { + internal: state.internal.map((i) => { + if (i.integration.id === integrationId) { + return { + ...i, + integrationValue: i.integrationValue.filter( + (_, idx) => idx !== index + ), + }; + } + return i; + }), + }; + }), + addRemoveInternal: (integrationId: string) => + set((state) => { + const integration = state.selectedIntegrations.find( + (i) => i.integration.id === integrationId + ); + const findIntegrationIndex = state.internal.findIndex( + (i) => i.integration.id === integrationId + ); + + if (findIntegrationIndex > -1) { + return { + internal: state.internal.filter( + (i) => i.integration.id !== integrationId + ), + }; + } + + return { + internal: [ + ...state.internal, + { + integration: integration.integration, + integrationValue: state.global.slice(0).map((p) => p), + }, + ], + }; + }), + changeOrderGlobal: (index: number, direction: 'up' | 'down') => + set((state) => { + return { + global: arrayMoveImmutable( + state.global, + index, + direction === 'up' ? index - 1 : index + 1 + ), + }; + }), + changeOrderInternal: ( + integrationId: string, + index: number, + direction: 'up' | 'down' + ) => + set((state) => { + 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) => ({ + global: state.global.map((item, i) => + 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) => + i === index ? { ...item, media: [...item.media, ...media] } : item + ), + })), + removeGlobalValueMedia: (index: number, mediaIndex: number) => + set((state) => ({ + global: state.global.map((item, i) => + i === index + ? { + ...item, + media: item.media.filter((_, idx) => idx !== mediaIndex), + } + : item + ), + })), + setInternalValueText: ( + integrationId: string, + index: number, + content: string + ) => { + set((state) => ({ + internal: state.internal.map((item) => + item.integration.id === integrationId + ? { + ...item, + integrationValue: item.integrationValue.map((v, i) => + i === index ? { ...v, content } : v + ), + } + : item + ), + })); + }, + addInternalValueMedia: ( + integrationId: string, + index: number, + media: { id: string; path: string }[] + ) => + set((state) => ({ + internal: state.internal.map((item) => + item.integration.id === integrationId + ? { + ...item, + integrationValue: item.integrationValue.map((v, i) => + i === index ? { ...v, media: [...v.media, ...media] } : v + ), + } + : item + ), + })), + removeInternalValueMedia: ( + integrationId: string, + index: number, + mediaIndex: number + ) => + set((state) => ({ + internal: state.internal.map((item) => + item.integration.id === integrationId + ? { + ...item, + integrationValue: item.integrationValue.map((v, i) => + i === index + ? { + ...v, + media: v.media.filter((_, idx) => idx !== mediaIndex), + } + : v + ), + } + : item + ), + })), + reset: () => + set((state) => ({ + ...state, + ...initialState, + })), + setAllIntegrations: (integrations: Integrations[]) => + 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/launches/u.text.tsx b/apps/frontend/src/components/new-launch/u.text.tsx similarity index 96% rename from apps/frontend/src/components/launches/u.text.tsx rename to apps/frontend/src/components/new-launch/u.text.tsx index 67f63e43..6ea6acae 100644 --- a/apps/frontend/src/components/launches/u.text.tsx +++ b/apps/frontend/src/components/new-launch/u.text.tsx @@ -1,3 +1,5 @@ +'use client'; + import { FC, useCallback } from 'react'; import { Editor, Transforms } from 'slate'; import { ReactEditor } from 'slate-react'; @@ -91,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 ad80f95d..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", @@ -213,7 +213,8 @@ "ws": "^8.18.0", "yargs": "^17.7.2", "yup": "^1.4.0", - "zod": "^3.24.1" + "zod": "^3.24.1", + "zustand": "^5.0.5" }, "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.32", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9089106e..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) @@ -519,6 +519,9 @@ importers: zod: specifier: ^3.24.1 version: 3.24.4 + zustand: + specifier: ^5.0.5 + version: 5.0.5(@types/react@18.3.1)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) devDependencies: '@crxjs/vite-plugin': specifier: ^2.0.0-beta.32 @@ -13068,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 @@ -15760,6 +15763,24 @@ packages: zod@3.24.4: resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} + zustand@5.0.5: + resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -18147,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: @@ -32381,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 @@ -35453,4 +35474,11 @@ snapshots: zod@3.24.4: {} + zustand@5.0.5(@types/react@18.3.1)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.1 + immer: 9.0.21 + react: 18.3.1 + use-sync-external-store: 1.5.0(react@18.3.1) + zwitch@2.0.4: {}