diff --git a/apps/frontend/src/app/(app)/(site)/analytics/page.tsx b/apps/frontend/src/app/(app)/(site)/analytics/page.tsx index 4fe2368b..8931fead 100644 --- a/apps/frontend/src/app/(app)/(site)/analytics/page.tsx +++ b/apps/frontend/src/app/(app)/(site)/analytics/page.tsx @@ -1,5 +1,4 @@ export const dynamic = 'force-dynamic'; -import { AnalyticsComponent } from '@gitroom/frontend/components/analytics/analytics.component'; import { Metadata } from 'next'; import { PlatformAnalytics } from '@gitroom/frontend/components/platform-analytics/platform.analytics'; import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; @@ -8,9 +7,5 @@ export const metadata: Metadata = { description: '', }; export default async function Index() { - return ( - <> - {isGeneralServerSide() ? : } - - ); + return ; } diff --git a/apps/frontend/src/app/(app)/(site)/billing/page.tsx b/apps/frontend/src/app/(app)/(site)/billing/page.tsx index 069dda23..bb879c10 100644 --- a/apps/frontend/src/app/(app)/(site)/billing/page.tsx +++ b/apps/frontend/src/app/(app)/(site)/billing/page.tsx @@ -7,5 +7,9 @@ export const metadata: Metadata = { description: '', }; export default async function Page() { - return ; + return ( +
+ +
+ ); } diff --git a/apps/frontend/src/app/(app)/(site)/layout.tsx b/apps/frontend/src/app/(app)/(site)/layout.tsx index ce69c4f8..825f87f7 100644 --- a/apps/frontend/src/app/(app)/(site)/layout.tsx +++ b/apps/frontend/src/app/(app)/(site)/layout.tsx @@ -1,8 +1,9 @@ -import { LayoutSettings } from '@gitroom/frontend/components/layout/layout.settings'; +import { LayoutComponent } from '@gitroom/frontend/components/new-layout/layout.component'; + export default async function Layout({ children, }: { children: React.ReactNode; }) { - return {children}; + return {children}; } diff --git a/apps/frontend/src/app/(app)/(site)/marketplace/buyer/page.tsx b/apps/frontend/src/app/(app)/(site)/marketplace/buyer/page.tsx deleted file mode 100644 index 119dd3fd..00000000 --- a/apps/frontend/src/app/(app)/(site)/marketplace/buyer/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Buyer } from '@gitroom/frontend/components/marketplace/buyer'; -export const dynamic = 'force-dynamic'; -import { Metadata } from 'next'; -import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; -export const metadata: Metadata = { - title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`, - description: '', -}; -export default async function Index({ - searchParams, -}: { - searchParams: { - code: string; - }; -}) { - return ; -} diff --git a/apps/frontend/src/app/(app)/(site)/marketplace/layout.tsx b/apps/frontend/src/app/(app)/(site)/marketplace/layout.tsx deleted file mode 100644 index 01237e28..00000000 --- a/apps/frontend/src/app/(app)/(site)/marketplace/layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { BuyerSeller } from '@gitroom/frontend/components/marketplace/buyer.seller'; -import { ReactNode } from 'react'; -export default function Layout({ children }: { children: ReactNode }) { - return ( - <> - - {children} - - ); -} diff --git a/apps/frontend/src/app/(app)/(site)/marketplace/page.tsx b/apps/frontend/src/app/(app)/(site)/marketplace/page.tsx deleted file mode 100644 index c4411068..00000000 --- a/apps/frontend/src/app/(app)/(site)/marketplace/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export const dynamic = 'force-dynamic'; -import { Metadata } from 'next'; -import { cookies } from 'next/headers'; -import { redirect } from 'next/navigation'; -import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; -export const metadata: Metadata = { - title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`, - description: '', -}; -export default async function Index({ - searchParams, -}: { - searchParams: { - code: string; - }; -}) { - const currentCookie = cookies()?.get('marketplace')?.value; - return redirect( - currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller' - ); -} diff --git a/apps/frontend/src/app/(app)/(site)/marketplace/seller/page.tsx b/apps/frontend/src/app/(app)/(site)/marketplace/seller/page.tsx deleted file mode 100644 index cac56929..00000000 --- a/apps/frontend/src/app/(app)/(site)/marketplace/seller/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Seller } from '@gitroom/frontend/components/marketplace/seller'; -export const dynamic = 'force-dynamic'; -import { Metadata } from 'next'; -import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; -export const metadata: Metadata = { - title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`, - description: '', -}; -export default async function Index({ - searchParams, -}: { - searchParams: { - code: string; - }; -}) { - return ; -} diff --git a/apps/frontend/src/app/(app)/(site)/media/page.tsx b/apps/frontend/src/app/(app)/(site)/media/page.tsx new file mode 100644 index 00000000..fcf13bff --- /dev/null +++ b/apps/frontend/src/app/(app)/(site)/media/page.tsx @@ -0,0 +1,5 @@ +import { MediaLayoutComponent } from '@gitroom/frontend/components/new-layout/layout.media.component'; + +export default async function Page() { + return +} diff --git a/apps/frontend/src/app/(app)/(site)/messages/[id]/page.tsx b/apps/frontend/src/app/(app)/(site)/messages/[id]/page.tsx deleted file mode 100644 index 8992ce09..00000000 --- a/apps/frontend/src/app/(app)/(site)/messages/[id]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Messages } from '@gitroom/frontend/components/messages/messages'; -export const dynamic = 'force-dynamic'; -import { Metadata } from 'next'; -import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; -export const metadata: Metadata = { - title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`, - description: '', -}; -export default async function Index() { - return ; -} diff --git a/apps/frontend/src/app/(app)/(site)/messages/layout.tsx b/apps/frontend/src/app/(app)/(site)/messages/layout.tsx deleted file mode 100644 index e158a6c3..00000000 --- a/apps/frontend/src/app/(app)/(site)/messages/layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Layout } from '@gitroom/frontend/components/messages/layout'; -export const dynamic = 'force-dynamic'; -import { ReactNode } from 'react'; -export default async function LayoutWrapper({ - children, -}: { - children: ReactNode; -}) { - return ; -} diff --git a/apps/frontend/src/app/(app)/(site)/messages/page.tsx b/apps/frontend/src/app/(app)/(site)/messages/page.tsx deleted file mode 100644 index 5be7a9bc..00000000 --- a/apps/frontend/src/app/(app)/(site)/messages/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { getT } from '@gitroom/react/translation/get.translation.service.backend'; - -export const dynamic = 'force-dynamic'; -import { Metadata } from 'next'; -import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side'; -export const metadata: Metadata = { - title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`, - description: '', -}; -export default async function Index() { - const t = await getT(); - - return ( -
-
-
- {t( - 'select_a_conversation_and_chat_away', - 'Select a conversation and chat away.' - )} -
-
- ); -} diff --git a/apps/frontend/src/app/(app)/(site)/plugs/page.tsx b/apps/frontend/src/app/(app)/(site)/plugs/page.tsx index 6182da18..af6f39bc 100644 --- a/apps/frontend/src/app/(app)/(site)/plugs/page.tsx +++ b/apps/frontend/src/app/(app)/(site)/plugs/page.tsx @@ -7,9 +7,5 @@ export const metadata: Metadata = { description: '', }; export default async function Index() { - return ( - <> - - - ); + return ; } diff --git a/apps/frontend/src/app/colors.scss b/apps/frontend/src/app/colors.scss index 372a0d42..65b8198f 100644 --- a/apps/frontend/src/app/colors.scss +++ b/apps/frontend/src/app/colors.scss @@ -1,25 +1,79 @@ :root { .dark { - --color-primary: #000000; + --new-bgColor: #0e0e0e; + --new-bgColorInner: #1a1919; + --new-bgLineColor: #212121; + --new-textItemFocused: #1a1919; + --new-textItemBlur: #999999; + --new-boxFocused: #fff; + --new-textColor: 255 255 255; + --new-blockSeparator: #272626; + --new-btn-simple: #313030; + --new-btn-text: #ffffff; + --new-btn-primary: #612bd3; + --new-ai-btn: #d82d7e; + --new-box-hover: #201f1f; + --new-table-border: #2b2b2b; + --new-table-header: #1e1d1d; + --new-table-text: #9c9c9c; + --new-table-text-focused: #fc69ff; + --new-small-strips: #191818; + --new-big-strips: #161515; + --new-col-color: #2c2b2b; + --new-menu-dots: #696868; + --new-menu-hover: #fff; + --menu-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.50); + } + .light { + --new-bgColor: #f0f2f4; + --new-bgColorInner: #ffffff; + --new-bgLineColor: #e7e9eb; + --new-textItemFocused: #3900b2; + --new-textItemBlur: #777b7f; + --new-boxFocused: #ebe8ff; + --new-textColor: 14 14 14; + --new-blockSeparator: #f2f2f4; + --new-btn-simple: #ECEEF1; + --new-btn-text: #0E0E0E; + --new-btn-primary: #612bd3; + --new-ai-btn: #d82d7e; + --new-box-hover: #f4f6f8; + --new-table-border: #e7e9eb; + --new-table-header: #f5f7f9; + --new-table-text: #777b7f; + --new-table-text-focused: #fc69ff; + --new-small-strips: #191818; + --new-big-strips: #F5F7F9; + --new-col-color: #EFF1F3; + --new-menu-dots: #696868; + --new-menu-hover: #000; + --menu-shadow: -22px 83px 24px 0 rgba(55, 52, 75, 0.00), -14px 53px 22px 0 rgba(55, 52, 75, 0.01), -8px 30px 19px 0 rgba(55, 52, 75, 0.05), -3px 13px 14px 0 rgba(55, 52, 75, 0.09), -1px 3px 8px 0 rgba(55, 52, 75, 0.10); + } +} + + +:root { + .dark { + --color-primary: #0e0e0e; --color-secondary: #090b13; --color-text: #ffffff; --color-third: #080b13; --color-forth: #612ad5; - --color-fifth: #28344f; - --color-sixth: #0b101b; + --color-fifth: var(--new-bgLineColor); + --color-sixth: var(--new-table-header); --color-seventh: #7236f1; --color-gray: #8c8c8c; - --color-input: #131b2c; + --color-input: var(--new-table-header); --color-input-text: #64748b; - --color-table-border: #1f2941; + --color-table-border: var(--new-table-border); --color-custom1: #324264; --color-custom2: #141c2c; --color-custom3: #0b0f1c; --color-custom4: #8155dd; --color-custom5: #e9e9f1; - --color-custom6: #172034; + --color-custom6: var(--new-bgLineColor); --color-custom7: #7950f2; - --color-custom8: #0f1524; + --color-custom8: var(--new-btn-simple); --color-custom9: #354258; --color-custom10: #e4b895; --color-custom11: #8b90ff; @@ -70,16 +124,16 @@ --color-modalCustom: #000000; } .light { - --color-primary: #fff; + --color-primary: #f0f2f4; --color-secondary: #fff; --color-text: #000; --color-third: white; --color-forth: #612ad5; - --color-fifth: #efefef; - --color-sixth: #fff; + --color-fifth: var(--new-bgLineColor); + --color-sixth: var(--new-table-header); --color-seventh: #7236f1; --color-gray: #8c8c8c; - --color-input: #f8f8f8; + --color-input: var(--new-table-header); --color-input-text: #64748b; --color-table-border: #efefef; --color-custom1: #324264; @@ -87,9 +141,9 @@ --color-custom3: #fff; --color-custom4: #8155dd; --color-custom5: #e9e9f1; - --color-custom6: #fff; + --color-custom6: var(--new-bgLineColor); --color-custom7: #7950f2; - --color-custom8: #efefef; + --color-custom8: var(--new-btn-simple); --color-custom9: #354258; --color-custom10: #e4b895; --color-custom11: #8b90ff; @@ -139,4 +193,4 @@ --color-custom55: #d5d7e1; --color-modalCustom: transparent; } -} +} \ No newline at end of file diff --git a/apps/frontend/src/app/global.scss b/apps/frontend/src/app/global.scss index fb46e49e..a8797b40 100644 --- a/apps/frontend/src/app/global.scss +++ b/apps/frontend/src/app/global.scss @@ -6,10 +6,15 @@ @import '@uppy/core/dist/style.css'; @import '@uppy/dashboard/dist/style.css'; -body, -html { - @apply bg-primary; +body { + background: var(--new-bgColor) !important; + color: var(--new-btn-text); } + +body * { + outline: none !important; +} + .box { position: relative; } @@ -89,13 +94,14 @@ html { } .react-tags { - @apply border border-fifth bg-input; + @apply border border-newTableBorder placeholder-textColor; position: relative; padding-left: 16px; - height: 44px; - border-radius: 4px; - /* shared font styles */ font-size: 14px; + background-color: var(--new-bgColorInner); + height: 42px; + border-radius: 8px; + /* shared font styles */ line-height: 1.2; /* clicking anywhere will focus the input */ cursor: text; @@ -104,6 +110,11 @@ html { margin-right: 0 !important; } +.react-tags input { + @apply placeholder-textColor; + font-size: 14px; +} + .react-tags.is-active { @apply border-customColor51; } @@ -398,6 +409,7 @@ div div .set-font-family { transform: translate(-50%, -50%); white-space: nowrap; opacity: 30%; + font-size: 14px; } .loading-shimmer { @@ -434,20 +446,29 @@ div div .set-font-family { } .tags-top .react-tags__combobox { - margin-left: 5px; + margin-left: 0; } .tags-top .react-tags__combobox { - height: 44px; + height: 42px; display: flex; - background-color: var(--color-input); + background-color: var(--new-bgColorInner); padding-left: 10px; padding-right: 10px; min-width: 150px; text-align: left; border-width: 1px; - border-radius: 4px; - border-color: var(--color-fifth); + border-radius: 8px; + border-color: var(--new-table-border); + font-size: 14px; +} + +.tags-top input { + font-size: 14px; +} + +.tags-top input::placeholder { + color: var(--new-textColor); } .tags-top .react-tags__list, @@ -537,7 +558,7 @@ html[dir='rtl'] [dir='ltr'] { } .ProseMirror:focus { - outline: none; + outline: none; } .ProseMirror .mention { @@ -545,34 +566,63 @@ html[dir='rtl'] [dir='ltr'] { color: #ae8afc; } -.ProseMirror ul, .preview ul { +.ProseMirror ul, +.preview ul { list-style: disc; padding-left: 20px; } -.preview ul, .preview li { +.preview ul, +.preview li { white-space: nowrap; } -.ProseMirror h1, .preview h1 { - font-size: 24px; - font-weight: bold; +.ProseMirror h1, +.preview h1 { + font-size: 24px; + font-weight: bold; } .ProseMirror * { white-space: break-spaces; } -.ProseMirror h2, .preview h2 { - font-size: 20px; - font-weight: bold; +.ProseMirror h2, +.preview h2 { + font-size: 20px; + font-weight: bold; } -.ProseMirror h3, .preview h3 { - font-size: 18px; - font-weight: bold; +.ProseMirror h3, +.preview h3 { + font-size: 18px; + font-weight: bold; } .preview p { min-height: 24px; -} \ No newline at end of file +} + +.repeated-strip { + background: repeating-linear-gradient( + 135deg, + var(--new-bgColorInner), + var(--new-bgColorInner) 4px, + var(--new-big-strips) 4px, + var(--new-big-strips) 8px + ); +} + +.mantine-Modal-inner { + backdrop-filter: blur(10px); +} + +.mantine-Modal-modal { + padding: 0; + @apply bg-newBgColorInner; + border-radius: 24px; +} + +.mantine-Overlay-root { + background: rgba(65, 64, 66, 0.3) !important; +} diff --git a/apps/frontend/src/components/analytics/chart-social.tsx b/apps/frontend/src/components/analytics/chart-social.tsx index 4c6e5a66..80173255 100644 --- a/apps/frontend/src/components/analytics/chart-social.tsx +++ b/apps/frontend/src/components/analytics/chart-social.tsx @@ -4,6 +4,7 @@ import { FC, useEffect, useMemo, useRef } from 'react'; import DrawChart from 'chart.js/auto'; import { TotalList } from '@gitroom/frontend/components/analytics/stars.and.forks.interface'; import { chunk } from 'lodash'; +import useCookie from 'react-use-cookie'; function mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] { const res = chunk(data, Math.ceil(data.length / numPoints)); return res.map((row) => { @@ -17,6 +18,7 @@ export const ChartSocial: FC<{ data: TotalList[]; }> = (props) => { const { data } = props; + const [mode] = useCookie('mode', 'dark'); const list = useMemo(() => { return mergeDataPoints(data, 7); }, [data]); @@ -26,8 +28,8 @@ export const ChartSocial: FC<{ const gradient = ref.current .getContext('2d') .createLinearGradient(0, 0, 0, ref.current.height); - gradient.addColorStop(0, 'rgb(20,101,6)'); // Start color with some transparency - gradient.addColorStop(1, 'rgb(9, 11, 19, 1)'); + gradient.addColorStop(0, 'rgb(90,46,203)'); // Start color with some transparency + gradient.addColorStop(1, 'rgb(65, 38, 136, 1)'); chart.current = new DrawChart(ref.current!, { type: 'line', options: { @@ -64,7 +66,7 @@ export const ChartSocial: FC<{ labels: list.map((row) => row.date), datasets: [ { - borderColor: '#fff', + borderColor: mode === 'dark' ? '#fff' : '#000', // @ts-ignore label: 'Total', backgroundColor: gradient, diff --git a/apps/frontend/src/components/billing/faq.component.tsx b/apps/frontend/src/components/billing/faq.component.tsx index ff53c498..7691718c 100644 --- a/apps/frontend/src/components/billing/faq.component.tsx +++ b/apps/frontend/src/components/billing/faq.component.tsx @@ -57,13 +57,6 @@ For example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouT 'If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels' ), }, - { - title: t('faq_what_is_ai_auto_complete', 'What is AI auto-complete?'), - description: t( - 'faq_we_automate_chatgpt_to_help_you_write', - 'We automate ChatGPT to help you write your social posts and articles' - ), - }, ]; }; export const FAQSection: FC<{ diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx index 3dba9ef5..72e558e6 100644 --- a/apps/frontend/src/components/launches/add.provider.component.tsx +++ b/apps/frontend/src/components/launches/add.provider.component.tsx @@ -16,6 +16,7 @@ import { object, string } from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { web3List } from '@gitroom/frontend/components/launches/web3/web3.list'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; const resolver = classValidatorResolver(ApiKeyDto); export const useAddProvider = (update?: () => void) => { const modal = useModals(); @@ -26,10 +27,14 @@ export const useAddProvider = (update?: () => void) => { title: '', withCloseButton: false, classNames: { - modal: 'bg-transparent text-textColor', + modal: 'text-textColor', }, - children: , size: 'auto', + children: ( + + + + ), }); }, []); }; @@ -42,24 +47,29 @@ export const AddProviderButton: FC<{ return ( ); }; @@ -261,28 +271,7 @@ export const CustomVariables: FC<{ const t = useT(); return ( -
- - +
+
- -

{t('social', 'Social')}

-
+
{social.map((item) => (
diff --git a/apps/frontend/src/components/launches/ai.image.tsx b/apps/frontend/src/components/launches/ai.image.tsx index 5d6e3469..9cfa00f8 100644 --- a/apps/frontend/src/components/launches/ai.image.tsx +++ b/apps/frontend/src/components/launches/ai.image.tsx @@ -68,7 +68,7 @@ ${type} } : {})} className={clsx( - 'relative ms-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-customColor21 bg-input', + 'relative ms-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-newBgLineColor bg-newColColor', value.length < 30 && 'opacity-25' )} > @@ -104,7 +104,7 @@ ${type} {value.length >= 30 && !loading && (
-
    +
      {list.map((p) => (
    • {p} diff --git a/apps/frontend/src/components/launches/ai.video.tsx b/apps/frontend/src/components/launches/ai.video.tsx index 65200636..ed0fd2c4 100644 --- a/apps/frontend/src/components/launches/ai.video.tsx +++ b/apps/frontend/src/components/launches/ai.video.tsx @@ -208,7 +208,7 @@ export const AiVideo: FC<{ } : {})} className={clsx( - 'relative ms-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-customColor21 bg-input', + 'relative ms-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-newBgLineColor bg-newColColor', value.length < 30 && 'opacity-25' )} > @@ -244,7 +244,7 @@ export const AiVideo: FC<{ {value.length >= 30 && !loading && (
      -
        +
          {data.map((p: any) => (
        • ([]); const searchParams = useSearchParams(); - const display = searchParams.get('display') || 'week'; + const [displaySaved, setDisplaySaved] = useCookie('calendar-display', 'week'); + const display = searchParams.get('display') || displaySaved; const [filters, setFilters] = useState({ currentDay: +(searchParams.get('day') || dayjs().day()) as | 0 @@ -166,6 +168,7 @@ export const CalendarWeekProvider: FC<{ display: 'week' | 'month' | 'day'; customer: string | null; }) => { + setDisplaySaved(filters.display); setFilters(filters); setInternalData([]); const path = [ diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index 99f3d949..97266c6c 100644 --- a/apps/frontend/src/components/launches/calendar.tsx +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -56,6 +56,7 @@ import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.m import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; +import { ModalWrapperComponent } from '../new-launch/modal.wrapper.component'; // Extend dayjs with necessary plugins extend(isSameOrAfter); @@ -142,10 +143,10 @@ export const DayView = () => { ); }, [integrations, posts]); return ( -
          +
          {options.map((option) => ( -
          +
          {dayjs() .utc() .startOf('day') @@ -155,7 +156,7 @@ export const DayView = () => {
          { }, [i18next.resolvedLanguage, currentYear, currentWeek]); return ( -
          +
          -
          -
          +
          +
          {localizedDays.map((day, index) => (
          -
          {day.name}
          -
          {day.day}
          +
          + {day.name} +
          +
          + {day.day === dayjs().format('L') && ( +
          + )} + {day.day} +
          ))} {hours.map((hour) => ( -
          +
          {convertTimeFormatBasedOnLocality(hour)}
          {days.map((day, indexDay) => ( -
          +
          { }, [currentYear, currentMonth]); return ( -
          +
          -
          +
          {localizedDays.map((day) => (
          {day}
          @@ -301,7 +314,7 @@ export const MonthView = () => { {calendarDays.map((date, index) => (
          @@ -564,26 +577,28 @@ export const CalendarColumn: FC<{ ? undefined : await new Promise((resolve) => { modal.openModal({ - title: t('select_set', 'Select a Set'), + title: '', closeOnClickOutside: true, closeOnEscape: true, - withCloseButton: true, + withCloseButton: false, onClose: () => resolve('exit'), classNames: { - modal: 'bg-secondary text-textColor', + modal: 'text-textColor', }, children: ( - { - resolve(selectedSet); - modal.closeAll(); - }} - onContinueWithoutSet={() => { - resolve(undefined); - modal.closeAll(); - }} - /> + + { + resolve(selectedSet); + modal.closeAll(); + }} + onContinueWithoutSet={() => { + resolve(undefined); + modal.closeAll(); + }} + /> + ), }); }); @@ -595,7 +610,7 @@ export const CalendarColumn: FC<{ closeOnEscape: false, withCloseButton: false, classNames: { - modal: 'w-[100%] max-w-[1400px] bg-transparent text-textColor', + modal: 'w-[100%] max-w-[1400px] text-textColor', }, children: ( , + children: ( + + + + ), size: '80%', // title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`, }); @@ -671,34 +690,30 @@ export const CalendarColumn: FC<{ const addProvider = useAddProvider(); return ( -
          +
          {display === 'month' && ( -
          - {getDate.date()} -
          +
          {getDate.date()}
          )}
          {list.map((post) => ( @@ -708,7 +723,7 @@ export const CalendarColumn: FC<{ 'text-textColor p-[2.5px] relative flex flex-col justify-center items-center' )} > -
          +
          + > +
          +
          )} {display === 'day' && (
          {selectedIntegrations.identifier}
          @@ -1048,7 +1067,7 @@ export const SetSelectionModal: FC<{ const t = useT(); return ( -
          +
          {t('choose_set_or_continue', 'Choose a set or continue without one')}
          @@ -1058,7 +1077,7 @@ export const SetSelectionModal: FC<{
          onSelect(set)} - className="p-3 border border-tableBorder rounded-lg cursor-pointer hover:bg-customColor31 transition-colors" + className="p-3 border border-tableBorder rounded-lg cursor-pointer hover:transition-colors" >
          {set.name}
          {set.description && ( @@ -1073,7 +1092,7 @@ export const SetSelectionModal: FC<{
          diff --git a/apps/frontend/src/components/launches/customer.modal.tsx b/apps/frontend/src/components/launches/customer.modal.tsx index 10cca9ea..5113863a 100644 --- a/apps/frontend/src/components/launches/customer.modal.tsx +++ b/apps/frontend/src/components/launches/customer.modal.tsx @@ -48,30 +48,8 @@ export const CustomerModal: FC<{ ); const { data } = useSWR('/customers', loadCustomers); return ( -
          - - - -
          +
          +
          { ); return (
          -
          -
          - - - -
          -
          +
          +
          - Today + + + +
          +
          +
          + Today +
          +
          +
          + + +
          -
          - - - -
          -
          +
          {week.display === 'day' ? `${dayjs() .month(week.currentMonth) @@ -275,11 +282,11 @@ export const Filters = () => { onChange={(customer: string) => setCustomer(customer)} integrations={week.integrations} /> -
          +
          @@ -287,8 +294,8 @@ export const Filters = () => {
          @@ -296,8 +303,8 @@ export const Filters = () => {
          diff --git a/apps/frontend/src/components/launches/generator/generator.tsx b/apps/frontend/src/components/launches/generator/generator.tsx index 9d531215..4d39fea7 100644 --- a/apps/frontend/src/components/launches/generator/generator.tsx +++ b/apps/frontend/src/components/launches/generator/generator.tsx @@ -19,6 +19,7 @@ import dayjs from 'dayjs'; import { Select } from '@gitroom/react/form/select'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; const FirstStep: FC = (props) => { const { integrations, reloadCalendarView } = useCalendar(); const modal = useModals(); @@ -275,28 +276,7 @@ export const GeneratorPopup = () => { modals.closeAll(); }, []); return ( -
          - -

          {t('generate_posts', 'Generate Posts')}

          +
          ); @@ -326,41 +306,50 @@ export const GeneratorComponent = () => { classNames: { modal: 'bg-transparent text-textColor', }, - size: '100%', + size: 'xl', children: ( - + + + ), }); }, [user, all]); return ( - +
          ); }; diff --git a/apps/frontend/src/components/launches/helpers/top.title.component.tsx b/apps/frontend/src/components/launches/helpers/top.title.component.tsx index e6cba125..2a44f958 100644 --- a/apps/frontend/src/components/launches/helpers/top.title.component.tsx +++ b/apps/frontend/src/components/launches/helpers/top.title.component.tsx @@ -1,13 +1,16 @@ import { FC, ReactNode } from 'react'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import clsx from 'clsx'; export const TopTitle: FC<{ title: string; shouldExpend?: boolean; removeTitle?: boolean; + extraClass?: string; expend?: () => void; collapse?: () => void; children?: ReactNode; + titleSize?: string; }> = (props) => { const { title, removeTitle, children, shouldExpend, expend, collapse } = props; @@ -25,42 +28,53 @@ export const TopTitle: FC<{ ); return ( -
          - {!removeTitle &&
          {translatedTitle}
          } - {children} - {shouldExpend !== undefined && ( -
          - {!shouldExpend ? ( - - - - ) : ( - - - - )} -
          +
          +
          + {!removeTitle && ( +
          + {translatedTitle} +
          + )} + {children} + {shouldExpend !== undefined && ( +
          + {!shouldExpend ? ( + + + + ) : ( + + + + )} +
          + )} +
          ); }; diff --git a/apps/frontend/src/components/launches/launches.component.tsx b/apps/frontend/src/components/launches/launches.component.tsx index 9db322d8..66611e11 100644 --- a/apps/frontend/src/components/launches/launches.component.tsx +++ b/apps/frontend/src/components/launches/launches.component.tsx @@ -25,7 +25,53 @@ import { useVariables } from '@gitroom/react/helpers/variable.context'; import { NewPost } from '@gitroom/frontend/components/launches/new.post'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { useIntegrationList } from '@gitroom/frontend/components/launches/helpers/use.integration.list'; +import useCookie from 'react-use-cookie'; +export const SVGLine = () => { + return ( + + + + + + + + + + + + + + + ); +}; interface MenuComponentInterface { refreshChannel: ( integration: Integration & { @@ -136,7 +182,7 @@ export const MenuGroupComponent: FC< )}
          @@ -191,16 +237,19 @@ export const MenuComponent: FC< })} key={integration.id} className={clsx( - 'flex gap-[8px] items-center', + 'flex gap-[12px] items-center bg-newBgColorInner hover:bg-boxHover group/profile transition-all rounded-e-[8px]', integration.refreshNeeded && 'cursor-pointer' )} >
          +
          + +
          {(integration.inBetweenSteps || integration.refreshNeeded) && (
          -
          +
          !
          @@ -219,24 +268,24 @@ export const MenuComponent: FC< {integration.identifier === 'youtube' ? ( ) : ( {integration.identifier} )}
          @@ -253,7 +302,7 @@ export const MenuComponent: FC< : {})} role="Handle" className={clsx( - 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden cursor-move', + 'group-[.sidebar]:hidden flex-1 whitespace-nowrap text-ellipsis overflow-hidden cursor-move', integration.disabled && 'opacity-50' )} > @@ -285,12 +334,9 @@ export const LaunchesComponent = () => { const fireEvents = useFireEvents(); const t = useT(); const [reload, setReload] = useState(false); + const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0'); - const { - isLoading, - data: integrations, - mutate, - } = useIntegrationList(); + const { isLoading, data: integrations, mutate } = useIntegrationList(); const totalNonDisabledChannels = useMemo(() => { return ( @@ -421,49 +467,71 @@ export const LaunchesComponent = () => { return ( -
          -
          -
          -
          -

          {t('channels')}

          -
          - {sortedIntegrations.length === 0 && ( -
          - {t('no_channels', 'No channels')} -
          - )} - {menuIntegrations.map((menu) => ( - - ))} -
          -
          - update(true)} /> - {sortedIntegrations?.length > 0 && } - {sortedIntegrations?.length > 0 && - user?.tier?.ai && - billingEnabled && } -
          - {process.env.NEXT_PUBLIC_VERSION - ? `${process.env.NEXT_PUBLIC_VERSION}` - : ''} -
          -
          -
          -
          - - -
          +
          +
          +

          {t('channels')}

          +
          setCollapseMenu(collapseMenu === '1' ? '0' : '1')} className="group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none"> + + +
          +
          + update(true)} /> +
          + {sortedIntegrations?.length > 0 && } + {sortedIntegrations?.length > 0 && + user?.tier?.ai && + billingEnabled && } +
          +
          + {process.env.NEXT_PUBLIC_VERSION + ? `${process.env.NEXT_PUBLIC_VERSION}` + : ''} +
          +
          +
          + {sortedIntegrations.length === 0 && ( +
          + {t('no_channels', 'No channels')} +
          + )} + {menuIntegrations.map((menu) => ( + + ))} +
          +
          +
          + +
          + +
          diff --git a/apps/frontend/src/components/launches/menu/menu.tsx b/apps/frontend/src/components/launches/menu/menu.tsx index 41892a93..9ed90a5a 100644 --- a/apps/frontend/src/components/launches/menu/menu.tsx +++ b/apps/frontend/src/components/launches/menu/menu.tsx @@ -27,6 +27,7 @@ import { useRouter } from 'next/navigation'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal'; import dayjs from 'dayjs'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; export const Menu: FC<{ canEnable: boolean; canDisable: boolean; @@ -142,7 +143,11 @@ export const Menu: FC<{ withCloseButton: false, closeOnEscape: false, closeOnClickOutside: false, - children: , + children: ( + + + + ), }); setShow(false); }, [integrations]); @@ -236,21 +241,23 @@ export const Menu: FC<{ ); modal.openModal({ classNames: { - modal: 'w-[100%] max-w-[600px] bg-transparent text-textColor', + modal: 'md', }, - size: '100%', + title: '', withCloseButton: false, closeOnEscape: true, closeOnClickOutside: true, children: ( - { - mutate(); - toast.show('Customer Updated', 'success'); - }} - /> + + { + mutate(); + toast.show('Customer Updated', 'success'); + }} + /> + ), }); setShow(false); @@ -260,14 +267,16 @@ export const Menu: FC<{ title: '', withCloseButton: false, classNames: { - modal: 'bg-transparent text-textColor', + modal: 'md', }, children: ( - router.push(url)} - variables={findIntegration.customFields} - /> + + router.push(url)} + variables={findIntegration.customFields} + /> + ), }); }, []); @@ -283,20 +292,21 @@ export const Menu: FC<{ height="24" viewBox="0 0 24 24" fill="none" + className="text-menuDots group-hover/profile:text-menuDotsHover" > {show && (
          e.stopPropagation()} - className={`absolute top-[100%] start-0 p-[8px] px-[20px] bg-fifth flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder ${interClass} text-nowrap`} + className={`absolute top-[100%] start-0 p-[12px] bg-newBgColorInner shadow-menu flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder ${interClass} text-nowrap`} > {canDisable && !findIntegration?.refreshNeeded && (
          @@ -313,7 +323,7 @@ export const Menu: FC<{ />
          -
          +
          {t('create_new_post', 'Create a new post')}
          @@ -322,7 +332,7 @@ export const Menu: FC<{ findIntegration?.refreshNeeded && !findIntegration.customFields && (
          @@ -339,14 +349,14 @@ export const Menu: FC<{ />
          -
          +
          {t('reconnect_channel', 'Reconnect channel')}
          )} {!!findIntegration?.isCustomFields && (
          @@ -363,14 +373,14 @@ export const Menu: FC<{ />
          -
          +
          {t('update_credentials', 'Update Credentials')}
          )} {findIntegration?.additionalSettings !== '[]' && (
          @@ -387,14 +397,14 @@ export const Menu: FC<{ />
          -
          +
          {t('additional_settings', 'Additional Settings')}
          )} {(canChangeProfilePicture || canChangeNickName) && (
          @@ -411,7 +421,7 @@ export const Menu: FC<{ />
          -
          +
          {t('change_bot', 'Change Bot')} {[ canChangeProfilePicture && 'Picture', @@ -422,7 +432,7 @@ export const Menu: FC<{
          )} -
          +
          -
          +
          {t('move_add_to_customer', 'Move / add to customer')}
          -
          +
          -
          +
          {t('edit_time_slots', 'Edit Time Slots')}
          {canEnable && (
          @@ -479,7 +489,7 @@ export const Menu: FC<{ />
          -
          +
          {t('enable_channel', 'Enable Channel')}
          @@ -487,7 +497,7 @@ export const Menu: FC<{ {canDisable && (
          @@ -504,13 +514,13 @@ export const Menu: FC<{ />
          -
          +
          {t('disable_channel', 'Disable Channel')}
          )} -
          +
          -
          {t('delete', 'Delete')}
          +
          {t('delete', 'Delete')}
          )} diff --git a/apps/frontend/src/components/launches/new.post.tsx b/apps/frontend/src/components/launches/new.post.tsx index efa76eb1..652c0880 100644 --- a/apps/frontend/src/components/launches/new.post.tsx +++ b/apps/frontend/src/components/launches/new.post.tsx @@ -6,6 +6,7 @@ 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 { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; export const NewPost = () => { const fetch = useFetch(); @@ -20,26 +21,28 @@ export const NewPost = () => { ? undefined : await new Promise((resolve) => { modal.openModal({ - title: t('select_set', 'Select a Set'), + title: '', closeOnClickOutside: true, closeOnEscape: true, - withCloseButton: true, + withCloseButton: false, onClose: () => resolve('exit'), classNames: { - modal: 'bg-secondary text-textColor', + modal: 'text-textColor', }, children: ( - { - resolve(selectedSet); - modal.closeAll(); - }} - onContinueWithoutSet={() => { - resolve(undefined); - modal.closeAll(); - }} - /> + + { + resolve(selectedSet); + modal.closeAll(); + }} + onContinueWithoutSet={() => { + resolve(undefined); + modal.closeAll(); + }} + /> + ), }); }); @@ -72,22 +75,26 @@ export const NewPost = () => { return ( ); diff --git a/apps/frontend/src/components/launches/statistics.tsx b/apps/frontend/src/components/launches/statistics.tsx index f42cb71b..d4680033 100644 --- a/apps/frontend/src/components/launches/statistics.tsx +++ b/apps/frontend/src/components/launches/statistics.tsx @@ -21,28 +21,7 @@ export const StatisticsModal: FC<{ loadStatistics ); return ( -
          - -

          {t('statistics', 'Statistics')}

          +
          {isLoading ? (
          {t('loading', 'Loading')}
          ) : ( diff --git a/apps/frontend/src/components/launches/time.table.tsx b/apps/frontend/src/components/launches/time.table.tsx index aa0fac91..7963b1df 100644 --- a/apps/frontend/src/components/launches/time.table.tsx +++ b/apps/frontend/src/components/launches/time.table.tsx @@ -108,29 +108,7 @@ export const TimeTable: FC<{ modal.closeAll(); }, [currentTimes]); return ( -
          - - - +
          {t('add_time_slot', 'Add Time Slot')} diff --git a/apps/frontend/src/components/layout/chrome.extension.component.tsx b/apps/frontend/src/components/layout/chrome.extension.component.tsx index 006f6a12..ae672ca7 100644 --- a/apps/frontend/src/components/layout/chrome.extension.component.tsx +++ b/apps/frontend/src/components/layout/chrome.extension.component.tsx @@ -8,18 +8,21 @@ export const ChromeExtensionComponent = () => { diff --git a/apps/frontend/src/components/layout/language.component.tsx b/apps/frontend/src/components/layout/language.component.tsx index 43033c75..34b9c453 100644 --- a/apps/frontend/src/components/layout/language.component.tsx +++ b/apps/frontend/src/components/layout/language.component.tsx @@ -16,9 +16,50 @@ import countries from 'i18n-iso-countries'; // Register required locales import countriesEn from 'i18n-iso-countries/langs/en.json'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { AddProviderComponent } from '@gitroom/frontend/components/launches/add.provider.component'; +import { ModalWrapperComponent } from '../new-launch/modal.wrapper.component'; + import clsx from 'clsx'; countries.registerLocale(countriesEn); + +const getCountryCodeForFlag = (languageCode: string) => { + // For multi-region languages, here are some common defaults + if (languageCode === 'en') return 'GB'; + if (languageCode === 'es') return 'ES'; + if (languageCode === 'ar') return 'SA'; + if (languageCode === 'zh') return 'CN'; + if (languageCode === 'he') return 'IL'; + if (languageCode === 'ja') return 'JP'; + if (languageCode === 'ko') return 'KR'; + + // Check if language code itself is a valid country code + try { + const countryName = countries.getName(languageCode.toUpperCase(), 'en'); + if (countryName) { + return languageCode.toUpperCase(); + } + } catch (e) { + // Not a valid country code, continue to next approach + } + + // Try to extract region code if language code has a region component (e.g., en-US) + const parts = languageCode.split('-'); + if (parts.length > 1) { + const regionCode = parts[1].toUpperCase(); + try { + const countryName = countries.getName(regionCode, 'en'); + if (countryName) { + return regionCode; + } + } catch (e) { + // Not a valid country code, continue to next approach + } + } + + // For most language codes that match their primary country + // Examples: fr->FR, it->IT, de->DE, etc. + return languageCode.toUpperCase(); +}; + export const ChangeLanguageComponent = () => { const currentLanguage = i18next.resolvedLanguage || fallbackLng; const availableLanguages = languages; @@ -46,72 +87,15 @@ export const ChangeLanguageComponent = () => { } }, []); - // Get appropriate country code for the flag based on language code - const getCountryCodeForFlag = useCallback((languageCode: string) => { - // For multi-region languages, here are some common defaults - if (languageCode === 'en') return 'GB'; - if (languageCode === 'es') return 'ES'; - if (languageCode === 'ar') return 'SA'; - if (languageCode === 'zh') return 'CN'; - if (languageCode === 'he') return 'IL'; - if (languageCode === 'ja') return 'JP'; - if (languageCode === 'ko') return 'KR'; - - // Check if language code itself is a valid country code - try { - const countryName = countries.getName(languageCode.toUpperCase(), 'en'); - if (countryName) { - return languageCode.toUpperCase(); - } - } catch (e) { - // Not a valid country code, continue to next approach - } - - // Try to extract region code if language code has a region component (e.g., en-US) - const parts = languageCode.split('-'); - if (parts.length > 1) { - const regionCode = parts[1].toUpperCase(); - try { - const countryName = countries.getName(regionCode, 'en'); - if (countryName) { - return regionCode; - } - } catch (e) { - // Not a valid country code, continue to next approach - } - } - - // For most language codes that match their primary country - // Examples: fr->FR, it->IT, de->DE, etc. - return languageCode.toUpperCase(); - }, []); return ( -
          - -

          {t('change_language', 'Change Language')}

          +
          {availableLanguages.map((language) => (
          handleLanguageChange(language)} > @@ -135,33 +119,41 @@ export const ChangeLanguageComponent = () => { }; export const LanguageComponent = () => { const modal = useModals(); + const currentLanguage = i18next.resolvedLanguage || fallbackLng; + const t = useT(); const openModal = () => { modal.openModal({ title: '', withCloseButton: false, - classNames: { - modal: 'bg-transparent text-textColor', - }, modalId: 'change-language', - children: , + children: ( + + + + ), size: 'lg', centered: true, }); }; return ( - - - +
          ); }; diff --git a/apps/frontend/src/components/layout/mode.component.tsx b/apps/frontend/src/components/layout/mode.component.tsx index 2504670b..73580ce3 100644 --- a/apps/frontend/src/components/layout/mode.component.tsx +++ b/apps/frontend/src/components/layout/mode.component.tsx @@ -1,12 +1,14 @@ 'use client'; import { useCallback, useEffect, useState } from 'react'; +import useCookie from 'react-use-cookie'; const ModeComponent = () => { - const [mode, setMode] = useState(localStorage.getItem('mode') || 'dark'); + const [mode, setMode] = useCookie('mode', 'dark'); + const changeMode = useCallback(() => { setMode(mode === 'dark' ? 'light' : 'dark'); - localStorage.setItem('mode', mode === 'dark' ? 'light' : 'dark'); }, [mode]); + useEffect(() => { document.body.classList.remove('dark', 'light'); document.body.classList.add(mode); @@ -16,14 +18,17 @@ const ModeComponent = () => { {mode === 'dark' ? ( ) : ( @@ -31,12 +36,15 @@ const ModeComponent = () => { xmlns="http://www.w3.org/2000/svg" width="24" height="24" - viewBox="0 0 32 32" + viewBox="0 0 24 24" fill="none" > )} diff --git a/apps/frontend/src/components/layout/settings.component.tsx b/apps/frontend/src/components/layout/settings.component.tsx index f8f39311..ac43dc8b 100644 --- a/apps/frontend/src/components/layout/settings.component.tsx +++ b/apps/frontend/src/components/layout/settings.component.tsx @@ -1,10 +1,14 @@ 'use client'; import { useModals } from '@mantine/modals'; -import React, { FC, Ref, useCallback, useEffect, useMemo } from 'react'; -import { Input } from '@gitroom/react/form/input'; -import { Button } from '@gitroom/react/form/button'; -import { Textarea } from '@gitroom/react/form/textarea'; +import React, { + FC, + Ref, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { showMediaBox } from '@gitroom/frontend/components/media/media.component'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; @@ -22,11 +26,10 @@ import { PublicComponent } from '@gitroom/frontend/components/public-api/public. import Link from 'next/link'; import { Webhooks } from '@gitroom/frontend/components/webhooks/webhooks'; import { Sets } from '@gitroom/frontend/components/sets/sets'; -import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; -import { Tabs } from '@mantine/core'; import { SignaturesComponent } from '@gitroom/frontend/components/settings/signatures.component'; import { Autopost } from '@gitroom/frontend/components/autopost/autopost'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { SVGLine } from '@gitroom/frontend/components/launches/launches.component'; export const SettingsPopup: FC<{ getRef?: Ref; }> = (props) => { @@ -63,6 +66,7 @@ export const SettingsPopup: FC<{ const remove = useCallback(() => { form.setValue('picture', null); }, []); + const submit = useCallback(async (val: any) => { await fetch('/user/personal', { method: 'POST', @@ -75,7 +79,8 @@ export const SettingsPopup: FC<{ swr.mutate('/marketplace/account'); close(); }, []); - const defaultValueForTabs = useMemo(() => { + + const [tab, setTab] = useState(() => { if (user?.tier?.team_members && isGeneral) { return 'teams'; } @@ -86,106 +91,126 @@ export const SettingsPopup: FC<{ return 'autopost'; } return 'sets'; - }, [user?.tier, isGeneral]); + }); const t = useT(); + const list = useMemo(() => { + const arr = []; + // Populate tabs based on user permissions + if (user?.tier?.team_members && isGeneral) { + arr.push({ tab: 'teams', label: t('teams', 'Teams') }); + } + if (user?.tier?.webhooks) { + arr.push({ tab: 'webhooks', label: t('webhooks_1', 'Webhooks') }); + } + if (user?.tier?.autoPost) { + arr.push({ tab: 'autopost', label: t('auto_post', 'Auto Post') }); + } + if (user?.tier.current !== 'FREE') { + arr.push({ tab: 'sets', label: t('sets', 'Sets') }); + } + if (user?.tier.current !== 'FREE') { + arr.push({ tab: 'signatures', label: t('signatures', 'Signatures') }); + } + if (user?.tier?.public_api && isGeneral && showLogout) { + arr.push({ tab: 'api', label: t('public_api', 'Public API') }); + } + + return arr; + }, [user, isGeneral, showLogout, t]); useEffect(() => { loadProfile(); }, []); + return ( - - - {!!getRef && ( - - )} -
          - - - {/* Profile */} - {!!user?.tier?.team_members && isGeneral && ( - {t('teams', 'Teams')} + <> +
          +
          + {list.map(({ tab: tabKey, label }) => ( +
          - {t('webhooks_1', 'Webhooks')} - - )} - {!!user?.tier?.autoPost && ( - - {t('auto_post', 'Auto Post')} - - )} - {user?.tier.current !== 'FREE' && ( - {t('sets', 'Sets')} - )} - {user?.tier.current !== 'FREE' && ( - - {t('signatures', 'Signatures')} - - )} - {!!user?.tier?.public_api && isGeneral && showLogout && ( - {t('public_api', 'Public API')} - )} - - - {!!user?.tier?.team_members && isGeneral && ( - - - - )} - - {!!user?.tier?.webhooks && ( - - - - )} - - {!!user?.tier?.autoPost && ( - - - - )} - - {user?.tier.current !== 'FREE' && ( - - - - )} - - {user?.tier.current !== 'FREE' && ( - - - - )} - {!!user?.tier?.public_api && isGeneral && showLogout && ( - - - - )} - - + onClick={() => setTab(tabKey)} + > +
          + +
          + {label} +
          + ))} +
          +
          {showLogout && (
          )}
          - - +
          +
          + +
          + {!!getRef && ( + + )} +
          + {tab === 'teams' && !!user?.tier?.team_members && isGeneral && ( +
          + +
          + )} + + {tab === 'webhooks' && !!user?.tier?.webhooks && ( +
          + +
          + )} + + {tab === 'autopost' && !!user?.tier?.autoPost && ( +
          + +
          + )} + + {tab === 'sets' && user?.tier.current !== 'FREE' && ( +
          + +
          + )} + + {tab === 'signatures' && user?.tier.current !== 'FREE' && ( +
          + +
          + )} + + {tab === 'api' && + !!user?.tier?.public_api && + isGeneral && + showLogout && ( +
          + +
          + )} +
          +
          +
          +
          + ); }; export const SettingsComponent = () => { diff --git a/apps/frontend/src/components/layout/title.tsx b/apps/frontend/src/components/layout/title.tsx index 31deb521..5fa3099a 100644 --- a/apps/frontend/src/components/layout/title.tsx +++ b/apps/frontend/src/components/layout/title.tsx @@ -2,16 +2,13 @@ import { usePathname } from 'next/navigation'; import { useMemo } from 'react'; -import { useMenuItems } from '@gitroom/frontend/components/layout/top.menu'; +import { useMenuItem } from '@gitroom/frontend/components/layout/top.menu'; export const Title = () => { const path = usePathname(); - const menuItems = useMenuItems(); + const { all: menuItems } = useMenuItem(); const currentTitle = useMemo(() => { return menuItems.find((item) => path.indexOf(item.path) > -1)?.name; }, [path]); - return ( -
          -

          {currentTitle}

          -
          - ); + + return

          {currentTitle}

          ; }; diff --git a/apps/frontend/src/components/layout/top.menu.tsx b/apps/frontend/src/components/layout/top.menu.tsx index 6a80c997..bb1220e2 100644 --- a/apps/frontend/src/components/layout/top.menu.tsx +++ b/apps/frontend/src/components/layout/top.menu.tsx @@ -1,92 +1,293 @@ 'use client'; -import { FC } from 'react'; -import Link from 'next/link'; -import clsx from 'clsx'; +import { FC, ReactNode } from 'react'; import { usePathname } from 'next/navigation'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const useMenuItems = () => { +import { MenuItem } from '@gitroom/frontend/components/new-layout/menu-item'; + +interface MenuItemInterface { + name: string; + icon: ReactNode; + path: string; + role?: string[]; + hide?: boolean; + requireBilling?: boolean; +} + +export const useMenuItem = () => { const { isGeneral } = useVariables(); const t = useT(); - return [ - ...(!isGeneral - ? [ - { - name: t('analytics', 'Analytics'), - icon: 'analytics', - path: '/analytics', - }, - ] - : []), + const firstMenu = [ { name: isGeneral ? t('calendar', 'Calendar') : t('launches', 'Launches'), - icon: 'launches', + icon: ( + + + + ), path: '/launches', }, - ...(isGeneral - ? [ - { - name: t('analytics', 'Analytics'), - icon: 'analytics', - path: '/analytics', - }, - ] - : []), - ...(!isGeneral - ? [ - { - name: t('settings', 'Settings'), - icon: 'settings', - path: '/settings', - role: ['ADMIN', 'SUPERADMIN'], - }, - ] - : []), + { + name: t('analytics', 'Analytics'), + icon: ( + + + + ), + path: '/analytics', + }, + { + name: t('media', 'Media'), + icon: ( + + + + ), + path: '/media', + }, { name: t('plugs', 'Plugs'), - icon: 'plugs', + icon: ( + + + + ), path: '/plugs', }, { name: t('integrations', 'Integrations'), - icon: 'integrations', + icon: ( + + + + ), path: '/third-party', }, + ] satisfies MenuItemInterface[] as MenuItemInterface[]; + + const secondMenu = [ + { + name: t('affiliate', 'Affiliate'), + icon: ( + + + + + + + + + ), + path: 'https://affiliate.postiz.com', + role: ['ADMIN', 'SUPERADMIN', 'USER'], + requireBilling: true, + }, { name: t('billing', 'Billing'), - icon: 'billing', + icon: ( + + + + ), path: '/billing', role: ['ADMIN', 'SUPERADMIN'], requireBilling: true, }, { name: t('settings', 'Settings'), - icon: 'settings', + icon: ( + + + + + ), path: '/settings', role: ['ADMIN', 'SUPERADMIN'], - hide: true, }, - { - name: t('affiliate', 'Affiliate'), - icon: 'affiliate', - path: 'https://affiliate.postiz.com', - role: ['ADMIN', 'SUPERADMIN', 'USER'], - requireBilling: true, - }, - ]; + ] satisfies MenuItemInterface[] as MenuItemInterface[]; + + return { + all: [...firstMenu, ...secondMenu], + firstMenu, + secondMenu, + }; }; + export const TopMenu: FC = () => { - const path = usePathname(); const user = useUser(); - const { billingEnabled } = useVariables(); - const menuItems = useMenuItems(); + const { firstMenu, secondMenu } = useMenuItem(); + const { isGeneral, billingEnabled } = useVariables(); return ( -
          -
            - {menuItems + <> +
            + { + // @ts-ignore + user?.orgId && + // @ts-ignore + (user.tier !== 'FREE' || !isGeneral || !billingEnabled) && + firstMenu + .filter((f) => { + if (f.hide) { + return false; + } + if (f.requireBilling && !billingEnabled) { + return false; + } + if (f.name === 'Billing' && user?.isLifetime) { + return false; + } + if (f.role) { + return f.role.includes(user?.role!); + } + return true; + }) + .map((item, index) => ( + + )) + } +
            +
            + {secondMenu .filter((f) => { if (f.hide) { return false; @@ -103,34 +304,14 @@ export const TopMenu: FC = () => { return true; }) .map((item, index) => ( -
          • - -1 ? '_blank' : '_self'} - href={item.path} - className={clsx( - 'flex gap-2 items-center box px-[6px] md:px-[24px] py-[8px]', - menuItems - .filter((f) => { - if (f.hide) { - return false; - } - if (f.role) { - return f.role.includes(user?.role!); - } - return true; - }) - .map((p) => (path.indexOf(p.path) > -1 ? index : -1)) - .indexOf(index) === index - ? 'text-primary showbox' - : 'text-gray' - )} - > - {item.name} - -
          • + ))} -
          -
          +
          + ); }; diff --git a/apps/frontend/src/components/media/media.component.tsx b/apps/frontend/src/components/media/media.component.tsx index ea172377..12d858f3 100644 --- a/apps/frontend/src/components/media/media.component.tsx +++ b/apps/frontend/src/components/media/media.component.tsx @@ -156,6 +156,7 @@ export const showMediaBox = ( const CHUNK_SIZE = 1024 * 1024; export const MediaBox: FC<{ setMedia: (params: { id: string; path: string }[]) => void; + standalone?: boolean; type?: 'image' | 'video'; closeModal: () => void; }> = (props) => { @@ -182,6 +183,9 @@ export const MediaBox: FC<{ const setNewMedia = useCallback( (media: Media) => () => { + if (props.standalone) { + return; + } setSelectedMedia( selectedMedia.find((p) => p.id === media.id) ? selectedMedia.filter((f) => f.id !== media.id) @@ -200,12 +204,18 @@ export const MediaBox: FC<{ const addNewMedia = useCallback( (media: Media[]) => () => { + if (props.standalone) { + return; + } setSelectedMedia((currentMedia) => [...currentMedia, ...media]); // closeModal(); }, [selectedMedia] ); const addMedia = useCallback(async () => { + if (props.standalone) { + return; + } // @ts-ignore setMedia(selectedMedia); closeModal(); @@ -223,6 +233,11 @@ export const MediaBox: FC<{ 0, untilLastMedia === -1 ? newData.results.length : untilLastMedia ); + + if (props.standalone) { + return; + } + addNewMedia(onlyNewMedia)(); }, [mutate, addNewMedia, mediaList, selectedMedia] @@ -290,6 +305,9 @@ export const MediaBox: FC<{ }, [mutate] ); + + const refNew = useRef(null); + useEffect(() => { if (data?.pages) { setPages(data.pages); @@ -299,37 +317,69 @@ export const MediaBox: FC<{ } }, [data]); + useEffect(() => { + refNew?.current?.scrollIntoView({ + behavior: 'smooth', + }); + }, []); + const t = useT(); return ( -
          -
          +
          +
          - + {!props.standalone ? ( + + ) : ( +
          + )}
          - + + + + + ) : ( +
          + )}
          {t( @@ -402,7 +452,10 @@ export const MediaBox: FC<{ <> {selectedMedia.length > 0 && (
          - +
          {current && (!!SettingsComponent || !!data?.internalPlugs?.length) && (
          - + )}) +
          )}
          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 73f2d800..6bf6ab36 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 @@ -183,10 +183,10 @@ export const ShowAllProviders = forwardRef((props, ref) => { }} >
          -
          - +
          {global?.[0]?.content?.length === 0 ? ( diff --git a/apps/frontend/src/components/new-launch/select.current.tsx b/apps/frontend/src/components/new-launch/select.current.tsx index 0e4283a0..8222939c 100644 --- a/apps/frontend/src/components/new-launch/select.current.tsx +++ b/apps/frontend/src/components/new-launch/select.current.tsx @@ -72,7 +72,7 @@ export const SelectCurrent: FC = () => { return ( <> -
          +
          { setHide(true); setCurrent('global'); }} - className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + className="cursor-pointer flex gap-[8px] items-center bg-newBgLineColor p-[10px] rounded-tl-[4px] rounded-tr-[4px]" >
          { setCurrent(integration.id); }} key={integration.id} - className="cursor-pointer flex gap-[8px] items-center bg-customColor2 p-[10px] rounded-tl-[4px] rounded-tr-[4px]" + className="cursor-pointer flex gap-[8px] items-center bg-newBgLineColor p-[10px] rounded-tl-[4px] rounded-tr-[4px]" >
          { + const user = useUser(); + const { isGeneral, billingEnabled } = useVariables(); + const t = useT(); + return ( +
          +
          +
          +

          + {t( + 'join_10000_entrepreneurs_who_use_postiz', + 'Join 10,000+ Entrepreneurs Who Use Postiz' + )} +
          + {t( + 'to_manage_all_your_social_media_channels', + 'To Manage All Your Social Media Channels' + )} +

          +
          + {user?.allowTrial && ( +
          +
          +
          + + + +
          +
          {t('100_no_risk_trial', '100% no-risk trial')}
          +
          +
          +
          + + + +
          +
          + {t( + 'pay_nothing_for_the_first_7_days', + 'Pay nothing for the first 7 days' + )} +
          +
          +
          +
          + + + +
          +
          + {t('cancel_anytime_hassle_free', 'Cancel anytime, hassle-free')} +
          +
          +
          + )} +
          + +
          + ); +}; diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx new file mode 100644 index 00000000..49dcfd64 --- /dev/null +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -0,0 +1,131 @@ +'use client'; + +import { ReactNode, useCallback } from 'react'; +import { Logo } from '@gitroom/frontend/components/new-layout/logo'; +import { MenuItem } from '@gitroom/frontend/components/new-layout/menu-item'; +import { Plus_Jakarta_Sans } from 'next/font/google'; +const ModeComponent = dynamic( + () => import('@gitroom/frontend/components/layout/mode.component'), + { + ssr: false, + } +); + +import clsx from 'clsx'; +import dynamic from 'next/dynamic'; +import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { useVariables } from '@gitroom/react/helpers/variable.context'; +import { useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; +import { CheckPayment } from '@gitroom/frontend/components/layout/check.payment'; +import { ToolTip } from '@gitroom/frontend/components/layout/top.tip'; +import { ShowMediaBoxModal } from '@gitroom/frontend/components/media/media.component'; +import { ShowLinkedinCompany } from '@gitroom/frontend/components/launches/helpers/linkedin.component'; +import { MediaSettingsLayout } from '@gitroom/frontend/components/launches/helpers/media.settings.component'; +import { Toaster } from '@gitroom/react/toaster/toaster'; +import { ShowPostSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector'; +import { NewSubscription } from '@gitroom/frontend/components/layout/new.subscription'; +import { Onboarding } from '@gitroom/frontend/components/onboarding/onboarding'; +import { Support } from '@gitroom/frontend/components/layout/support'; +import { ContinueProvider } from '@gitroom/frontend/components/layout/continue.provider'; +import { ContextWrapper } from '@gitroom/frontend/components/layout/user.context'; +import { CopilotKit } from '@copilotkit/react-core'; +import { MantineWrapper } from '@gitroom/react/helpers/mantine.wrapper'; +import { Impersonate } from '@gitroom/frontend/components/layout/impersonate'; +import { Title } from '@gitroom/frontend/components/layout/title'; +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'; + +const jakartaSans = Plus_Jakarta_Sans({ + weight: ['600', '500'], + style: ['normal', 'italic'], + subsets: ['latin'], +}); + +export const LayoutComponent = ({ children }: { children: ReactNode }) => { + const fetch = useFetch(); + + const { backendUrl, billingEnabled, isGeneral } = useVariables(); + const searchParams = useSearchParams(); + const load = useCallback(async (path: string) => { + return await (await fetch(path)).json(); + }, []); + const { data: user, mutate } = useSWR('/user/self', load, { + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + refreshWhenOffline: false, + refreshWhenHidden: false, + }); + + if (!user) return null; + + return ( + + + + {user.tier === 'FREE' && searchParams.get('check') && ( + + )} + + + + + + + + {user.tier !== 'FREE' && } + + +
          + {/*
          {user?.admin ? :
          }
          */} + {user.tier === 'FREE' && isGeneral && billingEnabled ? ( + + ) : ( +
          +
          +
          +
          + + +
          +
          +
          +
          +
          +
          + + </div> + <div className="flex gap-[20px] text-textItemBlur"> + <div className="hover:text-newTextColor"> + <ModeComponent /> + </div> + <div className="w-[1px] h-[20px] bg-blockSeparator" /> + <LanguageComponent /> + <ChromeExtensionComponent /> + <div className="w-[1px] h-[20px] bg-blockSeparator" /> + <NotificationComponent /> + </div> + </div> + <div className="flex flex-1 gap-[1px]">{children}</div> + </div> + </div> + )} + </div> + </MantineWrapper> + </CopilotKit> + </ContextWrapper> + ); +}; diff --git a/apps/frontend/src/components/new-layout/layout.media.component.tsx b/apps/frontend/src/components/new-layout/layout.media.component.tsx new file mode 100644 index 00000000..c95dd990 --- /dev/null +++ b/apps/frontend/src/components/new-layout/layout.media.component.tsx @@ -0,0 +1,9 @@ +'use client'; + +import { MediaBox } from '@gitroom/frontend/components/media/media.component'; + +export const MediaLayoutComponent = () => { + return ( + <MediaBox setMedia={() => {}} closeModal={() => {}} standalone={true} /> + ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/new-layout/logo.tsx b/apps/frontend/src/components/new-layout/logo.tsx new file mode 100644 index 00000000..dfa1481d --- /dev/null +++ b/apps/frontend/src/components/new-layout/logo.tsx @@ -0,0 +1,32 @@ +'use client'; + +export const Logo = () => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="60" + height="60" + viewBox="0 0 60 60" + fill="none" + className="mt-[8px]" + > + <path + d="M12.8816 11.4648C12.9195 12.0781 12.9731 12.7698 13.0342 13.5594L15.2408 42.0719C15.4874 45.2588 15.6107 46.8522 16.3251 48.0214C16.9535 49.0498 17.8913 49.853 19.0042 50.3156C20.2694 50.8416 21.8629 50.7183 25.0497 50.4717L46.8877 48.7817C48.9537 48.6218 50.3501 48.5137 51.3952 48.2628C51.0447 49.0858 50.5039 49.8189 49.8121 50.3992C48.7623 51.2797 47.205 51.6389 44.0905 52.3574L22.7476 57.2805C19.633 57.9989 18.0757 58.3581 16.7463 58.0264C15.5769 57.7346 14.5299 57.0801 13.7554 56.1566C12.8749 55.1069 12.5156 53.5496 11.7972 50.435L5.36942 22.569C4.651 19.4544 4.29178 17.8972 4.62351 16.5677C4.91531 15.3983 5.56982 14.3513 6.49325 13.5768C7.54303 12.6963 9.10032 12.3371 12.2149 11.6186L12.8816 11.4648Z" + fill="#612BD3" + /> + <path + d="M47.0122 2.5752C47.9217 2.57909 48.5299 2.67533 49.0386 2.88672C50.0099 3.29052 50.829 3.99206 51.3774 4.88965C51.6647 5.35982 51.8537 5.94554 51.9976 6.84375C52.1427 7.7505 52.2327 8.91127 52.3569 10.5166L54.564 39.0283C54.6882 40.6337 54.7769 41.7946 54.7729 42.7129C54.7691 43.6226 54.6729 44.2305 54.4614 44.7393C54.0576 45.7105 53.3561 46.5287 52.4585 47.0771C51.9883 47.3644 51.4027 47.5534 50.5044 47.6973C49.5977 47.8424 48.4376 47.9334 46.8325 48.0576L24.9937 49.748C23.3886 49.8723 22.2282 49.961 21.3101 49.957C20.4003 49.9531 19.7925 49.8561 19.2837 49.6445C18.3124 49.2407 17.4933 48.5402 16.9448 47.6426C16.6576 47.1724 16.4685 46.5867 16.3247 45.6885C16.1795 44.7817 16.0896 43.621 15.9653 42.0156L13.7583 13.5029C13.6341 11.8979 13.5454 10.7375 13.5493 9.81934C13.5532 8.90971 13.6494 8.30172 13.8608 7.79297C14.2646 6.82169 14.9662 6.00253 15.8638 5.4541C16.3339 5.16692 16.9197 4.97778 17.8179 4.83398C18.7246 4.68882 19.8854 4.59884 21.4907 4.47461L43.3286 2.78418C44.9336 2.65997 46.094 2.57129 47.0122 2.5752Z" + stroke="#131019" + stroke-width="1.45254" + /> + <path + d="M21.5681 5.49237L43.4061 3.80233C45.0283 3.67679 46.1402 3.59211 47.007 3.59582C47.8534 3.59945 48.3108 3.69053 48.6454 3.82963C49.4173 4.15056 50.0679 4.70763 50.5037 5.421C50.6927 5.7302 50.853 6.16816 50.9868 7.00389C51.1239 7.85984 51.2113 8.97152 51.3368 10.5937L53.5434 39.1062C53.6689 40.7284 53.7536 41.8403 53.7499 42.7071C53.7463 43.5535 53.6552 44.0109 53.5161 44.3455C53.1952 45.1174 52.6381 45.7679 51.9247 46.2038C51.6155 46.3927 51.1776 46.5531 50.3418 46.6869C49.4859 46.824 48.3742 46.9114 46.752 47.0369L24.914 48.7269C23.2918 48.8525 22.1799 48.9372 21.3131 48.9335C20.4667 48.9298 20.0093 48.8387 19.6747 48.6996C18.9028 48.3787 18.2522 47.8216 17.8164 47.1083C17.6274 46.7991 17.4671 46.3611 17.3333 45.5254C17.1962 44.6694 17.1088 43.5578 16.9833 41.9356L14.7767 13.4231C14.6512 11.8009 14.5665 10.689 14.5702 9.82217C14.5738 8.97581 14.6649 8.51838 14.804 8.1838C15.1249 7.41186 15.682 6.76133 16.3954 6.32545C16.7046 6.13653 17.1425 5.97616 17.9783 5.84235C18.8342 5.70531 19.9459 5.61791 21.5681 5.49237Z" + fill="white" + /> + <path + d="M31.0188 12.0956L31.2277 14.7969C31.6025 14.3332 32.0909 13.9331 32.6929 13.5967C33.2931 13.2362 34.0374 13.0217 34.9259 12.953C35.7423 12.8899 36.5227 12.9865 37.2672 13.243C38.0357 13.4976 38.723 13.9517 39.3291 14.6054C39.9574 15.2332 40.4832 16.0983 40.9067 17.2009C41.3301 18.3035 41.604 19.6592 41.7284 21.268C41.8175 22.4205 41.7985 23.5814 41.6715 24.7507C41.5685 25.9182 41.3002 26.9776 40.8665 27.929C40.4328 28.8805 39.806 29.6777 38.9861 30.3209C38.1884 30.9381 37.1532 31.2959 35.8806 31.3943C34.9681 31.4649 34.2385 31.4005 33.6917 31.2012C33.143 30.9779 32.7236 30.7084 32.4335 30.3927L33.1102 39.145L28.0238 40.8427L25.8323 12.4966L31.0188 12.0956ZM33.9095 28.3944C34.5338 28.3462 35.0463 28.1012 35.4469 27.6596C35.8457 27.194 36.1392 26.6157 36.3273 25.9248C36.5395 25.2321 36.662 24.4738 36.6949 23.6499C36.75 22.8002 36.746 21.9672 36.6829 21.1508C36.5808 19.8301 36.38 18.7949 36.0804 18.0451C35.8049 17.2934 35.4972 16.7495 35.1572 16.4135C34.8153 16.0534 34.4846 15.8374 34.165 15.7655C33.8694 15.6918 33.6376 15.6614 33.4695 15.6744C33.0373 15.7078 32.6292 15.8963 32.2451 16.2401C31.8592 16.5598 31.5934 17.0272 31.4477 17.6423L32.2246 27.6913C32.3595 27.8741 32.5665 28.0514 32.8455 28.2231C33.1226 28.3707 33.4773 28.4278 33.9095 28.3944Z" + fill="#131019" + /> + </svg> + ); +}; diff --git a/apps/frontend/src/components/new-layout/menu-item.tsx b/apps/frontend/src/components/new-layout/menu-item.tsx new file mode 100644 index 00000000..094120bc --- /dev/null +++ b/apps/frontend/src/components/new-layout/menu-item.tsx @@ -0,0 +1,28 @@ +'use client'; +import { FC, ReactNode } from 'react'; +import { usePathname } from 'next/navigation'; +import clsx from 'clsx'; +import Link from 'next/link'; + +export const MenuItem: FC<{ label: string; icon: ReactNode; path: string }> = ({ + label, + icon, + path, +}) => { + const currentPath = usePathname(); + const isActive = path.indexOf(currentPath) === 0; + + return ( + <Link + prefetch={true} + href={path} + className={clsx( + 'w-full h-[54px] py-[8px] px-[6px] gap-[4px] flex flex-col text-[10px] font-[600] items-center justify-center rounded-[12px] hover:text-textItemFocused hover:bg-boxFocused', + isActive ? 'text-textItemFocused bg-boxFocused' : 'text-textItemBlur' + )} + > + <div>{icon}</div> + <div className="text-[10px]">{label}</div> + </Link> + ); +}; diff --git a/apps/frontend/src/components/notifications/notification.component.tsx b/apps/frontend/src/components/notifications/notification.component.tsx index 6ddabef3..847d5fc8 100644 --- a/apps/frontend/src/components/notifications/notification.component.tsx +++ b/apps/frontend/src/components/notifications/notification.component.tsx @@ -112,22 +112,31 @@ const NotificationComponent = () => { return ( <div className="relative cursor-pointer select-none" ref={ref}> <div onClick={changeShow}> - {data && data.total > 0 && ( - <div className="w-[13px] h-[13px] bg-red-500 rounded-full absolute -start-[2px] -top-[2px] text-[10px] text-center flex justify-center items-center"> - {data.total} - </div> - )} <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" + className="hover:text-newTextColor" > <path - d="M20.7927 16.4944C20.2724 15.5981 19.4989 13.0622 19.4989 9.75C19.4989 7.76088 18.7087 5.85322 17.3022 4.4467C15.8957 3.04018 13.988 2.25 11.9989 2.25C10.0098 2.25 8.10214 3.04018 6.69561 4.4467C5.28909 5.85322 4.49891 7.76088 4.49891 9.75C4.49891 13.0631 3.72454 15.5981 3.20423 16.4944C3.07135 16.7222 3.00091 16.9811 3.00001 17.2449C2.9991 17.5086 3.06776 17.768 3.19907 17.9967C3.33037 18.2255 3.51968 18.4156 3.74789 18.5478C3.9761 18.6801 4.23515 18.7498 4.49891 18.75H8.32485C8.49789 19.5967 8.95806 20.3577 9.62754 20.9042C10.297 21.4507 11.1347 21.7492 11.9989 21.7492C12.8631 21.7492 13.7008 21.4507 14.3703 20.9042C15.0398 20.3577 15.4999 19.5967 15.673 18.75H19.4989C19.7626 18.7496 20.0215 18.6798 20.2496 18.5475C20.4777 18.4151 20.6669 18.225 20.7981 17.9963C20.9292 17.7676 20.9978 17.5083 20.9969 17.2446C20.9959 16.9809 20.9255 16.7222 20.7927 16.4944ZM11.9989 20.25C11.5337 20.2499 11.0801 20.1055 10.7003 19.8369C10.3205 19.5683 10.0333 19.1886 9.87829 18.75H14.1195C13.9645 19.1886 13.6773 19.5683 13.2975 19.8369C12.9178 20.1055 12.4641 20.2499 11.9989 20.25ZM4.49891 17.25C5.22079 16.0087 5.99891 13.1325 5.99891 9.75C5.99891 8.1587 6.63105 6.63258 7.75627 5.50736C8.88149 4.38214 10.4076 3.75 11.9989 3.75C13.5902 3.75 15.1163 4.38214 16.2416 5.50736C17.3668 6.63258 17.9989 8.1587 17.9989 9.75C17.9989 13.1297 18.7752 16.0059 19.4989 17.25H4.49891Z" - fill="currentColor" + d="M14 21H10M18 8C18 6.4087 17.3679 4.88258 16.2427 3.75736C15.1174 2.63214 13.5913 2 12 2C10.4087 2 8.8826 2.63214 7.75738 3.75736C6.63216 4.88258 6.00002 6.4087 6.00002 8C6.00002 11.0902 5.22049 13.206 4.34968 14.6054C3.61515 15.7859 3.24788 16.3761 3.26134 16.5408C3.27626 16.7231 3.31488 16.7926 3.46179 16.9016C3.59448 17 4.19261 17 5.38887 17H18.6112C19.8074 17 20.4056 17 20.5382 16.9016C20.6852 16.7926 20.7238 16.7231 20.7387 16.5408C20.7522 16.3761 20.3849 15.7859 19.6504 14.6054C18.7795 13.206 18 11.0902 18 8Z" + stroke="currentColor" + strokeWidth="1.5" + strokeLinecap="round" + strokeLinejoin="round" /> + {data && data.total > 0 && ( + <circle + cx="17.0625" + cy="5" + r="4" + fill="#FF3EA2" + stroke="#1A1919" + strokeWidth="2" + /> + )} </svg> </div> {show && <NotificationOpenComponent />} diff --git a/apps/frontend/src/components/onboarding/connect.channels.tsx b/apps/frontend/src/components/onboarding/connect.channels.tsx index 0c377f40..fa749fac 100644 --- a/apps/frontend/src/components/onboarding/connect.channels.tsx +++ b/apps/frontend/src/components/onboarding/connect.channels.tsx @@ -20,6 +20,7 @@ import { web3List } from '@gitroom/frontend/components/launches/web3/web3.list'; import { timer } from '@gitroom/helpers/utils/timer'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; export const ConnectChannels: FC = () => { const fetch = useFetch(); const { isGeneral } = useVariables(); @@ -33,17 +34,6 @@ export const ConnectChannels: FC = () => { }, []); const [reload, setReload] = useState(false); - // const getSocialLink = useCallback( - // (identifier: string) => async () => { - // const { url } = await ( - // await fetch('/integrations/social/' + identifier) - // ).json(); - // - // window.open(url, 'Social Connect', 'width=700,height=700'); - // }, - // [] - // ); - const openWeb3 = async (id: string) => { const { component: Web3Providers } = web3List.find( (item) => item.identifier === id @@ -142,21 +132,6 @@ export const ConnectChannels: FC = () => { window.open(url, 'Social Connect', 'width=700,height=700'); }; - // if (isExternal) { - // modal.closeAll(); - // - // modal.openModal({ - // title: '', - // withCloseButton: false, - // classNames: { - // modal: 'bg-transparent text-textColor', - // }, - // children: <UrlModal gotoUrl={gotoIntegration} />, - // }); - // - // return; - // } - if (isWeb3) { openWeb3(identifier); return; @@ -183,9 +158,11 @@ export const ConnectChannels: FC = () => { setPopups(list.map((p: any) => p.id)); return list; }, []); + const { data: integrations, mutate } = useSWR('/integrations/list', load, { fallbackData: [], }); + const user = useUser(); const totalNonDisabledChannels = useMemo(() => { return ( @@ -193,6 +170,7 @@ export const ConnectChannels: FC = () => { ?.length || 0 ); }, [integrations]); + const sortedIntegrations = useMemo(() => { return orderBy( integrations, @@ -242,9 +220,13 @@ export const ConnectChannels: FC = () => { return ( <> {!!showCustom && ( - <div className="absolute w-full h-full top-0 start-0 bg-black/40 z-[400]"> + <div className="absolute w-full h-full top-0 start-0 z-[400]"> <div className="absolute w-full h-full bg-primary/80 start-0 top-0 z-[200] p-[50px] flex justify-center"> - <div className="w-[400px]">{showCustom}</div> + <div className="w-[400px]"> + <ModalWrapperComponent title="" customClose={() => setShowCustom(undefined)}> + {showCustom} + </ModalWrapperComponent> + </div> </div> </div> )} @@ -260,7 +242,7 @@ export const ConnectChannels: FC = () => { </div> </div> )} - <div className="flex flex-col"> + <div className="flex flex-col gap-[24px]"> <div className="flex gap-[4px] flex-col"> <div className="text-[20px]"> {t('connect_channels', 'Connect Channels')} @@ -268,13 +250,92 @@ export const ConnectChannels: FC = () => { <div className="text-[14px] text-customColor18"> {t( 'connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later', - 'Connect your social media and publishing websites channels to\n schedule posts later' + 'Connect your social media and publishing websites channels to\n schedule posts later' )} </div> </div> - <div className="flex border border-customColor47 rounded-[4px] mt-[16px]"> - <div className="flex-1 flex flex-col p-[16px] gap-[10px]"> - <div className="text-[18px]">{t('social', 'Social')}</div> + {sortedIntegrations.length !== 0 && ( + <div className="border border-customColor47 rounded-[4px] p-[16px]"> + <div className="gap-[16px] flex flex-col"> + {sortedIntegrations.length === 0 && ( + <div className="text-[12px]"> + {t('no_channels', 'No channels')} + </div> + )} + {sortedIntegrations.map((integration) => ( + <div + key={integration.id} + className="flex gap-[8px] items-center" + > + <div + className={clsx( + 'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth', + integration.disabled && 'opacity-50' + )} + > + {integration.inBetweenSteps && ( + <div + className="absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer" + onClick={continueIntegration(integration)} + > + <div className="bg-red-500 w-[15px] h-[15px] rounded-full -start-[5px] -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center"> + ! + </div> + <div className="bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]" /> + </div> + )} + <Image + src={integration.picture} + className="rounded-full" + alt={integration.identifier} + width={32} + height={32} + /> + <Image + src={`/icons/platforms/${integration.identifier}.png`} + className="rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth" + alt={integration.identifier} + width={20} + height={20} + /> + </div> + <div + {...(integration.disabled && + totalNonDisabledChannels === user?.totalChannels + ? { + 'data-tooltip-id': 'tooltip', + 'data-tooltip-content': + 'This channel is disabled, please upgrade your plan to enable it.', + } + : {})} + className={clsx( + 'flex-1', + integration.disabled && 'opacity-50' + )} + > + {integration.name} + </div> + <Menu + canChangeProfilePicture={integration.changeProfile} + canChangeNickName={integration.changeNickName} + mutate={mutate} + onChange={update} + id={integration.id} + refreshChannel={refreshChannel} + canEnable={ + user?.totalChannels! > totalNonDisabledChannels && + integration.disabled + } + canDisable={!integration.disabled} + /> + </div> + ))} + </div> + </div> + )} + + <div className="flex mb-[16px]"> + <div className="flex-1 flex flex-col gap-[10px]"> <div className="grid grid-cols-3 gap-[16px]"> {data?.social.map((social: any) => ( <div @@ -296,113 +357,13 @@ export const ConnectChannels: FC = () => { height={32} /> </div> - <div className="text-inputText text-[10px] tracking-[1.2px] uppercase"> + <div className="text-textColor text-[10px] tracking-[1.2px] uppercase"> {social.name} </div> </div> ))} </div> </div> - {!isGeneral && ( - <div className="flex-1 flex flex-col p-[16px] gap-[10px]"> - <div className="text-[18px]"> - {t('publishing_platforms', 'Publishing Platforms')} - </div> - <div className="grid grid-cols-3 gap-[16px]"> - {data?.article.map((article: any) => ( - <div - onClick={() => setIdentifier(article)} - key={article.identifier} - className="h-[96px] bg-input flex flex-col justify-center items-center gap-[10px] cursor-pointer" - > - <div> - <img - src={`/icons/platforms/${article.identifier}.png`} - className="rounded-full w-[32px] h-[32px]" - /> - </div> - <div className="text-inputText text-[10px] tracking-[1.2px] uppercase"> - {article.name} - </div> - </div> - ))} - </div> - </div> - )} - </div> - <div className="my-[24px] border border-customColor47 rounded-[4px] p-[16px]"> - <div className="gap-[16px] flex flex-col"> - {sortedIntegrations.length === 0 && ( - <div className="text-[12px]"> - {t('no_channels', 'No channels')} - </div> - )} - {sortedIntegrations.map((integration) => ( - <div key={integration.id} className="flex gap-[8px] items-center"> - <div - className={clsx( - 'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth', - integration.disabled && 'opacity-50' - )} - > - {integration.inBetweenSteps && ( - <div - className="absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer" - onClick={continueIntegration(integration)} - > - <div className="bg-red-500 w-[15px] h-[15px] rounded-full -start-[5px] -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center"> - ! - </div> - <div className="bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]" /> - </div> - )} - <Image - src={integration.picture} - className="rounded-full" - alt={integration.identifier} - width={32} - height={32} - /> - <Image - src={`/icons/platforms/${integration.identifier}.png`} - className="rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth" - alt={integration.identifier} - width={20} - height={20} - /> - </div> - <div - {...(integration.disabled && - totalNonDisabledChannels === user?.totalChannels - ? { - 'data-tooltip-id': 'tooltip', - 'data-tooltip-content': - 'This channel is disabled, please upgrade your plan to enable it.', - } - : {})} - className={clsx( - 'flex-1', - integration.disabled && 'opacity-50' - )} - > - {integration.name} - </div> - <Menu - canChangeProfilePicture={integration.changeProfile} - canChangeNickName={integration.changeNickName} - mutate={mutate} - onChange={update} - id={integration.id} - refreshChannel={refreshChannel} - canEnable={ - user?.totalChannels! > totalNonDisabledChannels && - integration.disabled - } - canDisable={!integration.disabled} - /> - </div> - ))} - </div> </div> </div> </> diff --git a/apps/frontend/src/components/onboarding/onboarding.tsx b/apps/frontend/src/components/onboarding/onboarding.tsx index f060b55d..7e5f776b 100644 --- a/apps/frontend/src/components/onboarding/onboarding.tsx +++ b/apps/frontend/src/components/onboarding/onboarding.tsx @@ -3,279 +3,28 @@ import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useModals } from '@mantine/modals'; -import clsx from 'clsx'; -import { GithubOnboarding } from '@gitroom/frontend/components/onboarding/github.onboarding'; -import { SettingsPopup } from '@gitroom/frontend/components/layout/settings.component'; import { Button } from '@gitroom/react/form/button'; import { ConnectChannels } from '@gitroom/frontend/components/onboarding/connect.channels'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const Step: FC<{ - step: number; - title: string; - currentStep: number; - lastStep: number; -}> = (props) => { - const t = useT(); +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; - const { step, title, currentStep, lastStep } = props; - return ( - <div className="flex flex-col"> - <div className="mb-[8px]"> - <div className="w-[24px] h-[24px]"> - {step === currentStep && currentStep !== lastStep && ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - > - <path - d="M9.02439 3.47341C8.99956 3.378 8.99379 3.27862 9.00741 3.18097C9.02102 3.08333 9.05376 2.98932 9.10374 2.90433C9.15372 2.81935 9.21996 2.74505 9.29869 2.6857C9.37741 2.62634 9.46706 2.58309 9.56252 2.55841C11.1614 2.14606 12.8387 2.14606 14.4375 2.55841C14.6146 2.60375 14.769 2.71221 14.8718 2.8634C14.9746 3.01459 15.0185 3.19811 14.9955 3.37945C14.9725 3.5608 14.884 3.72749 14.7467 3.84821C14.6095 3.96892 14.4328 4.03533 14.25 4.03497C14.1867 4.03464 14.1238 4.02646 14.0625 4.0106C12.7097 3.66167 11.2904 3.66167 9.93752 4.0106C9.7452 4.06021 9.54107 4.03151 9.36988 3.93081C9.1987 3.8301 9.07445 3.66561 9.02439 3.47341ZM5.04283 5.16935C3.88656 6.34678 3.0479 7.79831 2.60533 9.3881C2.55224 9.5798 2.57749 9.78474 2.6755 9.95783C2.77352 10.1309 2.93628 10.258 3.12799 10.3111C3.31969 10.3642 3.52463 10.3389 3.69772 10.2409C3.87081 10.1429 3.99787 9.98011 4.05095 9.78841C4.42508 8.4427 5.13475 7.214 6.11345 6.21747C6.24147 6.07348 6.30915 5.88574 6.30248 5.69318C6.2958 5.50062 6.21528 5.31802 6.0776 5.18323C5.93992 5.04845 5.75565 4.97182 5.56299 4.96923C5.37034 4.96665 5.18408 5.03831 5.04283 5.16935ZM4.05095 14.2078C4.02461 14.1129 3.97982 14.0241 3.91916 13.9464C3.85849 13.8688 3.78313 13.8039 3.69738 13.7554C3.61163 13.707 3.51718 13.6758 3.4194 13.6638C3.32162 13.6519 3.22244 13.6593 3.12752 13.6856C3.0326 13.7119 2.94379 13.7567 2.86618 13.8174C2.78857 13.8781 2.72366 13.9534 2.67517 14.0392C2.62668 14.1249 2.59556 14.2194 2.58357 14.3172C2.57159 14.4149 2.57898 14.5141 2.60533 14.609C3.04816 16.1987 3.88679 17.6502 5.04283 18.8278C5.18232 18.9696 5.37244 19.0503 5.57138 19.0519C5.77031 19.0536 5.96176 18.9762 6.10361 18.8367C6.24546 18.6972 6.32609 18.5071 6.32776 18.3081C6.32943 18.1092 6.252 17.9178 6.11252 17.7759C5.13502 16.7797 4.42578 15.5522 4.05095 14.2078ZM14.0625 19.9893C12.7097 20.3386 11.2903 20.3386 9.93752 19.9893C9.84161 19.963 9.74142 19.956 9.64279 19.9688C9.54415 19.9815 9.44903 20.0137 9.36297 20.0636C9.27691 20.1134 9.20163 20.1799 9.1415 20.2591C9.08137 20.3384 9.0376 20.4288 9.01273 20.5251C8.98786 20.6214 8.9824 20.7216 8.99665 20.8201C9.01091 20.9185 9.04459 21.0131 9.09576 21.0984C9.14692 21.1837 9.21454 21.2579 9.29467 21.3169C9.3748 21.3758 9.46585 21.4181 9.56252 21.4415C11.1614 21.8539 12.8387 21.8539 14.4375 21.4415C14.6274 21.3894 14.7892 21.2646 14.8879 21.0942C14.9866 20.9238 15.0143 20.7215 14.9651 20.5308C14.9159 20.3401 14.7936 20.1765 14.6247 20.0752C14.4559 19.9739 14.2539 19.943 14.0625 19.9893ZM20.8735 13.6875C20.7785 13.6611 20.6792 13.6538 20.5814 13.6658C20.4836 13.6778 20.3891 13.709 20.3033 13.7576C20.2175 13.8062 20.1422 13.8712 20.0816 13.9489C20.021 14.0267 19.9762 14.1156 19.95 14.2106C19.5761 15.5561 18.8664 16.7846 17.8875 17.7806C17.8185 17.8509 17.764 17.9341 17.7272 18.0255C17.6903 18.1168 17.6718 18.2145 17.6727 18.313C17.6737 18.4115 17.694 18.5089 17.7325 18.5995C17.771 18.6902 17.8271 18.7724 17.8974 18.8414C17.9677 18.9104 18.0509 18.9649 18.1422 19.0017C18.2336 19.0386 18.3313 19.0571 18.4298 19.0562C18.5283 19.0552 18.6257 19.0349 18.7163 18.9964C18.807 18.9579 18.8891 18.9018 18.9581 18.8315C20.1146 17.6542 20.9533 16.2027 21.3956 14.6128C21.4223 14.5177 21.4299 14.4184 21.4181 14.3204C21.4062 14.2224 21.3752 14.1277 21.3267 14.0417C21.2781 13.9558 21.2131 13.8802 21.1354 13.8194C21.0576 13.7586 20.9686 13.7138 20.8735 13.6875ZM19.9491 9.7931C19.9754 9.88802 20.0202 9.97682 20.0809 10.0544C20.1415 10.132 20.2169 10.197 20.3027 10.2454C20.3884 10.2939 20.4829 10.3251 20.5806 10.337C20.6784 10.349 20.7776 10.3416 20.8725 10.3153C20.9674 10.2889 21.0562 10.2442 21.1339 10.1835C21.2115 10.1228 21.2764 10.0475 21.3249 9.96172C21.3734 9.87597 21.4045 9.78151 21.4165 9.68373C21.4284 9.58595 21.4211 9.48677 21.3947 9.39185C20.9523 7.80198 20.1136 6.3504 18.9572 5.1731C18.8881 5.10286 18.8059 5.04692 18.7152 5.00846C18.6245 4.97 18.5272 4.94978 18.4287 4.94895C18.3302 4.94812 18.2325 4.96671 18.1411 5.00364C18.0498 5.04057 17.9667 5.09512 17.8964 5.16419C17.8262 5.23326 17.7702 5.31548 17.7318 5.40617C17.6933 5.49686 17.6731 5.59424 17.6723 5.69274C17.6714 5.79124 17.69 5.88894 17.727 5.98026C17.7639 6.07158 17.8185 6.15474 17.8875 6.22497C18.8653 7.22055 19.5746 8.44788 19.9491 9.79216V9.7931Z" - fill="#8155DD" - /> - </svg> - )} - {(currentStep > step || currentStep == lastStep) && ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - > - <path - d="M12 2.25C10.0716 2.25 8.18657 2.82183 6.58319 3.89317C4.97982 4.96451 3.73013 6.48726 2.99218 8.26884C2.25422 10.0504 2.06114 12.0108 2.43735 13.9021C2.81355 15.7934 3.74215 17.5307 5.10571 18.8943C6.46928 20.2579 8.20656 21.1865 10.0979 21.5627C11.9892 21.9389 13.9496 21.7458 15.7312 21.0078C17.5127 20.2699 19.0355 19.0202 20.1068 17.4168C21.1782 15.8134 21.75 13.9284 21.75 12C21.7473 9.41498 20.7192 6.93661 18.8913 5.10872C17.0634 3.28084 14.585 2.25273 12 2.25ZM16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.2891 9.14969 15.3718 9.09442 15.4628 9.0567C15.5539 9.01899 15.6515 8.99958 15.75 8.99958C15.8486 8.99958 15.9461 9.01899 16.0372 9.0567C16.1282 9.09442 16.2109 9.14969 16.2806 9.21937C16.3503 9.28906 16.4056 9.37178 16.4433 9.46283C16.481 9.55387 16.5004 9.65145 16.5004 9.75C16.5004 9.84855 16.481 9.94613 16.4433 10.0372C16.4056 10.1282 16.3503 10.2109 16.2806 10.2806Z" - fill="#8155DD" - /> - </svg> - )} - {step > currentStep && currentStep !== lastStep && ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - > - <path - d="M12 2.25C10.0716 2.25 8.18657 2.82183 6.58319 3.89317C4.97982 4.96451 3.73013 6.48726 2.99218 8.26884C2.25422 10.0504 2.06114 12.0108 2.43735 13.9021C2.81355 15.7934 3.74215 17.5307 5.10571 18.8943C6.46928 20.2579 8.20656 21.1865 10.0979 21.5627C11.9892 21.9389 13.9496 21.7458 15.7312 21.0078C17.5127 20.2699 19.0355 19.0202 20.1068 17.4168C21.1782 15.8134 21.75 13.9284 21.75 12C21.7473 9.41498 20.7192 6.93661 18.8913 5.10872C17.0634 3.28084 14.585 2.25273 12 2.25ZM12 20.25C10.3683 20.25 8.77326 19.7661 7.41655 18.8596C6.05984 17.9531 5.00242 16.6646 4.378 15.1571C3.75358 13.6496 3.5902 11.9908 3.90853 10.3905C4.22685 8.79016 5.01259 7.32015 6.16637 6.16637C7.32016 5.01259 8.79017 4.22685 10.3905 3.90852C11.9909 3.59019 13.6497 3.75357 15.1571 4.37799C16.6646 5.00242 17.9531 6.05984 18.8596 7.41655C19.7661 8.77325 20.25 10.3683 20.25 12C20.2475 14.1873 19.3775 16.2843 17.8309 17.8309C16.2843 19.3775 14.1873 20.2475 12 20.25Z" - fill="#172034" - /> - </svg> - )} - </div> - </div> - <div className="mb-[4px] text-[10px] text-customColor18 tracking-[1.2px]"> - {t('step', 'STEP')} - {step} - </div> - <div - className={clsx('text-[12px]', step > currentStep && 'text-inputText')} - > - {title} - </div> - </div> - ); -}; -export const StepSpace: FC = () => { - return ( - <div className="flex-1 justify-center items-center flex px-[20px]"> - <div className="h-[1px] w-full bg-white"></div> - </div> - ); -}; -const SkipOnboarding: FC = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - const t = useT(); - - const onSkip = useCallback(() => { - const keys = Array.from(searchParams.keys()); - const buildNewQuery = keys - .reduce((all, current) => { - if (current === 'onboarding') { - return all; - } - const value = searchParams.get(current); - all.push(`${current}=${value}`); - return all; - }, [] as string[]) - .join('&'); - router.push(`?${buildNewQuery}`); - }, [searchParams]); - return ( - <Button - secondary={true} - className="border-[2px] border-customColor21" - onClick={onSkip} - > - {t('skip_onboarding', 'Skip onboarding')} - </Button> - ); -}; const Welcome: FC = () => { - const [seller, setSeller] = useState(false); const { isGeneral } = useVariables(); - const [lastStep, setLastStep] = useState(isGeneral ? 3 : 4); const [step, setStep] = useState(1); - const ref = useRef(); const router = useRouter(); - const t = useT(); - const nextStep = useCallback( - (stepIt?: number) => () => { - setStep(stepIt ? stepIt : step + 1); - }, - [step] - ); - const firstNext = useCallback(() => { - // @ts-ignore - ref?.current?.click(); - nextStep(2)(); - }, [nextStep]); - const goToAnalytics = useCallback(() => { - router.push('/analytics'); - }, []); const goToLaunches = useCallback(() => { router.push('/launches'); }, []); - const buyPosts = useCallback(() => { - router.push('/marketplace/buyer'); - }, []); - const sellPosts = useCallback(() => { - nextStep()(); - setLastStep(isGeneral ? 4 : 5); - setSeller(true); - }, [step]); - const connectBankAccount = useCallback(() => { - router.push('/marketplace/seller'); - }, []); + return ( - <div className="bg-sixth p-[32px] w-full max-w-[920px] mx-auto flex flex-col gap-[24px] rounded-[4px] border border-customColor6 relative"> - <h1 className="text-[24px]">{t('onboarding', 'Onboarding')}</h1> - <div className="flex"> - <Step - title="Connect Channels" - step={1} - currentStep={step} - lastStep={lastStep} - /> - <StepSpace /> - {!isGeneral && ( - <> - <Step - title="Connect Github" - step={2} - currentStep={step} - lastStep={2} - /> - <StepSpace /> - </> - )} - <Step - title="Finish" - step={3 - (isGeneral ? 1 : 0)} - currentStep={step} - lastStep={2} - /> - {/*<StepSpace />*/} - {/*<Step title="Finish" step={4 - (isGeneral ? 1 : 0)} currentStep={step} lastStep={lastStep} />*/} - {seller && ( - <> - <StepSpace /> - <Step - title="Sell Posts" - step={5 - (isGeneral ? 1 : 0)} - currentStep={step} - lastStep={lastStep} - /> - </> - )} - </div> - {step === 1 && !isGeneral && ( - <div> - <GithubOnboarding /> - <div className="flex justify-end gap-[8px]"> - {/*<SkipOnboarding />*/} - <Button onClick={nextStep()}>{t('next', 'Next')}</Button> - </div> - </div> - )} + <div className="relative"> {step === 2 - (isGeneral ? 1 : 0) && ( <div> <ConnectChannels /> <div className="flex justify-end gap-[8px]"> - {/*<SkipOnboarding />*/} - <Button onClick={nextStep()}>{t('next', 'Next')}</Button> - </div> - </div> - )} - {step === 3 - (isGeneral ? 1 : 0) && ( - <div className="items-center justify-center flex flex-col gap-[24px]"> - <div className="items-center justify-center flex flex-col"> - <img src="/success.svg" alt="success" /> - </div> - <div className="text-[18px] text-center"> - {t( - 'you_are_done_from_here_you_can', - 'You are done, from here you can:' - )} - </div> - <div className="flex flex-col gap-[8px]"> - <div - className={clsx( - isGeneral ? 'grid' : 'grid grid-cols-2 gap-[8px]' - )} - > - {!isGeneral && ( - <Button onClick={goToAnalytics}> - {t('view_analytics', 'View Analytics')} - </Button> - )} - <Button onClick={goToLaunches}> - {t('schedule_a_new_post', 'Schedule a new post')} - </Button> - </div> - - {/*<div className="grid grid-cols-2 gap-[8px]">*/} - {/* /!*<Button onClick={buyPosts}>Buy posts from Influencers</Button>*!/*/} - {/* /!*<Button onClick={sellPosts}>Sell your services</Button>*!/*/} - {/*</div>*/} - </div> - </div> - )} - {step === 4 - (isGeneral ? 1 : 0) && ( - <div> - <div className="text-[24px] mb-[24px]"> - {t( - 'to_sell_posts_you_would_have_to', - 'To sell posts you would have to:' - )} - </div> - <ul> - <li> - {t( - '1_connect_at_least_one_channel', - '1. Connect at least one channel' - )} - </li> - <li> - {t('2_connect_you_bank_account', '2. Connect you bank account')} - </li> - </ul> - - <div className="grid grid-cols-2 gap-[8px] mt-[24px]"> - <Button onClick={() => setStep(isGeneral ? 2 : 3)}> - {t('go_back_to_connect_channels', 'Go back to connect channels')} - </Button> - <Button onClick={connectBankAccount}> - {t( - 'move_to_the_seller_page_to_connect_you_bank', - 'Move to the seller page to connect you bank' - )} - </Button> + <Button onClick={goToLaunches}>Close</Button> </div> </div> )} @@ -286,6 +35,8 @@ export const Onboarding: FC = () => { const query = useSearchParams(); const modal = useModals(); const modalOpen = useRef(false); + const t = useT(); + useEffect(() => { const onboarding = query.get('onboarding'); if (!onboarding) { @@ -298,11 +49,12 @@ export const Onboarding: FC = () => { title: '', withCloseButton: false, closeOnEscape: false, - classNames: { - modal: 'bg-transparent text-textColor', - }, - size: '100%', - children: <Welcome />, + size: '900px', + children: ( + <ModalWrapperComponent title={t('onboarding', 'Onboarding')}> + <Welcome /> + </ModalWrapperComponent> + ), }); }, [query]); return null; diff --git a/apps/frontend/src/components/platform-analytics/platform.analytics.tsx b/apps/frontend/src/components/platform-analytics/platform.analytics.tsx index 0b68723b..54c57fe3 100644 --- a/apps/frontend/src/components/platform-analytics/platform.analytics.tsx +++ b/apps/frontend/src/components/platform-analytics/platform.analytics.tsx @@ -14,6 +14,8 @@ import { useRouter } from 'next/navigation'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { useVariables } from '@gitroom/react/helpers/variable.context'; +import useCookie from 'react-use-cookie'; +import { SVGLine } from '@gitroom/frontend/components/launches/launches.component'; const allowedIntegrations = [ 'facebook', 'instagram', @@ -34,6 +36,7 @@ export const PlatformAnalytics = () => { const [current, setCurrent] = useState(0); const [key, setKey] = useState(7); const [refresh, setRefresh] = useState(false); + const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0'); const toaster = useToaster(); const load = useCallback(async () => { const int = ( @@ -150,11 +153,38 @@ export const PlatformAnalytics = () => { ); } return ( - <div className="flex gap-[30px] flex-1"> - <div className="p-[16px] bg-customColor48 overflow-hidden flex w-[220px]"> - <div className="flex gap-[16px] flex-col overflow-hidden"> - <div className="text-[20px] mb-[8px]"> - {t('channels', 'Channels')} + <> + <div + className={clsx( + 'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all', + collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]' + )} + > + <div className="flex gap-[12px] flex-col"> + <div className="flex items-center"> + <h2 className="group-[.sidebar]:hidden flex-1 text-[20px] font-[500]"> + {t('channels')} + </h2> + <div + onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')} + className="group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none" + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="7" + height="13" + viewBox="0 0 7 13" + fill="none" + > + <path + d="M6 11.5L1 6.5L6 1.5" + stroke="currentColor" + strokeWidth="1.5" + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + </div> </div> {sortedIntegrations.map((integration, index) => ( <div @@ -174,14 +204,14 @@ export const PlatformAnalytics = () => { setCurrent(index); }} className={clsx( - 'flex gap-[8px] items-center', + 'flex gap-[12px] items-center group/profile justify-center hover:bg-boxHover rounded-e-[8px]', currentIntegration.id !== integration.id && 'opacity-20 hover:opacity-100 cursor-pointer' )} > <div className={clsx( - 'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth', + 'relative rounded-full flex justify-center items-center gap-[6px]', integration.disabled && 'opacity-50' )} > @@ -193,25 +223,28 @@ export const PlatformAnalytics = () => { <div className="bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]" /> </div> )} + <div className="h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity"> + <SVGLine /> + </div> <ImageWithFallback fallbackSrc={`/icons/platforms/${integration.identifier}.png`} src={integration.picture} - className="rounded-full" + className="rounded-[8px]" alt={integration.identifier} - width={32} - height={32} + width={36} + height={36} /> <Image src={`/icons/platforms/${integration.identifier}.png`} - className="rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth" + className="rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth" alt={integration.identifier} - width={20} - height={20} + width={18.41} + height={18.41} /> </div> <div className={clsx( - 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden', + 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden', integration.disabled && 'opacity-50' )} > @@ -221,31 +254,32 @@ export const PlatformAnalytics = () => { ))} </div> </div> - {!!options.length && ( - <div className="flex-1 flex flex-col gap-[14px]"> - <div className="max-w-[200px]"> - <Select - className="bg-customColor49 !border-0" - label="" - name="date" - disableForm={true} - hideErrors={true} - onChange={(e) => setKey(+e.target.value)} - > - {options.map((option) => ( - <option key={option.key} value={option.key}> - {option.value} - </option> - ))} - </Select> + <div className="bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]"> + {!!options.length && ( + <div className="flex-1 flex flex-col gap-[14px]"> + <div className="max-w-[200px]"> + <Select + label="" + name="date" + disableForm={true} + hideErrors={true} + onChange={(e) => setKey(+e.target.value)} + > + {options.map((option) => ( + <option key={option.key} value={option.key}> + {option.value} + </option> + ))} + </Select> + </div> + <div className="flex-1"> + {!!keys && !!currentIntegration && !refresh && ( + <RenderAnalytics integration={currentIntegration} date={keys} /> + )} + </div> </div> - <div className="flex-1"> - {!!keys && !!currentIntegration && !refresh && ( - <RenderAnalytics integration={currentIntegration} date={keys} /> - )} - </div> - </div> - )} - </div> + )} + </div> + </> ); }; diff --git a/apps/frontend/src/components/platform-analytics/render.analytics.tsx b/apps/frontend/src/components/platform-analytics/render.analytics.tsx index 91b6444b..55358f27 100644 --- a/apps/frontend/src/components/platform-analytics/render.analytics.tsx +++ b/apps/frontend/src/components/platform-analytics/render.analytics.tsx @@ -87,7 +87,7 @@ export const RenderAnalytics: FC<{ )} {data?.map((p: any, index: number) => ( <div key={`pl-${index}`} className="flex"> - <div className="flex-1 bg-secondary py-[10px] px-[16px] gap-[10px] flex flex-col"> + <div className="flex-1 bg-newTableHeader rounded-[8px] py-[10px] px-[16px] gap-[10px] flex flex-col"> <div className="flex items-center gap-[14px]"> <div className="text-[20px]">{p.label}</div> </div> diff --git a/apps/frontend/src/components/plugs/plug.tsx b/apps/frontend/src/components/plugs/plug.tsx index e1bff188..eb5f8e06 100644 --- a/apps/frontend/src/components/plugs/plug.tsx +++ b/apps/frontend/src/components/plugs/plug.tsx @@ -25,6 +25,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Slider } from '@gitroom/react/form/slider'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; export function convertBackRegex(s: string) { const matches = s.match(/\/(.*)\/([a-z]*)/); const pattern = matches?.[1] || ''; @@ -133,57 +134,27 @@ export const PlugPop: FC<{ return ( <FormProvider {...form}> <form onSubmit={form.handleSubmit(submit)}> - <div className="fixed start-0 top-0 bg-primary/80 z-[300] w-full min-h-full p-4 md:p-[60px] animate-fade"> - <div className="max-w-[1000px] w-full h-full bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative mx-auto"> - <div className="flex flex-col"> - <div className="flex-1"> - <TopTitle title={`Auto Plug: ${plug.title}`} /> + <div className="relative mx-auto"> + <div className="my-[20px]">{plug.description}</div> + <div> + {plug.fields.map((field) => ( + <div key={field.name}> + {field.type === 'richtext' ? ( + <TextArea name={field.name} placeHolder={field.placeholder} /> + ) : ( + <Input + name={field.name} + label={field.description} + className="w-full mt-[8px] p-[8px] border border-tableBorder rounded-md text-black" + placeholder={field.placeholder} + type={field.type} + /> + )} </div> - <button - onClick={closeAll} - className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa" - type="button" - > - <svg - viewBox="0 0 15 15" - fill="none" - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - > - <path - d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z" - fill="currentColor" - fillRule="evenodd" - clipRule="evenodd" - ></path> - </svg> - </button> - </div> - <div className="my-[20px]">{plug.description}</div> - <div> - {plug.fields.map((field) => ( - <div key={field.name}> - {field.type === 'richtext' ? ( - <TextArea - name={field.name} - placeHolder={field.placeholder} - /> - ) : ( - <Input - name={field.name} - label={field.description} - className="w-full mt-[8px] p-[8px] border border-tableBorder rounded-md text-black" - placeholder={field.placeholder} - type={field.type} - /> - )} - </div> - ))} - </div> - <div className="mt-[20px]"> - <Button type="submit">{t('activate', 'Activate')}</Button> - </div> + ))} + </div> + <div className="mt-[20px]"> + <Button type="submit">{t('activate', 'Activate')}</Button> </div> </div> </form> @@ -227,7 +198,7 @@ export const PlugItem: FC<{ <div onClick={() => addPlug(data)} key={plug.title} - className="w-full h-[300px] bg-customColor48 hover:bg-customColor2 hover:border-customColor48 hover:border" + className="w-full h-[300px] rounded-[8px] bg-newTableHeader hover:bg-newTableBorder" > <div key={plug.title} className="p-[16px] h-full flex flex-col flex-1"> <div className="flex"> @@ -267,24 +238,23 @@ export const Plug = () => { plugFunction: string; }) => { modals.openModal({ - classNames: { - modal: 'bg-transparent text-textColor', - }, withCloseButton: false, onClose() { mutate(); }, - size: '100%', + size: '500px', children: ( - <PlugPop - plug={p} - data={data} - settings={{ - identifier: plug.identifier, - providerId: plug.providerId, - name: plug.name, - }} - /> + <ModalWrapperComponent title={`Auto Plug: ${p.title}`}> + <PlugPop + plug={p} + data={data} + settings={{ + identifier: plug.identifier, + providerId: plug.providerId, + name: plug.name, + }} + /> + </ModalWrapperComponent> ), }); }, diff --git a/apps/frontend/src/components/plugs/plugs.tsx b/apps/frontend/src/components/plugs/plugs.tsx index fc7c0855..d06e9a5b 100644 --- a/apps/frontend/src/components/plugs/plugs.tsx +++ b/apps/frontend/src/components/plugs/plugs.tsx @@ -14,6 +14,8 @@ import { useToaster } from '@gitroom/react/toaster/toaster'; import { PlugsContext } from '@gitroom/frontend/components/plugs/plugs.context'; import { Plug } from '@gitroom/frontend/components/plugs/plug'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; +import useCookie from 'react-use-cookie'; +import { SVGLine } from '@gitroom/frontend/components/launches/launches.component'; export const Plugs = () => { const fetch = useFetch(); const router = useRouter(); @@ -37,6 +39,8 @@ export const Plugs = () => { fallbackData: [], }); + const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0'); + const t = useT(); const sortedIntegrations = useMemo(() => { @@ -96,11 +100,38 @@ export const Plugs = () => { ); } return ( - <div className="flex gap-[30px] flex-1"> - <div className="p-[16px] bg-customColor48 overflow-hidden flex w-[220px]"> - <div className="flex gap-[16px] flex-col overflow-hidden"> - <div className="text-[20px] mb-[8px]"> - {t('channels', 'Channels')} + <> + <div + className={clsx( + 'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all', + collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]' + )} + > + <div className="flex gap-[12px] flex-col"> + <div className="flex items-center"> + <h2 className="group-[.sidebar]:hidden flex-1 text-[20px] font-[500]"> + {t('channels')} + </h2> + <div + onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')} + className="group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none" + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="7" + height="13" + viewBox="0 0 7 13" + fill="none" + > + <path + d="M6 11.5L1 6.5L6 1.5" + stroke="currentColor" + strokeWidth="1.5" + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + </div> </div> {sortedIntegrations.map((integration, index) => ( <div @@ -120,14 +151,14 @@ export const Plugs = () => { setCurrent(index); }} className={clsx( - 'flex gap-[8px] items-center', + 'flex gap-[8px] items-center justify-center group/profile hover:bg-boxHover rounded-e-[8px]', currentIntegration.id !== integration.id && 'opacity-20 hover:opacity-100 cursor-pointer' )} > <div className={clsx( - 'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth', + 'relative rounded-full flex justify-center items-center gap-[8px]', integration.disabled && 'opacity-50' )} > @@ -139,25 +170,28 @@ export const Plugs = () => { <div className="bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]" /> </div> )} + <div className="h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity"> + <SVGLine /> + </div> <ImageWithFallback fallbackSrc={`/icons/platforms/${integration.identifier}.png`} src={integration.picture} - className="rounded-full" + className="rounded-[8px]" alt={integration.identifier} - width={32} - height={32} + width={36} + height={36} /> <Image src={`/icons/platforms/${integration.identifier}.png`} - className="rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth" + className="rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth" alt={integration.identifier} - width={20} - height={20} + width={18.41} + height={18.41} /> </div> <div className={clsx( - 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden', + 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden', integration.disabled && 'opacity-50' )} > @@ -167,11 +201,11 @@ export const Plugs = () => { ))} </div> </div> - <div className="flex-1 flex flex-col gap-[14px]"> + <div className="bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]"> <PlugsContext.Provider value={currentIntegrationPlug}> <Plug /> </PlugsContext.Provider> </div> - </div> + </> ); }; diff --git a/apps/frontend/src/components/settings/signatures.component.tsx b/apps/frontend/src/components/settings/signatures.component.tsx index 081163dd..403fd32e 100644 --- a/apps/frontend/src/components/settings/signatures.component.tsx +++ b/apps/frontend/src/components/settings/signatures.component.tsx @@ -187,7 +187,7 @@ const AddOrRemoveSignature: FC<{ <CopilotTextarea disableBranding={true} className={clsx( - '!min-h-40 !max-h-80 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none' + '!min-h-40 !max-h-80 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-bigStrip outline-none' )} value={text} onChange={(e) => { diff --git a/apps/frontend/src/components/third-parties/third-party.component.tsx b/apps/frontend/src/components/third-parties/third-party.component.tsx index 1e86db15..53c9430b 100644 --- a/apps/frontend/src/components/third-parties/third-party.component.tsx +++ b/apps/frontend/src/components/third-parties/third-party.component.tsx @@ -10,6 +10,8 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import interClass from '@gitroom/react/helpers/inter.font'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +import useCookie from 'react-use-cookie'; +import { SVGLine } from '@gitroom/frontend/components/launches/launches.component'; export const ThirdPartyMenuComponent: FC<{ reload: () => void; @@ -102,64 +104,95 @@ export const ThirdPartyComponent = () => { }, []); const { data, isLoading, mutate } = useSWR('third-party', integrations); + const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0'); return ( - <div className="flex flex-1 flex-col"> - <div className="flex flex-1 relative"> - <div className="outline-none w-full h-full grid grid-cols[1fr] md:grid-cols-[220px_minmax(0,1fr)] gap-[30px] scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary"> - <div className="bg-third p-[16px] flex flex-col gap-[24px] min-h-[100%]"> - <h2 className="text-[20px]">{t('integrations')}</h2> - <div className="flex flex-col gap-[10px]"> - <div className="flex-1 flex flex-col gap-[14px]"> - <div className={clsx('gap-[16px] flex flex-col relative')}> - {!isLoading && !data?.length ? ( - <div>No Integrations Yet</div> - ) : ( - data?.map((p: any) => ( - <div - key={p.id} - className={clsx('flex gap-[8px] items-center')} - > - <div - className={clsx( - 'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth' - )} - data-tooltip-id="tooltip" - data-tooltip-content={p.title} - > - <ImageWithFallback - fallbackSrc={`/icons/third-party/${p.identifier}.png`} - src={`/icons/third-party/${p.identifier}.png`} - className="rounded-full" - alt={p.title} - width={32} - height={32} - /> - </div> - <div - // @ts-ignore - role="Handle" - className={clsx( - 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden' - )} - data-tooltip-id="tooltip" - data-tooltip-content={p.title} - > - {p.name} - </div> - <ThirdPartyMenuComponent reload={mutate} tParty={p} /> + <> + <div + className={clsx( + 'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all', + collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]' + )} + > + <div className="flex gap-[12px] flex-col"> + <div className="flex items-center"> + <h2 className="group-[.sidebar]:hidden flex-1 text-[20px] font-[500]"> + {t('integrations')} + </h2> + <div + onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')} + className="group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none" + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="7" + height="13" + viewBox="0 0 7 13" + fill="none" + > + <path + d="M6 11.5L1 6.5L6 1.5" + stroke="currentColor" + strokeWidth="1.5" + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + </div> + </div> + <div className="flex flex-col gap-[10px]"> + <div className="flex-1 flex flex-col gap-[14px]"> + <div className={clsx('gap-[16px] flex flex-col relative justify-center group/profile hover:bg-boxHover rounded-e-[8px]')}> + {!isLoading && !data?.length ? ( + <div>No Integrations Yet</div> + ) : ( + data?.map((p: any) => ( + <div + key={p.id} + className={clsx('flex gap-[8px] items-center')} + > + <div className="h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity"> + <SVGLine /> </div> - )) - )} - </div> + <div + className={clsx( + 'relative rounded-full flex justify-center items-center bg-fifth' + )} + data-tooltip-id="tooltip" + data-tooltip-content={p.title} + > + <ImageWithFallback + fallbackSrc={`/icons/third-party/${p.identifier}.png`} + src={`/icons/third-party/${p.identifier}.png`} + className="rounded-full" + alt={p.title} + width={32} + height={32} + /> + </div> + <div + // @ts-ignore + role="Handle" + className={clsx( + 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden' + )} + data-tooltip-id="tooltip" + data-tooltip-content={p.title} + > + {p.name} + </div> + <ThirdPartyMenuComponent reload={mutate} tParty={p} /> + </div> + )) + )} </div> </div> </div> - <div> - <ThirdPartyListComponent reload={mutate} /> - </div> </div> </div> - </div> + <div className="bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]"> + <ThirdPartyListComponent reload={mutate} /> + </div> + </> ); }; diff --git a/apps/frontend/src/components/third-parties/third-party.list.component.tsx b/apps/frontend/src/components/third-parties/third-party.list.component.tsx index df4b920f..de95c9dd 100644 --- a/apps/frontend/src/components/third-parties/third-party.list.component.tsx +++ b/apps/frontend/src/components/third-parties/third-party.list.component.tsx @@ -4,14 +4,13 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import useSWR from 'swr'; import React, { FC, useCallback, useState } from 'react'; import { Button } from '@gitroom/react/form/button'; -import { string } from 'yup'; import { useRouter } from 'next/navigation'; import { useModals } from '@mantine/modals'; import { FieldValues, FormProvider, useForm } from 'react-hook-form'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; -import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { Input } from '@gitroom/react/form/input'; import { useToaster } from '@gitroom/react/toaster/toaster'; +import { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component'; export const ApiModal: FC<{ identifier: string; @@ -39,61 +38,43 @@ export const ApiModal: FC<{ modal.closeAll(); }, []); - const submit = useCallback(async (data: FieldValues) => { - setLoading(true); - const add = await fetch(`/third-party/${identifier}`, { - method: 'POST', - body: JSON.stringify({ - api: data.api, - }), - }); + const submit = useCallback( + async (data: FieldValues) => { + setLoading(true); + const add = await fetch(`/third-party/${identifier}`, { + method: 'POST', + body: JSON.stringify({ + api: data.api, + }), + }); - if (add.ok) { - toaster.show('Integration added successfully', 'success'); - if (closePopup) { - closePopup(); - } else { - modal.closeAll(); + if (add.ok) { + toaster.show('Integration added successfully', 'success'); + if (closePopup) { + closePopup(); + } else { + modal.closeAll(); + } + router.refresh(); + if (update) update(); + return; } - router.refresh(); - if (update) update(); - return; - } - const {message} = await add.json(); + const { message } = await add.json(); - methods.setError('api', { - message, - }); + methods.setError('api', { + message, + }); - setLoading(false); - }, [props]); + setLoading(false); + }, + [props] + ); const t = useT(); return ( - <div className="rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative"> - <TopTitle title={`Add API key for ${title}`} /> - <button - onClick={close} - className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa" - type="button" - > - <svg - viewBox="0 0 15 15" - fill="none" - xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" - > - <path - d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z" - fill="currentColor" - fillRule="evenodd" - clipRule="evenodd" - ></path> - </svg> - </button> + <div className="relative"> <FormProvider {...methods}> <form className="gap-[8px] flex flex-col" @@ -103,7 +84,9 @@ export const ApiModal: FC<{ <Input label="API Key" name="api" /> </div> <div> - <Button loading={loading} type="submit">{t('add_integration', 'Add Integration')}</Button> + <Button loading={loading} type="submit"> + {t('add_integration', 'Add Integration')} + </Button> </div> </form> </FormProvider> @@ -111,7 +94,7 @@ export const ApiModal: FC<{ ); }; -export const ThirdPartyListComponent: FC<{reload: () => void}> = (props) => { +export const ThirdPartyListComponent: FC<{ reload: () => void }> = (props) => { const fetch = useFetch(); const modals = useModals(); const { reload } = props; @@ -122,19 +105,20 @@ export const ThirdPartyListComponent: FC<{reload: () => void}> = (props) => { const { data } = useSWR('third-party-list', integrationsList); - const addApiKey = useCallback((title: string, identifier: string) => () => { - modals.openModal({ - title: '', - withCloseButton: false, - classNames: { - modal: 'bg-transparent text-textColor', - }, - children: ( - <ApiModal identifier={identifier} title={title} update={reload} /> - ), - }); - }, []); - + const addApiKey = useCallback( + (title: string, identifier: string) => () => { + modals.openModal({ + title: '', + withCloseButton: false, + children: ( + <ModalWrapperComponent title={`Add API key for ${title}`}> + <ApiModal identifier={identifier} title={title} update={reload} /> + </ModalWrapperComponent> + ), + }); + }, + [] + ); return ( <div className="grid grid-cols-4 gap-[10px] justify-items-center justify-center"> @@ -142,7 +126,7 @@ export const ThirdPartyListComponent: FC<{reload: () => void}> = (props) => { <div onClick={addApiKey(p.title, p.identifier)} key={p.identifier} - className="w-full h-full p-[20px] min-h-[100px] text-[14px] bg-third hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer" + className="w-full h-full p-[20px] min-h-[100px] text-[14px] bg-newTableHeader hover:bg-newTableBorder rounded-[8px] transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer" > <div> <img @@ -150,9 +134,7 @@ export const ThirdPartyListComponent: FC<{reload: () => void}> = (props) => { src={`/icons/third-party/${p.identifier}.png`} /> </div> - <div className="whitespace-pre-wrap text-left text-lg"> - {p.title} - </div> + <div className="whitespace-pre-wrap text-left text-lg">{p.title}</div> <div className="whitespace-pre-wrap text-left">{p.description}</div> <div className="w-full flex"> <Button className="w-full">Add</Button> 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 f96bbda9..0545c627 100644 --- a/apps/frontend/src/components/third-parties/third-party.media.tsx +++ b/apps/frontend/src/components/third-parties/third-party.media.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useEffect, useMemo, + useRef, useState, } from 'react'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; @@ -58,6 +59,7 @@ export const ThirdPartyPopup: FC<{ }> = (props) => { const { closeModal, thirdParties, allData, onChange } = props; const [thirdParty, setThirdParty] = useState<any>(null); + const refNew = useRef(null); const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton); useEffect(() => { @@ -83,11 +85,20 @@ export const ThirdPartyPopup: FC<{ closeModal(); }, [setThirdParty, closeModal]); + useEffect(() => { + refNew?.current?.scrollIntoView({ + behavior: 'smooth', + }); + }, []); + return ( <div - className="removeEditor fixed start-0 top-0 bg-primary/80 z-[300] w-full min-h-full animate-fade bg-black/30" + className="removeEditor fixed start-0 top-0 bg-primary/80 z-[300] w-full min-h-full animate-fade bg-black/30 rounded-[24px]" onClick={closeModal} > + <div className="relative"> + <div className="absolute -top-[50px] left-0" ref={refNew} /> + </div> <div className="max-w-[1000px] w-full h-full bg-sixth border-tableBorder border-2 rounded-xl relative mx-auto" onClick={(e) => e.stopPropagation()} @@ -213,7 +224,7 @@ export const ThirdPartyMedia: FC<{ <div className="relative group"> <Button className={clsx( - 'relative ms-[10px] !px-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-customColor21 bg-input' + 'relative ms-[10px] !px-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-newBgLineColor bg-newColColor' )} onClick={() => setPopup(true)} > diff --git a/apps/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js index dbc6b7b9..71fa7e9b 100644 --- a/apps/frontend/tailwind.config.js +++ b/apps/frontend/tailwind.config.js @@ -6,7 +6,7 @@ module.exports = { colors: { primary: 'var(--color-primary)', secondary: 'var(--color-secondary)', - textColor: 'var(--color-text)', + textColor: 'var(--new-btn-text)', third: 'var(--color-third)', forth: 'var(--color-forth)', fifth: 'var(--color-fifth)', @@ -72,6 +72,30 @@ module.exports = { customColor54: 'var(--color-custom54)', customColor55: 'var(--color-custom55)', modalCustom: 'var(--color-modalCustom)', + + + + newBgColor: 'var(--new-bgColor)', + newBgColorInner: 'var(--new-bgColorInner)', + newBgLineColor: 'var(--new-bgLineColor)', + textItemFocused: 'var(--new-textItemFocused)', + textItemBlur: 'var(--new-textItemBlur)', + boxFocused: 'var(--new-boxFocused)', + newTextColor: 'rgb(var(--new-textColor) / <alpha-value>)', + blockSeparator: 'var(--new-blockSeparator)', + btnSimple: 'var(--new-btn-simple)', + btnText: 'var(--new-btn-text)', + btnPrimary: 'var(--new-btn-primary)', + ai: 'var(--new-ai-btn)', + boxHover: 'var(--new-box-hover)', + newTableBorder: 'var(--new-table-border)', + newTableHeader: 'var(--new-table-header)', + newTableText: 'var(--new-table-text)', + newTableTextFocused: 'var(--new-table-text-focused)', + newColColor: 'var(--new-col-color)', + menuDots: 'var(--new-menu-dots)', + menuDotsHover: 'var(--new-menu-hover)', + bigStrip: 'var(--new-big-strips)', }, gridTemplateColumns: { 13: 'repeat(13, minmax(0, 1fr));', @@ -97,6 +121,7 @@ module.exports = { yellow: '0 0 60px 20px #6b6237', yellowToast: '0px 0px 50px rgba(252, 186, 3, 0.3)', greenToast: '0px 0px 50px rgba(60, 124, 90, 0.3)', + menu: 'var(--menu-shadow)' }, // that is actual animation keyframes: (theme) => ({ diff --git a/libraries/react-shared-libraries/src/form/input.tsx b/libraries/react-shared-libraries/src/form/input.tsx index 012013bf..18e0cfc7 100644 --- a/libraries/react-shared-libraries/src/form/input.tsx +++ b/libraries/react-shared-libraries/src/form/input.tsx @@ -63,14 +63,14 @@ export const Input: FC< )} <div className={clsx( - 'bg-input h-[44px] border-fifth border rounded-[4px] text-inputText placeholder-inputText flex items-center justify-center', + 'bg-newBgColorInner h-[42px] border-newTableBorder border rounded-[8px] text-textColor placeholder-textColor flex items-center justify-center', className )} > {icon && <div className="ps-[16px]">{icon}</div>} <input className={clsx( - 'h-full bg-transparent outline-none flex-1', + 'h-full bg-transparent outline-none flex-1 text-[14px] text-textColor', icon ? 'pl-[8px] pe-[16px]' : 'px-[16px]' )} {...(disableForm ? {} : form.register(props.name))} diff --git a/libraries/react-shared-libraries/src/form/select.tsx b/libraries/react-shared-libraries/src/form/select.tsx index dba0dd03..778b1695 100644 --- a/libraries/react-shared-libraries/src/form/select.tsx +++ b/libraries/react-shared-libraries/src/form/select.tsx @@ -58,7 +58,7 @@ export const Select: FC< ref={ref} {...(disableForm ? {} : form.register(props.name, extraForm))} className={clsx( - 'bg-input h-[44px] px-[16px] outline-none border-fifth border rounded-[4px] text-inputText placeholder-inputText', + 'h-[42px] bg-newBgColorInner px-[16px] outline-none border-newTableBorder border rounded-[8px] text-[14px]', className )} {...rest} diff --git a/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx b/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx index f314088a..ffdaa18a 100644 --- a/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx +++ b/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx @@ -14,7 +14,7 @@ export const MantineWrapper = (props: { children: ReactNode }) => { modalProps={{ dir, classNames: { - modal: 'bg-primary text-white border-fifth border', + modal: 'bg-primary text-white', close: 'bg-black hover:bg-black cursor-pointer', }, }} 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 71036063..f1ae8f6e 100644 --- a/libraries/react-shared-libraries/src/translation/locales/en/translation.json +++ b/libraries/react-shared-libraries/src/translation/locales/en/translation.json @@ -160,7 +160,7 @@ "customer": "Customer:", "repeat_post_every": "Repeat Post Every...", "use_this_media": "Use this media", - "create_new_post": "Create New Post", + "create_new_post": "Create Post", "update_post": "Update Existing Post", "merge_comments_into_one_post": "Merge comments into one post", "accounts_that_will_engage": "Accounts that will engage:", @@ -447,10 +447,10 @@ "choose_upload_without_posting_description": "Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.", "faq_am_i_going_to_be_charged_by_postiz": "Am I going to be charged by Postiz?", "faq_to_confirm_credit_card_information_postiz_will_hold": "To confirm credit card information Postiz will hold $2 and release it immediately", - "faq_can_i_trust_postiz_gitroom": "Can I trust Postiz/Gitroom?", - "faq_postiz_gitroom_is_proudly_open_source": "Postiz/Gitroom is proudly open-source! We believe in an ethical and transparent culture, meaning that Postiz/Gitroom will live forever. You can check out the entire code or use it for personal projects. To view the open-source repository, <a href=\"https://github.com/gitroomhq/postiz-app\" target=\"_blank\" style=\"text-decoration: underline;\">click here</a>.", + "faq_can_i_trust_postiz_gitroom": "Can I trust Postiz?", + "faq_postiz_gitroom_is_proudly_open_source": "Postiz is proudly open-source! We believe in an ethical and transparent culture, meaning that Postiz will live forever. You can check out the entire code or use it for personal projects. To view the open-source repository, <a href=\"https://github.com/gitroomhq/postiz-app\" target=\"_blank\" style=\"text-decoration: underline;\">click here</a>.", "faq_what_are_channels": "What are channels?", - "faq_postiz_gitroom_allows_you_to_schedule_posts": "Postiz/Gitroom allows you to schedule your posts between different channels.\nA channel is a publishing platform where you can schedule your posts.\nFor example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest.", + "faq_postiz_gitroom_allows_you_to_schedule_posts": "Postiz allows you to schedule your posts between different channels.\nA channel is a publishing platform where you can schedule your posts.\nFor example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest.", "faq_what_are_team_members": "What are team members?", "faq_if_you_have_a_team_with_multiple_members": "If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels", "faq_what_is_ai_auto_complete": "What is AI auto-complete?",