'use client'; import React, { FC, Fragment, MouseEventHandler, useCallback, useEffect, useState, } from 'react'; import dayjs from 'dayjs'; import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; import clsx from 'clsx'; import MDEditor, { commands } from '@uiw/react-md-editor'; 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 { newImage } from '@gitroom/frontend/components/launches/helpers/new.image.component'; 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 { v4 as uuidv4 } from 'uuid'; import { useSWRConfig } from 'swr'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector'; 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 { linkedinCompany } from '@gitroom/frontend/components/launches/helpers/linkedin.component'; export const AddEditModal: FC<{ date: dayjs.Dayjs; integrations: Integrations[]; }> = (props) => { const { date, integrations } = props; const [dateState, setDateState] = useState(date); const { mutate } = useSWRConfig(); // selected integrations to allow edit const [selectedIntegrations, setSelectedIntegrations] = useState< Integrations[] >([]); // value of each editor const [value, setValue] = useState< Array<{ content: string; id?: string; image?: Array<{ id: string; path: string }>; }> >([{ content: '' }]); const fetch = useFetch(); // 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(); const [showError, setShowError] = useState(false); // hook to open a new modal const modal = useModals(); // are we in edit mode? const existingData = useExistingData(); 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)!, ]); } }, [existingData.integration]); // if the user exit the popup we reset the global variable with all the values useEffect(() => { return () => { 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 ( await deleteDialog( 'Are you sure you want to close this modal? (all data will be lost)', 'Yes, close it!' ) ) { modal.closeAll(); } }, []); // 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 () => { if (type === 'delete') { if ( !(await deleteDialog( 'Are you sure you want to delete this post?', 'Yes, delete it!' )) ) { return; } await fetch(`/posts/${existingData.group}`, { method: 'DELETE', }); mutate('/posts'); 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(), })); for (const key of allKeys) { if (key.value.some((p) => !p.content || p.content.length < 6)) { setShowError(true); return; } if (!key.valid) { await key.trigger(); moveToIntegration(key?.integration?.id!); return; } } await fetch('/posts', { method: 'POST', body: JSON.stringify({ type, date: dateState.utc().format('YYYY-MM-DDTHH:mm:ss'), posts: allKeys, }), }); existingData.group = uuidv4(); mutate('/posts'); toaster.show( !existingData.integration ? 'Added successfully' : 'Updated successfully' ); modal.closeAll(); }, [] ); return ( <>
{!existingData.integration && ( )}
{!existingData.integration && !showHide.hideTopEditor ? ( <>
You are in global editing mode
{value.map((p, index) => (
1 ? 150 : 250} commands={[ ...commands .getCommands() .filter((f) => f.name !== 'image'), newImage, postSelector(dateState), ]} value={p.content} preview="edit" // @ts-ignore onChange={changeValue(index)} /> {showError && (!p.content || p.content.length < 6) && (
The post should be at least 6 characters long
)}
{value.length > 1 && (
Delete Post
)}
))} ) : null}
{!!existingData.integration && ( )}
{!!selectedIntegrations.length && (
)}
); };