From 37c45743a8c0a1e2a3c85d133d9406ab3714b96c Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 25 Dec 2025 18:29:12 +0700 Subject: [PATCH] feat: refactor for creation modal --- apps/frontend/src/app/colors.scss | 8 + apps/frontend/src/app/global.scss | 49 +- .../src/components/launches/ai.image.tsx | 42 +- .../src/components/launches/ai.video.tsx | 42 +- .../src/components/launches/calendar.tsx | 4 +- .../launches/general.preview.component.tsx | 2 +- .../launches/helpers/date.picker.tsx | 25 +- .../launches/information.component.tsx | 205 +++++ .../components/launches/repeat.component.tsx | 129 ++- .../components/launches/select.customer.tsx | 130 ++- .../components/launches/tags.component.tsx | 317 ++++++- .../src/components/launches/up.down.arrow.tsx | 31 +- .../src/components/layout/drop.files.tsx | 4 +- .../src/components/layout/new-modal.tsx | 47 +- .../src/components/layout/support.tsx | 1 + .../src/components/media/media.component.tsx | 780 ++++++++++-------- .../src/components/media/new.uploader.tsx | 4 +- .../src/components/new-launch/a.component.tsx | 40 +- .../components/new-launch/add.edit.modal.tsx | 14 +- .../components/new-launch/add.post.button.tsx | 21 +- .../src/components/new-launch/bold.text.tsx | 35 +- .../new-launch/bullets.component.tsx | 19 +- .../src/components/new-launch/editor.tsx | 564 +++++++++---- .../new-launch/heading.component.tsx | 23 +- .../components/new-launch/manage.modal.tsx | 592 ++++++++++++- .../new-launch/picks.socials.component.tsx | 40 +- .../providers/high.order.provider.tsx | 59 +- .../providers/show.all.providers.tsx | 7 - .../components/new-launch/select.current.tsx | 80 +- .../src/components/new-launch/u.text.tsx | 39 +- .../new-layout/layout.component.tsx | 1 - apps/frontend/src/components/sets/sets.tsx | 7 +- apps/frontend/src/components/signature.tsx | 43 +- .../third-parties/third-party.media.tsx | 12 +- apps/frontend/tailwind.config.js | 10 + .../database/prisma/media/media.repository.ts | 9 +- 36 files changed, 2522 insertions(+), 913 deletions(-) create mode 100644 apps/frontend/src/components/launches/information.component.tsx diff --git a/apps/frontend/src/app/colors.scss b/apps/frontend/src/app/colors.scss index c0082f70..9ae668e9 100644 --- a/apps/frontend/src/app/colors.scss +++ b/apps/frontend/src/app/colors.scss @@ -1,7 +1,11 @@ :root { .dark { + --new-back-drop: #000; + --new-settings: #242323; + --new-border: #252525; --new-bgColor: #0e0e0e; --new-bgColorInner: #1a1919; + --new-sep: #454444; --new-bgLineColor: #212121; --new-textItemFocused: #1a1919; --new-textItemBlur: #999999; @@ -26,6 +30,10 @@ --popup-color: rgba(65, 64, 66, 0.3); } .light { + --new-back-drop: #2d1b57; + --new-settings: #ECEEF1; + --new-sep: #D5D9DD; + --new-border: #EAECEE; --new-bgColor: #f0f2f4; --new-bgColorInner: #ffffff; --new-bgLineColor: #e7e9eb; diff --git a/apps/frontend/src/app/global.scss b/apps/frontend/src/app/global.scss index a5f342b9..5a524d1f 100644 --- a/apps/frontend/src/app/global.scss +++ b/apps/frontend/src/app/global.scss @@ -679,6 +679,9 @@ html[dir='rtl'] [dir='ltr'] { .blur-xs { filter: blur(4px); } +.blur-s { + filter: blur(5px); +} .agent { .copilotKitInputContainer { @@ -736,7 +739,45 @@ html[dir='rtl'] [dir='ltr'] { background: transparent !important; } -//html body iframe[title="Stripe developer tools frame"] { -// display: none !important; -// height: 0 !important; -//} \ No newline at end of file +.menu-shadow { + border-radius: 12px; + box-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.5); +} + +.post-now { + box-shadow: -33px 57px 18px 0 rgba(0, 0, 0, 0.01), -21px 36px 17px 0 rgba(0, 0, 0, 0.06), -12px 20px 14px 0 rgba(0, 0, 0, 0.20), -5px 9px 11px 0 rgba(0, 0, 0, 0.34), -1px 2px 6px 0 rgba(0, 0, 0, 0.39); +} + +.uppyChange .uppy-Dashboard-inner { + width: 100% !important; +} + +.btnSub:disabled .arrow-change { + display: none !important; +} +.btnSub:disabled + button { + display: none !important; +} + +.tiptap p.is-editor-empty:first-child::before { + color: #adb5bd; + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; +} + +.w8-max { + width: calc(100% / 6); + max-width: calc(100% / 6); +} + +.withp3 { + width: calc(100% + 9px); + height: calc(100% + 6px); +} + +.forceChange .changeColor { + background: var(--new-btn-primary) !important; + color: #fff !important; +} \ No newline at end of file diff --git a/apps/frontend/src/components/launches/ai.image.tsx b/apps/frontend/src/components/launches/ai.image.tsx index 9cfa00f8..611af14a 100644 --- a/apps/frontend/src/components/launches/ai.image.tsx +++ b/apps/frontend/src/components/launches/ai.image.tsx @@ -28,7 +28,7 @@ export const AiImage: FC<{ const t = useT(); const { value, onChange } = props; const [loading, setLoading] = useState(false); - const setLocked = useLaunchStore(p => p.setLocked); + const setLocked = useLaunchStore((p) => p.setLocked); const fetch = useFetch(); const generateImage = useCallback( (type: string) => async () => { @@ -59,7 +59,7 @@ ${type} ); return (
-
- + {value.length >= 30 && !loading && ( -
+
    {list.map((p) => (
  • diff --git a/apps/frontend/src/components/launches/ai.video.tsx b/apps/frontend/src/components/launches/ai.video.tsx index ed0fd2c4..5b37f389 100644 --- a/apps/frontend/src/components/launches/ai.video.tsx +++ b/apps/frontend/src/components/launches/ai.video.tsx @@ -44,9 +44,9 @@ export const Modal: FC<{ setLocked(true); const customParams = form.getValues(); - if (!await form.trigger()) { + if (!(await form.trigger())) { toaster.show('Please fill all required fields', 'warning'); - return ; + return; } try { const image = await fetch(`/media/generate-video`, { @@ -199,7 +199,7 @@ export const AiVideo: FC<{ /> )}
    -
    - +
{value.length >= 30 && !loading && ( -
+
    {data.map((p: any) => (
  • +
    {renderContent.map((value, index) => (
    -
    - {date.format(isUSCitizen() ? 'MM/DD/YYYY hh:mm A' : 'DD/MM/YYYY HH:mm')} -
    +
    + {date.format(isUSCitizen() ? 'MM/DD/YYYY hh:mm A' : 'DD/MM/YYYY HH:mm')} +
    {open && (
    e.stopPropagation()} - className="animate-normalFadeDown absolute top-[100%] mt-[16px] end-0 bg-sixth border border-tableBorder text-textColor rounded-[16px] z-[300] p-[16px] flex flex-col" + className="animate-fadeIn absolute bottom-[100%] mb-[16px] start-[50%] -translate-x-[50%] bg-sixth border border-tableBorder text-textColor rounded-[16px] z-[300] p-[16px] flex flex-col" >
    )} diff --git a/apps/frontend/src/components/launches/information.component.tsx b/apps/frontend/src/components/launches/information.component.tsx new file mode 100644 index 00000000..38ed5f21 --- /dev/null +++ b/apps/frontend/src/components/launches/information.component.tsx @@ -0,0 +1,205 @@ +import React, { FC, Fragment, useMemo } from 'react'; +import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; +import { useShallow } from 'zustand/react/shallow'; +import clsx from 'clsx'; +import Image from 'next/image'; +import { capitalize } from 'lodash'; + +const Valid: FC = () => { + return ( + + + + ); +}; + +const Invalid: FC = () => { + return ( + + + + + + + + + + + ); +}; +export const InformationComponent: FC<{ + chars: Record; + totalChars: number; + totalAllowedChars: number; + isPicture: boolean; +}> = ({ totalChars, totalAllowedChars, chars, isPicture }) => { + const { isGlobal, selectedIntegrations, internal } = useLaunchStore( + useShallow((state) => ({ + isGlobal: state.current === 'global', + selectedIntegrations: state.selectedIntegrations, + internal: state.internal, + })) + ); + + const isInternal = useMemo(() => { + if (!isGlobal) { + return []; + } + return selectedIntegrations.map((p) => { + const findIt = internal.find( + (a) => a.integration.id === p.integration.id + ); + + return !!findIt; + }); + }, [isGlobal, internal, selectedIntegrations]); + + const isValid = useMemo(() => { + if (!isPicture && !totalChars) { + return false; + } + + if (totalChars > totalAllowedChars && !isGlobal) { + return false; + } + + if (totalChars <= totalAllowedChars && !isGlobal) { + return true; + } + + if ( + selectedIntegrations.some((p, index) => { + if (isInternal[index]) { + return false; + } + + return totalChars > (chars?.[p.integration.id] || 0); + }) + ) { + return false; + } + + return true; + }, [totalAllowedChars, totalChars, isInternal, isPicture]); + + return ( +
    + {isValid ? : } + + {!isGlobal && ( +
    + {totalChars}/{totalAllowedChars} +
    + )} + {((isGlobal && selectedIntegrations.length) || !isValid) && ( + + + + )} + {((isGlobal && selectedIntegrations.length) || !isValid) && ( + + )} +
    + ); +}; diff --git a/apps/frontend/src/components/launches/repeat.component.tsx b/apps/frontend/src/components/launches/repeat.component.tsx index 883255bd..416426ab 100644 --- a/apps/frontend/src/components/launches/repeat.component.tsx +++ b/apps/frontend/src/components/launches/repeat.component.tsx @@ -1,42 +1,49 @@ -import { FC } from 'react'; +import { FC, useMemo, useState } from 'react'; import { Select } from '@gitroom/react/form/select'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useClickOutside } from '@mantine/hooks'; +import { isUSCitizen } from '@gitroom/frontend/components/launches/helpers/isuscitizen.utils'; +import clsx from 'clsx'; const list = [ { value: 1, - label: 'Every Day', + label: 'Day', }, { value: 2, - label: 'Every Two Days', + label: 'Two Days', }, { value: 3, - label: 'Every Three Days', + label: 'Three Days', }, { value: 4, - label: 'Every Four Days', + label: 'Four Days', }, { value: 5, - label: 'Every Five Days', + label: 'Five Days', }, { value: 6, - label: 'Every Six Days', + label: 'Six Days', }, { value: 7, - label: 'Every Week', + label: 'Week', }, { value: 14, - label: 'Every Two Weeks', + label: 'Two Weeks', }, { value: 30, - label: 'Every Month', + label: 'Month', + }, + { + value: null, + label: 'Cancel', }, ]; export const RepeatComponent: FC<{ @@ -45,21 +52,95 @@ export const RepeatComponent: FC<{ }> = (props) => { const { repeat } = props; const t = useT(); + const [isOpen, setIsOpen] = useState(false); + + const ref = useClickOutside(() => { + if (!isOpen) { + return; + } + setIsOpen(false); + }); + + const everyLabel = useMemo(() => { + if (!repeat) { + return ''; + } + return list.find((p) => p.value === repeat)?.label; + }, [repeat]); + return ( - +
    setIsOpen(!isOpen)} + className="px-[16px] justify-center flex gap-[8px] items-center h-full select-none flex-1" + > +
    + + + + + + + + + + +
    +
    + {repeat + ? `Repeat Post Every ${everyLabel}` + : t('repeat_post_every', 'Repeat Post Every...')} +
    +
    + + + +
    +
    + {isOpen && ( +
    + {list.map((p) => ( +
    { + props.onChange(Number(p.value)); + setIsOpen(false); + }} + key={p.label} + className="h-[40px] py-[8px] px-[20px] -mx-[12px] hover:bg-newBgColor" + > + {p.label} +
    + ))} +
    + )} +
    ); }; diff --git a/apps/frontend/src/components/launches/select.customer.tsx b/apps/frontend/src/components/launches/select.customer.tsx index 82022090..157e5365 100644 --- a/apps/frontend/src/components/launches/select.customer.tsx +++ b/apps/frontend/src/components/launches/select.customer.tsx @@ -1,43 +1,127 @@ -import { Select } from '@gitroom/react/form/select'; import { uniqBy } from 'lodash'; -import React, { FC, useMemo, useState } from 'react'; +import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import clsx from 'clsx'; +import { useClickOutside } from '@mantine/hooks'; +import { useToaster } from '@gitroom/react/toaster/toaster'; +import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; +import { useShallow } from 'zustand/react/shallow'; + export const SelectCustomer: FC<{ onChange: (value: string) => void; integrations: Integrations[]; customer?: string; }> = (props) => { const { onChange, integrations, customer: currentCustomer } = props; + const { setCurrent } = useLaunchStore( + useShallow((state) => ({ + setCurrent: state.setCurrent, + })) + ); + const toaster = useToaster(); const t = useT(); const [customer, setCustomer] = useState(currentCustomer || ''); + const [pos, setPos] = useState({}); + const [open, setOpen] = useState(false); + const ref = useClickOutside(() => { + if (open) { + setOpen(false); + } + }); + + const openClose = useCallback(() => { + if (open) { + setOpen(false); + return; + } + + const { x, y, width, height } = ref.current?.getBoundingClientRect(); + setPos({ top: y + height, left: x }); + setOpen(true); + }, [open]); + const totalCustomers = useMemo(() => { return uniqBy(integrations, (i) => i?.customer?.id).length; }, [integrations]); if (totalCustomers <= 1) { return null; } + return ( - +
    +
    +
    + + + +
    +
    + + + +
    +
    + {open && ( +
    +
    + Customers +
    + {uniqBy(integrations, (u) => u?.customer?.name) + .filter((f) => f.customer?.name) + .map((p) => ( +
    { + toaster.show( + t('customer_socials_selected', 'Customer socials selected'), + 'success' + ); + setCustomer(p.customer?.id); + onChange(p.customer?.id); + setOpen(false); + setCurrent('global') + }} + key={p.customer?.id} + className="p-[12px] hover:bg-newBgColor text-[14px] font-[500] h-[32px] flex items-center" + > + {p.customer?.name} +
    + ))} +
    + )} +
    ); }; diff --git a/apps/frontend/src/components/launches/tags.component.tsx b/apps/frontend/src/components/launches/tags.component.tsx index b7da39e4..457233b5 100644 --- a/apps/frontend/src/components/launches/tags.component.tsx +++ b/apps/frontend/src/components/launches/tags.component.tsx @@ -8,6 +8,10 @@ import { ColorPicker } from '@gitroom/react/form/color.picker'; import { Button } from '@gitroom/react/form/button'; import { uniqBy } from 'lodash'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useClickOutside } from '@mantine/hooks'; +import clsx from 'clsx'; +import { useModals } from '@gitroom/frontend/components/layout/new-modal'; + export const TagsComponent: FC<{ name: string; label: string; @@ -18,6 +22,254 @@ export const TagsComponent: FC<{ name: string; }; }) => void; +}> = (props) => { + const fetch = useFetch(); + + const loadTags = useCallback(async () => { + return (await fetch('/posts/tags')).json(); + }, []); + + const { data, isLoading, mutate } = useSWR('load-tags', loadTags); + + if (isLoading) { + return null; + } + + return ; +}; + +export const TagsComponentInner: FC<{ + name: string; + label: string; + initial: any[]; + allTags: any; + mutate: () => Promise; + onChange: (event: { + target: { + value: any[]; + name: string; + }; + }) => void; +}> = ({ initial, onChange, name, mutate, allTags: data }) => { + const t = useT(); + const [isOpen, setIsOpen] = useState(false); + const [tagValue, setTagValue] = useState( + (initial?.slice(0) || []).map((p: any) => { + return data?.tags.find((a: any) => a.name === p.value) || p; + }) + ); + const modals = useModals(); + + const ref = useClickOutside(() => { + if (!isOpen) { + return; + } + setIsOpen(false); + }); + + const addTag = useCallback(async () => { + const val: string | undefined = await new Promise((resolve) => { + modals.openModal({ + title: 'Add new tag', + children: (close) => ( + + ), + }); + }); + + const newValues = await mutate(); + + if (!val) { + return; + } + + const newTag = newValues.tags.find((p: any) => p.name === val); + if (newTag) { + const modify = [...tagValue, newTag]; + setTagValue(modify); + onChange({ + target: { + value: modify, + name, + }, + }); + } + }, []); + + return ( +
    +
    setIsOpen(!isOpen)} + className="px-[16px] justify-center flex gap-[8px] items-center h-full select-none flex-1" + > +
    + + + +
    +
    + {tagValue.length === 0 ? ( + 'Add New Tag' + ) : ( + <> +
    + + {tagValue[0].name} + +
    + {tagValue.length > 1 ? +{tagValue.length - 1} : null} + + )} +
    +
    + + + +
    +
    + {isOpen && ( +
    + {(data?.tags || []).map((p: any) => ( +
    { + const exists = !!tagValue.find((a) => a.id === p.id); + let modify = []; + if (exists) { + modify = tagValue.filter((a) => a.id !== p.id); + } else { + modify = [...tagValue, p]; + } + setTagValue(modify); + onChange({ + target: { + value: modify.map((p: any) => ({ + label: p.name, + value: p.name, + })), + name, + }, + }); + }} + key={p.name} + className="h-[40px] py-[8px] px-[20px] -mx-[12px] flex gap-[8px]" + > + {}} + value={!!tagValue.find((a) => a.id === p.id)} + /> +
    + + {p.name} + +
    +
    + ))} +
    +
    + + + +
    +
    Add New Tag
    +
    +
    + )} +
    + ); +}; + +const Check: FC<{ value: boolean; onChange: (value: boolean) => void }> = ({ + value, + onChange, +}) => { + return ( +
    onChange(!value)} + className={clsx( + 'text-[10px] font-[500] text-center flex border border-btnSimple rounded-[6px] w-[20px] h-[20px] justify-center items-center', + value && 'bg-[#612BD3]' + )} + > + {value ? ( + + + + ) : ( + '' + )} +
    + ); +}; +export const TagsComponentA: FC<{ + name: string; + label: string; + initial: any[]; + onChange: (event: { + target: { + value: any[]; + name: string; + }; + }) => void; }> = (props) => { const { onChange, name, initial } = props; const fetch = useFetch(); @@ -228,53 +480,28 @@ const ShowModal: FC<{ }), }); resolve(tagName); + close(); }, [tagName, color, id]); return ( -
    -
    - - - -
    - setTagName(e.target.value)} - /> - setColor(e.target.value)} - label="Tag Color" - name="color" - value={color} - enabled={true} - canBeCancelled={false} - /> - -
    -
    +
    + setTagName(e.target.value)} + /> + setColor(e.target.value)} + label="Tag Color" + name="color" + value={color} + enabled={true} + canBeCancelled={false} + /> +
    ); }; diff --git a/apps/frontend/src/components/launches/up.down.arrow.tsx b/apps/frontend/src/components/launches/up.down.arrow.tsx index b4ad786c..f88b29fd 100644 --- a/apps/frontend/src/components/launches/up.down.arrow.tsx +++ b/apps/frontend/src/components/launches/up.down.arrow.tsx @@ -7,17 +7,20 @@ const Arrow: FC<{ return ( ); @@ -35,14 +38,14 @@ export const UpDownArrow: FC<{ [] ); return ( -
    +
    + ); }, []); - const t = useT(); - return ( -
    -
    - -
    -
    -
    - {!props.standalone ? ( - - ) : ( -
    - )} -
    - {!props.standalone ? ( - - ) : ( -
    - )} - -
    - {t( - 'select_or_upload_pictures_maximum_5_at_a_time', - 'Select or upload pictures (maximum 5 at a time)' - )} -
    - {t( - 'you_can_also_drag_drop_pictures', - 'You can also drag & drop pictures' - )} -
    - - {!!mediaList.length && ( - <> -
    -
    -
    - -
    -
    - - )} + +
    +
    + {!isLoading && !!data?.results?.length && ( +
    + Select or upload pictures (maximum 5 at a time).{'\n'} + You can also drag & drop pictures.
    -
    - {!mediaList.length ? ( -
    -
    - {t( - 'you_don_t_have_any_assets_yet', - "You don't have any assets yet." - )} -
    -
    - {t( - 'click_the_button_below_to_upload_one', - 'Click the button below to upload one' - )} -
    -
    - -
    + )} + + {!isLoading && !!data?.results?.length && btn} +
    +
    +
    + +
    +
    +
    +
    +
    + {!isLoading && !data?.results?.length && ( + <> + + + + + + + + + + +
    + You don't have any media yet
    - ) : ( - <> - {selectedMedia.length > 0 && ( -
    - -
    - )} - - )} - {mediaList - .filter((f) => { - if (type === 'video') { - return f.path.indexOf('mp4') > -1; - } else if (type === 'image') { - return f.path.indexOf('mp4') === -1; - } - return true; - }) - .map((media) => ( +
    + Select or upload pictures (maximum 5 at a time). {'\n'} + You can also drag & drop pictures. +
    +
    {btn}
    + + )} + {isLoading && ( + <> + {[...new Array(16)].map((_, i) => (
    p.id === media.id) - ? 'border-4 border-forth' - : 'border-tableBorder border-2' + 'px-[3px] py-[3px] float-left rounded-[6px] cursor-pointer w8-max aspect-square' )} - onClick={props.standalone ? () => {} : setNewMedia(media)} + key={i} > -
    - X -
    - - {media.path.indexOf('mp4') > -1 ? ( - - ) : ( - media - )} +
    ))} -
    - {(pages || 0) > 1 && ( - + + )} + {data?.results + ?.filter((f: any) => { + if (type === 'video') { + return f.path.indexOf('mp4') > -1; + } else if (type === 'image') { + return f.path.indexOf('mp4') === -1; + } + return true; + }) + .map((media: any) => ( +
    +
    p.id === media.id) + ? 'border-[#612BD3]' + : 'border-transparent' + )} + onClick={addRemoveSelected(media)} + > + {!!selected.find((p: any) => p.id === media.id) ? ( +
    + {selected.findIndex((z: any) => z.id === media.id) + 1} +
    + ) : ( + + + + + )} +
    + {media.path.indexOf('mp4') > -1 ? ( + + ) : ( + media + )} +
    +
    +
    + ))} +
    +
    + {(data?.pages || 0) > 1 && ( + + )} + {!standalone && ( +
    + + {!isLoading && !!data?.results?.length && ( + )}
    - + )}
    -
    + ); }; export const MultiMediaComponent: FC<{ @@ -543,6 +569,8 @@ export const MultiMediaComponent: FC<{ error?: any; onOpen?: () => void; onClose?: () => void; + toolBar?: React.ReactNode; + information?: React.ReactNode; onChange: (event: { target: { name: string; @@ -557,8 +585,6 @@ export const MultiMediaComponent: FC<{ }) => void; }> = (props) => { const { - onOpen, - onClose, name, error, text, @@ -566,6 +592,8 @@ export const MultiMediaComponent: FC<{ value, allData, dummy, + toolBar, + information, } = props; const user = useUser(); const modals = useModals(); @@ -574,7 +602,7 @@ export const MultiMediaComponent: FC<{ setCurrentMedia(value); } }, [value]); - const [mediaModal, setMediaModal] = useState(false); + const [currentMedia, setCurrentMedia] = useState(value); const mediaDirectory = useMediaDirectory(); const changeMedia = useCallback( @@ -603,7 +631,12 @@ export const MultiMediaComponent: FC<{ ); const showModal = useCallback(() => { modals.openModal({ + title: 'Media Library', askClose: false, + closeOnEscape: true, + fullScreen: true, + size: 'calc(100% - 80px)', + height: 'calc(100% - 80px)', children: (close) => ( ), @@ -637,40 +670,12 @@ export const MultiMediaComponent: FC<{ } }, [changeMedia]); - const mediaSettings = useMediaSettings(); - const t = useT(); return ( <> -
    -
    - - +
    +
    {!!currentMedia && ( {currentMedia.map((media, index) => ( -
    -
    - :: -
    +
    + + + +
    { - console.log(value); onChange({ target: { name: 'upload', @@ -719,7 +740,7 @@ export const MultiMediaComponent: FC<{ ), }); }} - className="absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] bg-black/80 rounded-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-[100]" + className="absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] bg-black/80 rounded-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-[9]" > )}
    -
    - x -
    + +
    ))} @@ -757,32 +787,76 @@ export const MultiMediaComponent: FC<{ )}
    {!dummy && ( -
    -
    - +
    @@ -793,6 +867,32 @@ export const MultiMediaComponent: FC<{ )}
    +
    + + + +
    + {!!toolBar && ( +
    + {toolBar} +
    + )} + {information && ( +
    + {information} +
    + )}
    )}
    diff --git a/apps/frontend/src/components/media/new.uploader.tsx b/apps/frontend/src/components/media/new.uploader.tsx index febe58fe..be292f37 100644 --- a/apps/frontend/src/components/media/new.uploader.tsx +++ b/apps/frontend/src/components/media/new.uploader.tsx @@ -116,7 +116,8 @@ export function useUppyUploader(props: { uppy2.log(error.message, 'error'); uppy2.info(error.message, 'error', 5000); toast.show( - `File type "${fileType}" is not allowed. Allowed types: ${allowedFileTypes}` + `File type "${fileType}" is not allowed. Allowed types: ${allowedFileTypes}`, + 'warning' ); uppy2.removeFile(file.id); return reject(error); @@ -197,6 +198,7 @@ export function useUppyUploader(props: { }); }); uppy2.on('error', (result) => { + uppy2.clear(); setLocked(false); }); uppy2.on('complete', async (result) => { diff --git a/apps/frontend/src/components/new-launch/a.component.tsx b/apps/frontend/src/components/new-launch/a.component.tsx index 10e6802f..16f65ab2 100644 --- a/apps/frontend/src/components/new-launch/a.component.tsx +++ b/apps/frontend/src/components/new-launch/a.component.tsx @@ -24,27 +24,43 @@ export const AComponent: FC<{ // update link try { - editor?.chain()?.focus()?.extendMarkRange('link')?.setLink({ href: url })?.run(); - } catch (e) { - } + editor + ?.chain() + ?.focus() + ?.extendMarkRange('link') + ?.setLink({ href: url }) + ?.run(); + } catch (e) {} editor?.commands?.focus(); }; return (
    - + + + + + + + +
    ); diff --git a/apps/frontend/src/components/new-launch/add.edit.modal.tsx b/apps/frontend/src/components/new-launch/add.edit.modal.tsx index 1c01faae..961d5c43 100644 --- a/apps/frontend/src/components/new-launch/add.edit.modal.tsx +++ b/apps/frontend/src/components/new-launch/add.edit.modal.tsx @@ -128,7 +128,7 @@ export const AddEditModalInnerInner: FC = (props) => { internal: state.internal, setTags: state.setTags, setEditor: state.setEditor, - setRepeater: state.setRepeater + setRepeater: state.setRepeater, })) ); @@ -161,8 +161,7 @@ export const AddEditModalInnerInner: FC = (props) => { })) ); setCurrent(existingData.integration); - } - else { + } else { setEditor('normal'); } @@ -212,5 +211,12 @@ export const AddEditModalInnerInner: FC = (props) => { return null; } - return ; + return ( + <> + + + + ); }; diff --git a/apps/frontend/src/components/new-launch/add.post.button.tsx b/apps/frontend/src/components/new-launch/add.post.button.tsx index 1a2ca5b7..c43c991e 100644 --- a/apps/frontend/src/components/new-launch/add.post.button.tsx +++ b/apps/frontend/src/components/new-launch/add.post.button.tsx @@ -13,22 +13,25 @@ export const AddPostButton: FC<{ const t = useT(); return ( -
    - +
    ); }; diff --git a/apps/frontend/src/components/new-launch/bold.text.tsx b/apps/frontend/src/components/new-launch/bold.text.tsx index d0478a81..76ef1d80 100644 --- a/apps/frontend/src/components/new-launch/bold.text.tsx +++ b/apps/frontend/src/components/new-launch/bold.text.tsx @@ -81,32 +81,25 @@ export const BoldText: FC<{ }; return (
    - - - - - - - - +
    ); diff --git a/apps/frontend/src/components/new-launch/bullets.component.tsx b/apps/frontend/src/components/new-launch/bullets.component.tsx index 454162e1..24cb3344 100644 --- a/apps/frontend/src/components/new-launch/bullets.component.tsx +++ b/apps/frontend/src/components/new-launch/bullets.component.tsx @@ -11,19 +11,24 @@ export const Bullets: FC<{ }; return (
    diff --git a/apps/frontend/src/components/new-launch/editor.tsx b/apps/frontend/src/components/new-launch/editor.tsx index 6d6c9b09..eb9e51a0 100644 --- a/apps/frontend/src/components/new-launch/editor.tsx +++ b/apps/frontend/src/components/new-launch/editor.tsx @@ -58,7 +58,8 @@ import Mention from '@tiptap/extension-mention'; import { suggestion } from '@gitroom/frontend/components/new-launch/mention.component'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { AComponent } from '@gitroom/frontend/components/new-launch/a.component'; -import { capitalize } from 'lodash'; +import { Placeholder } from '@tiptap/extensions'; +import { InformationComponent } from '@gitroom/frontend/components/launches/information.component'; const InterceptBoldShortcut = Extension.create({ name: 'preventBoldWithUnderline', @@ -91,7 +92,7 @@ const InterceptUnderlineShortcut = Extension.create({ export const EditorWrapper: FC<{ totalPosts: number; value: string; -}> = (props) => { +}> = () => { const { setGlobalValueText, setInternalValueText, @@ -278,6 +279,12 @@ export const EditorWrapper: FC<{ const addValue = useCallback( (index: number) => () => { + setTimeout(() => { + // scroll the the bottom + document.querySelector('#social-content').scrollTo({ + top: document.querySelector('#social-content').scrollHeight, + }); + }, 20); if (internal) { return addInternalValue(index, current, [ { @@ -326,30 +333,116 @@ export const EditorWrapper: FC<{ } return ( -
    +
    + {isCreateSet && current !== 'global' && ( + <> +
    +
    +
    + + + +
    +
    +
    +
    + You can't edit networks when creating a set +
    +
    +
    + + )} + {!canEdit && !isCreateSet && ( + <> +
    { + setLoaded(false); + addRemoveInternal(current); + }} + className="text-center absolute w-full h-full left-0 top-0 items-center justify-center flex z-[101] flex-col gap-[16px]" + > +
    +
    + + + +
    +
    +
    +
    + Click this button to exit global editing +
    + and customize the post for this channel +
    +
    +
    + Edit content +
    +
    +
    +
    + + )} {items.map((g, index) => ( -
    - {!canEdit && !isCreateSet && ( -
    { - if (index !== 0) { - return; - } - - setLoaded(false); - addRemoveInternal(current); - }} - className="select-none cursor-pointer absolute w-full h-full left-0 top-0 bg-red-600/10 z-[100]" - > - {index === 0 && ( -
    - Edit +
    +
    +
    + {index > 0 && ( +
    + + +
    )} -
    - )} -
    -
    - {canEdit ? ( - - ) : ( -
    - )} + {canEdit && items.length - 1 === index ? ( +
    +
    + +
    + {!!internal && !existingData?.integration && ( +
    +
    +
    +
    + Editing a Specific Network +
    +
    +
    +
    + + + +
    +
    + Back to global +
    +
    +
    + )} +
    + ) : null} } />
    -
    +
    - {index === 0 && - current !== 'global' && - canEdit && - !existingData.integration && ( - - - - )} {items.length > 1 && ( )} @@ -528,64 +640,47 @@ export const Editor: FC<{ [props.value, id] ); + const [loadedEditor, setLoadedEditor] = useState(editorType); + const [showEditor, setShowEditor] = useState(true); + useEffect(() => { + if (editorType === loadedEditor) { + return; + } + setLoadedEditor(editorType); + setShowEditor(false); + }, [editorType]); + + useEffect(() => { + if (showEditor) { + return; + } + setTimeout(() => { + setShowEditor(true); + }, 20); + }, [showEditor]); + + if (!showEditor) { + return null; + } + return ( -
    -
    -
    - - - - {(editorType === 'markdown' || editorType === 'html') && - identifier !== 'telegram' && ( - <> - - - - - )} -
    setEmojiPickerOpen(!emojiPickerOpen)} - > - {'\uD83D\uDE00'} -
    -
    -
    - { - addText(e.emoji); - setEmojiPickerOpen(false); - }} - open={emojiPickerOpen} - /> -
    -
    -
    -
    - {validateChars && - props.value.length === 0 && - pictures?.length === 0 && ( -
    - Your post should have at least one character or one image. -
    - )} -
    +
    +
    0 && '!rounded-bs-[0]' + )} + id={id} + > +
    + {/*{validateChars &&*/} + {/* props.value.length === 0 &&*/} + {/* pictures?.length === 0 && (*/} + {/*
    */} + {/* Your post should have at least one character or one image.*/} + {/*
    */} + {/* )}*/} +
    Drop your files here to upload
    -
    +
    -
    { if (editorRef?.current?.editor?.isFocused) { return; } editorRef?.current?.editor?.commands?.focus('end'); }} - > - + /> +
    +
    + +
    -
    { if (editorRef?.current?.editor?.isFocused) { return; } editorRef?.current?.editor?.commands?.focus('end'); }} - > + /> +
    {setImages && ( 0} + chars={chars} + totalChars={valueWithoutHtml.length} + totalAllowedChars={props.totalChars} + /> + } + toolBar={ +
    + + + + {(editorType === 'markdown' || editorType === 'html') && + identifier !== 'telegram' && ( + <> + + + + + )} +
    setEmojiPickerOpen(!emojiPickerOpen)} + > + + + + + + +
    +
    +
    1 + ? 'top-[35px]' + : 'bottom-[35px]' + )} + > + { + addText(e.emoji); + setEmojiPickerOpen(false); + }} + open={emojiPickerOpen} + /> +
    +
    +
    + } onChange={(value) => { setImages(value.target.value); }} @@ -652,52 +855,52 @@ export const Editor: FC<{ /> )}
    +
    {childButton}
    -
    -
    {childButton}
    -
    - {(props?.totalChars || 0) > 0 ? ( -
    props.totalChars && '!text-red-500' - )} - > - {valueWithoutHtml.length}/{props.totalChars} -
    - ) : ( -
    - {selectedIntegration?.map((p) => ( - -
    chars?.[p.integration.id] && - '!text-red-500' - } - > - {p.integration.name} ({capitalize(p.integration.identifier)} - ): -
    -
    chars?.[p.integration.id] && - '!text-red-500' - } - > - {valueWithoutHtml.length}/{chars?.[p.integration.id]} -
    -
    - ))} -
    - )} -
    -
    + {/*
    */} + {/*
    */} + {/* {(props?.totalChars || 0) > 0 ? (*/} + {/* props.totalChars && '!text-red-500'*/} + {/* )}*/} + {/* >*/} + {/* {valueWithoutHtml.length}/{props.totalChars}*/} + {/*
    */} + {/* ) : (*/} + {/* */} + {/* {selectedIntegration?.map((p) => (*/} + {/* */} + {/* chars?.[p.integration.id] &&*/} + {/* '!text-red-500'*/} + {/* }*/} + {/* >*/} + {/* {p.integration.name} ({capitalize(p.integration.identifier)}*/} + {/* ):*/} + {/*
    */} + {/* chars?.[p.integration.id] &&*/} + {/* '!text-red-500'*/} + {/* }*/} + {/* >*/} + {/* {valueWithoutHtml.length}/{chars?.[p.integration.id]}*/} + {/*
    */} + {/* */} + {/* ))}*/} + {/*
    */} + {/* )}*/} + {/*
    */} + {/*
    */}
    ); }; @@ -712,6 +915,7 @@ export const OnlyEditor = forwardRef< } >(({ editorType, value, onChange, paste }, ref) => { const fetch = useFetch(); + const { internal } = useLaunchStore( useShallow((state) => ({ internal: state.internal.find((p) => p.integration.id === state.current), @@ -759,6 +963,10 @@ export const OnlyEditor = forwardRef< InterceptUnderlineShortcut, BulletList, ListItem, + Placeholder.configure({ + placeholder: 'Write something …', + emptyEditorClass: 'is-editor-empty', + }), ...(editorType === 'html' || editorType === 'markdown' ? [ Link.configure({ diff --git a/apps/frontend/src/components/new-launch/heading.component.tsx b/apps/frontend/src/components/new-launch/heading.component.tsx index 26985fe0..99ba129c 100644 --- a/apps/frontend/src/components/new-launch/heading.component.tsx +++ b/apps/frontend/src/components/new-launch/heading.component.tsx @@ -13,20 +13,27 @@ export const HeadingComponent: FC<{ }; return ( -
    +
    -
    +
    = (props) => { const [loading, setLoading] = useState(false); const toaster = useToaster(); const modal = useModals(); + const [showSettings, setShowSettings] = useState(false); + + const { addEditSets, mutate, customClose, dummy } = props; + + const { + selectedIntegrations, + hide, + date, + setDate, + repeater, + setRepeater, + tags, + setTags, + integrations, + setSelectedIntegrations, + locked, + current, + activateExitButton, + } = useLaunchStore( + useShallow((state) => ({ + hide: state.hide, + date: state.date, + setDate: state.setDate, + current: state.current, + repeater: state.repeater, + setRepeater: state.setRepeater, + tags: state.tags, + setTags: state.setTags, + selectedIntegrations: state.selectedIntegrations, + integrations: state.integrations, + setSelectedIntegrations: state.setSelectedIntegrations, + locked: state.locked, + activateExitButton: state.activateExitButton, + })) + ); + + const currentIntegrationText = useMemo(() => { + if (current === 'global') { + return ''; + } + + const currentIntegration = integrations.find((p) => p.id === current)!; + + return `${currentIntegration.name} (${capitalize( + currentIntegration.identifier.split('-').shift() + )})`; + }, [current]); + + const changeCustomer = useCallback( + (customer: string) => { + const neededIntegrations = integrations.filter( + (p) => p?.customer?.id === customer + ); + setSelectedIntegrations( + neededIntegrations.map((p) => ({ + settings: {}, + selectedIntegrations: p, + })) + ); + }, + [integrations] + ); + + const askClose = useCallback(async () => { + if (!activateExitButton || dummy) { + 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(); + } + }, [activateExitButton, dummy]); + + const deletePost = useCallback(async () => { + setLoading(true); + 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 schedule = useCallback( + (type: 'draft' | 'now' | 'schedule') => async () => { + setLoading(true); + const checkAllValid = await ref.current.checkAllValid(); + if (type !== 'draft') { + const notEnoughChars = checkAllValid.filter((p: any) => { + return p.values.some((a: any) => { + return ( + countCharacters( + stripHtmlValidation('normal', a.content, true), + p?.integration?.identifier || '' + ) === 0 && a.media?.length === 0 + ); + }); + }); + + for (const item of notEnoughChars) { + toaster.show( + '' + + item.integration.name + + ' Your post should have at least one character or one image.', + 'warning' + ); + setLoading(false); + item.preview(); + return; + } + + for (const item of checkAllValid) { + if (item.valid === false) { + toaster.show('Please fix your settings', 'warning'); + item.fix(); + setLoading(false); + setShowSettings(true); + return; + } + + if (item.errors !== true) { + toaster.show( + `${capitalize(item.integration.identifier.split('-')[0])} (${ + item.integration.name + }): ${item.errors}`, + 'warning' + ); + item.preview(); + setLoading(false); + setShowSettings(true); + return; + } + } + + const sliceNeeded = checkAllValid.filter((p: any) => { + return p.values.some((a: any) => { + const strip = stripHtmlValidation('normal', a.content, true); + const weightedLength = countCharacters( + strip, + p?.integration?.identifier || '' + ); + const totalCharacters = + weightedLength > strip.length ? weightedLength : strip.length; + + return totalCharacters > (p.maximumCharacters || 1000000); + }); + }); + + for (const item of sliceNeeded) { + toaster.show( + `${item?.integration?.name} (${item?.integration?.identifier}) post is too long, please fix it`, + 'warning' + ); + item.preview(); + setLoading(false); + return; + } + } + + const shortLinkUrl = dummy + ? { ask: false } + : 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, + ...(repeater ? { inter: repeater } : {}), + tags, + shortLink, + date: date.utc().format('YYYY-MM-DDTHH:mm:ss'), + posts: checkAllValid.map((post: any) => ({ + integration: { + id: post.integration.id, + }, + group, + settings: { ...(post.settings || {}) }, + value: post.values.map((value: any) => ({ + ...(value.id ? { id: value.id } : {}), + content: value.content, + image: + (value?.media || []).map( + ({ id, path, alt, thumbnail, thumbnailTimestamp }: any) => ({ + id, + path, + alt, + thumbnail, + thumbnailTimestamp, + }) + ) || [], + })), + })), + }; + + if (dummy) { + modal.openModal({ + title: '', + children: , + classNames: { + modal: 'w-[100%] bg-transparent text-textColor', + }, + size: '100%', + withCloseButton: false, + closeOnEscape: true, + closeOnClickOutside: true, + }); + + setLoading(false); + } + + if (!dummy) { + 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, dummy] + ); + + return ( +
    +
    +
    +
    +
    + Create Post +
    +
    +
    +
    +
    +
    + +
    +
    + {!dummy && ( + + )} +
    +
    +
    +
    {!existingData.integration && }
    +
    + {!hide && } +
    +
    +
    +
    +
    +
    +
    +
    setShowSettings(!showSettings)} + className={clsx( + 'bg-[#612BD3] rounded-[12px] flex items-center gap-[8px] cursor-pointer p-[12px]', + showSettings ? '!rounded-b-none' : '' + )} + > +
    + + + + +
    +
    + {currentIntegrationText} Settings +
    +
    + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    Post Preview
    +
    + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + {!dummy && ( + { + setTags(e.target.value); + }} + /> + )} + + {!dummy && ( + + )} +
    +
    + {existingData?.integration && ( + + )} + + {!addEditSets && ( + + )} + {addEditSets && ( + + )} + {!addEditSets && ( +
    + + + +
    + )} +
    +
    +
    + +
    + ); +}; +export const ManageModalA: 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(); const { addEditSets, mutate, customClose, dummy } = props; @@ -490,25 +1061,6 @@ export const ManageModal: FC = (props) => {
    -
    ); diff --git a/apps/frontend/src/components/new-launch/picks.socials.component.tsx b/apps/frontend/src/components/new-launch/picks.socials.component.tsx index c966a177..6451ea66 100644 --- a/apps/frontend/src/components/new-launch/picks.socials.component.tsx +++ b/apps/frontend/src/components/new-launch/picks.socials.component.tsx @@ -6,6 +6,7 @@ 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'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; export const PicksSocialsComponent: FC<{ toolTip?: boolean }> = ({ toolTip, @@ -26,13 +27,13 @@ export const PicksSocialsComponent: FC<{ toolTip?: boolean }> = ({ })) ); + return (
    -
    -
    -
    - {integrations - .filter((f) => { +
    +
    +
    + {integrations.filter((f) => { if (exising.integration) { return f.id === exising.integration; } @@ -55,34 +56,41 @@ export const PicksSocialsComponent: FC<{ toolTip?: boolean }> = ({ 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', + 'cursor-pointer border-[1.5px] relative 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' - : '' + ? 'grayscale border-transparent' + : 'border-[#622FF6]' )} > p.integration.id === integration.id + ) === -1 + ? 'border-transparent' + : 'border-[#000]' + )} alt={integration.identifier} - width={32} - height={32} + width={42} + height={42} /> {integration.identifier === 'youtube' ? ( ) : ( {integration.identifier} )}
    diff --git a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx index eb888169..82bf421e 100644 --- a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx @@ -22,6 +22,7 @@ import useSWR from 'swr'; import { InternalChannels } from '@gitroom/frontend/components/launches/internal.channels'; import { capitalize } from 'lodash'; import clsx from 'clsx'; +import { createPortal } from 'react-dom'; class Empty { @IsOptional() @@ -255,40 +256,6 @@ export const withProvider = function (params: { >
    -
    -
    -
    setTab(0)} - className={clsx( - 'cursor-pointer rounded-[4px] flex-1 overflow-hidden whitespace-nowrap text-center pt-[6px] pb-[5px]', - tab !== 0 && !!SettingsComponent - ? '' - : 'text-textItemFocused bg-boxFocused' - )} - > - {t('preview', 'Preview')} -
    -
    - {current && - (!!SettingsComponent || !!data?.internalPlugs?.length) && ( -
    -
    setTab(1)} - className={clsx( - 'cursor-pointer rounded-[4px] flex-1 overflow-hidden whitespace-nowrap text-center pt-[6px] pb-[5px]', - tab !== 1 ? '' : 'text-textItemFocused bg-boxFocused' - )} - > - {t('settings', 'Settings')} ( - {capitalize( - selectedIntegration.integration.identifier.split('-')[0] - )} - ) -
    -
    - )} -
    - {current && (tab === 0 || (!SettingsComponent && !data?.internalPlugs?.length)) && @@ -331,14 +298,22 @@ export const withProvider = function (params: { } /> ))} - {(SettingsComponent || !!data?.internalPlugs?.length) && ( -
    - - {!!data?.internalPlugs?.length && !dummy && ( - - )} -
    - )} + {(SettingsComponent || !!data?.internalPlugs?.length) && + createPortal( +
    + + {!!data?.internalPlugs?.length && !dummy && ( + + )} +
    , + document.querySelector('#social-settings') || document.createElement('div') + )} + {current && + !SettingsComponent && + createPortal( + , + document.querySelector('#social-settings')! + )}
    diff --git a/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx b/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx index 187a9657..6afe1533 100644 --- a/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx +++ b/apps/frontend/src/components/new-launch/providers/show.all.providers.tsx @@ -192,13 +192,6 @@ export const ShowAllProviders = forwardRef((props, ref) => { })), }} > -
    -
    -
    - {t('preview', 'Preview')} -
    -
    -
    {global?.[0]?.content?.length === 0 ? (
    {t( diff --git a/apps/frontend/src/components/new-launch/select.current.tsx b/apps/frontend/src/components/new-launch/select.current.tsx index 8222939c..53350bbe 100644 --- a/apps/frontend/src/components/new-launch/select.current.tsx +++ b/apps/frontend/src/components/new-launch/select.current.tsx @@ -1,12 +1,6 @@ 'use client'; -import { - FC, - RefObject, - useEffect, - useRef, - useState, -} from 'react'; +import { FC, RefObject, useEffect, useRef, useState } from 'react'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; import clsx from 'clsx'; import Image from 'next/image'; @@ -72,11 +66,11 @@ export const SelectCurrent: FC = () => { return ( <> -
    +
    @@ -85,19 +79,27 @@ export const SelectCurrent: FC = () => { setHide(true); setCurrent('global'); }} - className="cursor-pointer flex gap-[8px] items-center bg-newBgLineColor p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + className={clsx( + 'cursor-pointer flex gap-[8px] rounded-[8px] w-[40px] h-[40px] justify-center items-center bg-newBgLineColor', + current !== 'global' + ? 'text-[#A3A3A3]' + : 'border border-[#FC69FF] text-[#FC69FF]' + )} > -
    +
    @@ -109,34 +111,39 @@ export const SelectCurrent: FC = () => { setCurrent(integration.id); }} key={integration.id} - className="cursor-pointer flex gap-[8px] items-center bg-newBgLineColor p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + className={clsx( + 'border cursor-pointer relative flex gap-[8px] w-[40px] h-[40px] rounded-[8px] items-center bg-newBgLineColor justify-center', + current === integration.id + ? 'border-[#FC69FF] text-[#FC69FF]' + : 'border-transparent' + )} > +
    {integration.identifier} {integration.identifier === 'youtube' ? ( ) : ( {integration.identifier} )}
    @@ -148,3 +155,24 @@ export const SelectCurrent: FC = () => { ); }; + +export const IsGlobal: FC<{ id: string }> = ({ id }) => { + const { isInternal } = + useLaunchStore( + useShallow((state) => ({ + isInternal: !!state.internal.find(p => p.integration.id === id), + })) + ); + + if (!isInternal) { + return null; + } + + return ( +
    + ); +}; diff --git a/apps/frontend/src/components/new-launch/u.text.tsx b/apps/frontend/src/components/new-launch/u.text.tsx index 3c0315a0..a03d392b 100644 --- a/apps/frontend/src/components/new-launch/u.text.tsx +++ b/apps/frontend/src/components/new-launch/u.text.tsx @@ -81,36 +81,25 @@ export const UText: FC<{ }; return (
    - - - - - - - - - +
    ); diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx index d34db3aa..dcfc7d37 100644 --- a/apps/frontend/src/components/new-layout/layout.component.tsx +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -35,7 +35,6 @@ import { TopMenu } from '@gitroom/frontend/components/layout/top.menu'; import { LanguageComponent } from '@gitroom/frontend/components/layout/language.component'; import { ChromeExtensionComponent } from '@gitroom/frontend/components/layout/chrome.extension.component'; import NotificationComponent from '@gitroom/frontend/components/notifications/notification.component'; -import { BillingAfter } from '@gitroom/frontend/components/new-layout/billing.after'; import { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector'; import { PreConditionComponent } from '@gitroom/frontend/components/layout/pre-condition.component'; import { AttachToFeedbackIcon } from '@gitroom/frontend/components/new-layout/sentry.feedback.component'; diff --git a/apps/frontend/src/components/sets/sets.tsx b/apps/frontend/src/components/sets/sets.tsx index df07d7e2..a37ae809 100644 --- a/apps/frontend/src/components/sets/sets.tsx +++ b/apps/frontend/src/components/sets/sets.tsx @@ -94,10 +94,13 @@ export const Sets: FC = () => { (params?: { id?: string; name?: string; content?: string }) => () => { modal.openModal({ closeOnClickOutside: false, + removeLayout: true, closeOnEscape: false, withCloseButton: false, - removeLayout: true, - askClose: true, + fullScreen: true, + classNames: { + modal: 'w-[100%] max-w-[1400px] text-textColor', + }, id: 'add-edit-modal', children: ( { modals.openModal({ title: 'Add Signature', + withCloseButton: true, children: (close) => ( ), @@ -23,18 +24,36 @@ export const SignatureBox: FC<{ <>
    - + + + + + + + + +
    @@ -45,11 +64,5 @@ export const SignatureModal: FC<{ appendSignature: (sign: string) => void; }> = (props) => { const { appendSignature } = props; - return ( -
    -
    - -
    -
    - ); + return ; }; diff --git a/apps/frontend/src/components/third-parties/third-party.media.tsx b/apps/frontend/src/components/third-parties/third-party.media.tsx index 2729607c..59917fec 100644 --- a/apps/frontend/src/components/third-parties/third-party.media.tsx +++ b/apps/frontend/src/components/third-parties/third-party.media.tsx @@ -182,9 +182,9 @@ export const ThirdPartyMedia: FC<{ return ( <>
    - +
    ); diff --git a/apps/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js index e5c3f7e9..15e6a72b 100644 --- a/apps/frontend/tailwind.config.js +++ b/apps/frontend/tailwind.config.js @@ -74,6 +74,9 @@ module.exports = { modalCustom: 'var(--color-modalCustom)', newBgColor: 'var(--new-bgColor)', + newBackdrop: 'var(--new-back-drop)', + newSep: 'var(--new-sep)', + newBorder: 'var(--new-border)', newBgColorInner: 'var(--new-bgColorInner)', newBgLineColor: 'var(--new-bgLineColor)', textItemFocused: 'var(--new-textItemFocused)', @@ -91,6 +94,7 @@ module.exports = { newTableText: 'var(--new-table-text)', newTableTextFocused: 'var(--new-table-text-focused)', newColColor: 'var(--new-col-color)', + newSettings: 'var(--new-settings)', menuDots: 'var(--new-menu-dots)', menuDotsHover: 'var(--new-menu-hover)', bigStrip: 'var(--new-big-strips)', @@ -223,6 +227,12 @@ module.exports = { }, }), screens: { + iconBreak: { + raw: '(max-width: 1560px)', + }, + maxMedia: { + raw: '(max-width: 1400px)', + }, custom: { raw: '(max-height: 800px)', }, diff --git a/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts b/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts index 572d1dfd..4c1d3e0b 100644 --- a/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts @@ -78,10 +78,7 @@ export class MediaRepository { }, }, }; - const pages = - pageNum === 0 - ? Math.ceil((await this._media.model.media.count(query)) / 28) - : 0; + const pages = Math.ceil((await this._media.model.media.count(query)) / 18); const results = await this._media.model.media.findMany({ where: { organizationId: org, @@ -98,8 +95,8 @@ export class MediaRepository { alt: true, thumbnailTimestamp: true, }, - skip: pageNum * 28, - take: 28, + skip: pageNum * 18, + take: 18, }); return {