diff --git a/apps/builder/src/components/inputs/RadioButtons.tsx b/apps/builder/src/components/inputs/RadioButtons.tsx index a31134eca..9453f43cc 100644 --- a/apps/builder/src/components/inputs/RadioButtons.tsx +++ b/apps/builder/src/components/inputs/RadioButtons.tsx @@ -10,7 +10,7 @@ import { import { ReactNode } from 'react' type Props = { - options: (T | { value: T; label: ReactNode })[] + options: readonly (T | { value: T; label: ReactNode })[] value?: T defaultValue?: T direction?: 'row' | 'column' diff --git a/apps/builder/src/features/theme/components/ThemeTemplateCard.tsx b/apps/builder/src/features/theme/components/ThemeTemplateCard.tsx index ed5654785..5a6574de1 100644 --- a/apps/builder/src/features/theme/components/ThemeTemplateCard.tsx +++ b/apps/builder/src/features/theme/components/ThemeTemplateCard.tsx @@ -213,7 +213,7 @@ const AvatarPreview = ({ avatar: NonNullable['hostAvatar'] }) => { const { t } = useTranslate() - if (avatar?.isEnabled) return null + if (!avatar?.isEnabled) return null return avatar?.url ? ( void +} + +export const CustomFontForm = ({ font, onFontChange }: Props) => { + const updateFamily = (family: string) => onFontChange({ ...font, family }) + const updateUrl = (url: string) => onFontChange({ ...font, url }) + return ( + + + + + ) +} diff --git a/apps/builder/src/features/theme/components/general/FontForm.tsx b/apps/builder/src/features/theme/components/general/FontForm.tsx new file mode 100644 index 000000000..548582530 --- /dev/null +++ b/apps/builder/src/features/theme/components/general/FontForm.tsx @@ -0,0 +1,15 @@ +import { Font } from '@typebot.io/schemas' +import { GoogleFontForm } from './GoogleFontForm' +import { CustomFontForm } from './CustomFontForm' + +type Props = { + font: Font + onFontChange: (font: Font) => void +} + +export const FontForm = ({ font, onFontChange }: Props) => { + if (typeof font === 'string' || font.type === 'Google') + return + if (font.type === 'Custom') + return +} diff --git a/apps/builder/src/features/theme/components/general/FontSelector.tsx b/apps/builder/src/features/theme/components/general/FontSelector.tsx deleted file mode 100644 index 06d3ebbd2..000000000 --- a/apps/builder/src/features/theme/components/general/FontSelector.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { Text, HStack } from '@chakra-ui/react' -import { AutocompleteInput } from '@/components/inputs/AutocompleteInput' -import { env } from '@typebot.io/env' -import { useTranslate } from '@tolgee/react' - -type FontSelectorProps = { - activeFont?: string - onSelectFont: (font: string) => void -} - -export const FontSelector = ({ - activeFont, - onSelectFont, -}: FontSelectorProps) => { - const { t } = useTranslate() - const [currentFont, setCurrentFont] = useState(activeFont) - const [googleFonts, setGoogleFonts] = useState([]) - - useEffect(() => { - fetchPopularFonts().then(setGoogleFonts) - }, []) - - const fetchPopularFonts = async () => { - if (!env.NEXT_PUBLIC_GOOGLE_API_KEY) return [] - const response = await fetch( - `https://www.googleapis.com/webfonts/v1/webfonts?key=${env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity` - ) - return (await response.json()).items.map( - (item: { family: string }) => item.family - ) - } - - const handleFontSelected = (nextFont: string) => { - if (nextFont === currentFont) return - setCurrentFont(nextFont) - onSelectFont(nextFont) - } - - return ( - - {t('theme.sideMenu.global.font')} - - - ) -} diff --git a/apps/builder/src/features/theme/components/general/GeneralSettings.tsx b/apps/builder/src/features/theme/components/general/GeneralSettings.tsx index a65963259..a4bec14a7 100644 --- a/apps/builder/src/features/theme/components/general/GeneralSettings.tsx +++ b/apps/builder/src/features/theme/components/general/GeneralSettings.tsx @@ -1,18 +1,29 @@ -import { Flex, FormLabel, Stack, Switch, useDisclosure } from '@chakra-ui/react' -import { Background, Theme } from '@typebot.io/schemas' +import { + Flex, + FormLabel, + Stack, + Switch, + useDisclosure, + Text, +} from '@chakra-ui/react' +import { Background, Font, Theme } from '@typebot.io/schemas' import React from 'react' import { BackgroundSelector } from './BackgroundSelector' -import { FontSelector } from './FontSelector' import { LockTag } from '@/features/billing/components/LockTag' import { Plan } from '@typebot.io/prisma' import { isFreePlan } from '@/features/billing/helpers/isFreePlan' import { useWorkspace } from '@/features/workspace/WorkspaceProvider' import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal' import { useTranslate } from '@tolgee/react' -import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants' +import { + defaultTheme, + fontTypes, +} from '@typebot.io/schemas/features/typebot/theme/constants' import { trpc } from '@/lib/trpc' import { env } from '@typebot.io/env' import { useTypebot } from '@/features/editor/providers/TypebotProvider' +import { RadioButtons } from '@/components/inputs/RadioButtons' +import { FontForm } from './FontForm' type Props = { isBrandingEnabled: boolean @@ -36,9 +47,19 @@ export const GeneralSettings = ({ const { mutate: trackClientEvents } = trpc.telemetry.trackClientEvents.useMutation() - const handleSelectFont = (font: string) => + const updateFont = (font: Font) => onGeneralThemeChange({ ...generalTheme, font }) + const updateFontType = (type: (typeof fontTypes)[number]) => { + onGeneralThemeChange({ + ...generalTheme, + font: + typeof generalTheme?.font === 'string' + ? { type } + : { ...generalTheme?.font, type }, + }) + } + const handleBackgroundChange = (background: Background) => onGeneralThemeChange({ ...generalTheme, background }) @@ -63,6 +84,11 @@ export const GeneralSettings = ({ onBrandingChange(!isBrandingEnabled) } + const fontType = + (typeof generalTheme?.font === 'string' + ? 'Google' + : generalTheme?.font?.type) ?? defaultTheme.general.font.type + return ( - + + {t('theme.sideMenu.global.font')} + + + void +} + +export const GoogleFontForm = ({ font, onFontChange }: Props) => { + const [currentFont, setCurrentFont] = useState( + typeof font === 'string' ? font : font.family + ) + const [googleFonts, setGoogleFonts] = useState([]) + + useEffect(() => { + fetchPopularFonts().then(setGoogleFonts) + }, []) + + const fetchPopularFonts = async () => { + if (!env.NEXT_PUBLIC_GOOGLE_API_KEY) return [] + try { + const response = await fetch( + `https://www.googleapis.com/webfonts/v1/webfonts?key=${env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity` + ) + return (await response.json()).items.map( + (item: { family: string }) => item.family + ) + } catch (error) { + console.error('Failed to fetch Google Fonts:', error) + return [] + } + } + + const handleFontSelected = (nextFont: string | undefined) => { + if (nextFont === currentFont || !nextFont) return + setCurrentFont(nextFont) + onFontChange({ type: 'Google', family: nextFont }) + } + + return ( +