From eaadc59b1fc39ab79e024b7d85b4ef7269ccc852 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Tue, 20 Jun 2023 14:04:33 +0200 Subject: [PATCH] :zap: Add recent section in icon and emoji picker Closes #536 --- apps/builder/src/components/ColorPicker.tsx | 2 +- .../ImageUploadContent/IconPicker.tsx | 111 +++++++++++++----- .../emoji/EmojiSearchableList.tsx | 91 ++++++++------ .../blocks/inputs/payment/payment.spec.ts | 4 +- .../googleAnalytics/googleAnalytics.spec.ts | 7 +- .../settings/components/MetadataForm.tsx | 4 +- .../theme/components/chat/AvatarForm.tsx | 1 + .../components/general/BackgroundContent.tsx | 19 +-- .../src/features/workspace/workspaces.spec.ts | 4 +- apps/builder/src/pages/_app.tsx | 1 - packages/embeds/js/src/components/Button.tsx | 2 +- packages/embeds/js/src/components/Spinner.tsx | 2 +- 12 files changed, 161 insertions(+), 87 deletions(-) diff --git a/apps/builder/src/components/ColorPicker.tsx b/apps/builder/src/components/ColorPicker.tsx index bdf7c6408..66842b3d5 100644 --- a/apps/builder/src/components/ColorPicker.tsx +++ b/apps/builder/src/components/ColorPicker.tsx @@ -65,7 +65,7 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => { - + void } +const localStorageRecentIconNamesKey = 'recentIconNames' +const localStorageDefaultIconColorKey = 'defaultIconColor' + export const IconPicker = ({ onIconSelected }: Props) => { const initialIconColor = useColorModeValue('#222222', '#ffffff') const scrollContainer = useRef(null) @@ -28,11 +32,22 @@ export const IconPicker = ({ onIconSelected }: Props) => { const [selectedColor, setSelectedColor] = useState(initialIconColor) const isWhite = useMemo( () => - selectedColor.toLowerCase() === '#ffffff' || - selectedColor.toLowerCase() === '#fff' || - selectedColor === 'white', - [selectedColor] + initialIconColor === '#222222' && + (selectedColor.toLowerCase() === '#ffffff' || + selectedColor.toLowerCase() === '#fff' || + selectedColor === 'white'), + [initialIconColor, selectedColor] ) + const [recentIconNames, setRecentIconNames] = useState([]) + + useEffect(() => { + const recentIconNames = localStorage.getItem(localStorageRecentIconNamesKey) + const defaultIconColor = localStorage.getItem( + localStorageDefaultIconColorKey + ) + if (recentIconNames) setRecentIconNames(JSON.parse(recentIconNames)) + if (defaultIconColor) setSelectedColor(defaultIconColor) + }, []) useEffect(() => { if (!bottomElement.current) return @@ -70,10 +85,15 @@ export const IconPicker = ({ onIconSelected }: Props) => { const updateColor = (color: string) => { if (!color.startsWith('#')) return + localStorage.setItem(localStorageDefaultIconColorKey, color) setSelectedColor(color) } const selectIcon = async (iconName: string) => { + localStorage.setItem( + localStorageRecentIconNamesKey, + JSON.stringify([...new Set([iconName, ...recentIconNames].slice(0, 30))]) + ) const svg = await (await fetch(`/icons/${iconName}.svg`)).text() const dataUri = `data:image/svg+xml;utf8,${svg .replace(' { onChange={searchIcon} withVariableButton={false} /> - + - - {displayedIconNames.map((iconName) => ( - + ))} + + + )} + + {recentIconNames.length > 0 && ( + + ICONS + + )} + - - - ))} + {displayedIconNames.map((iconName) => ( + + ))} + + +
- + ) } diff --git a/apps/builder/src/components/ImageUploadContent/emoji/EmojiSearchableList.tsx b/apps/builder/src/components/ImageUploadContent/emoji/EmojiSearchableList.tsx index 703db265e..9b27dffd7 100644 --- a/apps/builder/src/components/ImageUploadContent/emoji/EmojiSearchableList.tsx +++ b/apps/builder/src/components/ImageUploadContent/emoji/EmojiSearchableList.tsx @@ -21,6 +21,8 @@ const objects = emojis['Objects'] const symbols = emojis['Symbols'] const flags = emojis['Flags'] +const localStorageRecentEmojisKey = 'recentEmojis' + export const EmojiSearchableList = ({ onEmojiSelected, }: { @@ -38,6 +40,13 @@ export const EmojiSearchableList = ({ const [filteredSymbols, setFilteredSymbols] = useState(symbols) const [filteredFlags, setFilteredFlags] = useState(flags) const [totalDisplayedCategories, setTotalDisplayedCategories] = useState(1) + const [recentEmojis, setRecentEmojis] = useState([]) + + useEffect(() => { + const recentIconNames = localStorage.getItem(localStorageRecentEmojisKey) + if (!recentIconNames) return + setRecentEmojis(JSON.parse(recentIconNames)) + }, []) useEffect(() => { if (!bottomElement.current) return @@ -85,84 +94,88 @@ export const EmojiSearchableList = ({ setFilteredFlags(flags) } + const selectEmoji = (emoji: string) => { + localStorage.setItem( + localStorageRecentEmojisKey, + JSON.stringify([...new Set([emoji, ...recentEmojis].slice(0, 30))]) + ) + onEmojiSelected(emoji) + } + return ( + {recentEmojis.length > 0 && ( + + + RECENT + + + + )} {filteredPeople.length > 0 && ( - - People + + PEOPLE - + )} {filteredAnimals.length > 0 && totalDisplayedCategories >= 2 && ( - - Animals & Nature + + ANIMALS & NATURE - + )} {filteredFood.length > 0 && totalDisplayedCategories >= 3 && ( - - Food & Drink + + FOOD & DRINK - + )} {filteredTravel.length > 0 && totalDisplayedCategories >= 4 && ( - - Travel & Places + + TRAVEL & PLACES - + )} {filteredActivities.length > 0 && totalDisplayedCategories >= 5 && ( - - Activities + + ACTIVITIES - + )} {filteredObjects.length > 0 && totalDisplayedCategories >= 6 && ( - - Objects + + OBJECTS - + )} {filteredSymbols.length > 0 && totalDisplayedCategories >= 7 && ( - - Symbols + + SYMBOLS - + )} {filteredFlags.length > 0 && totalDisplayedCategories >= 8 && ( - - Flags + + FLAGS - + )}
@@ -180,7 +193,11 @@ const EmojiGrid = ({ }) => { const handleClick = (emoji: string) => () => onEmojiClick(emoji) return ( - + {emojis.map((emoji) => ( { .locator(`[placeholder="MM / YY"]`) .fill('12 / 25') await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240') - await page.locator(`text="Pay 30€"`).click() + await page.getByRole('button', { name: 'Pay 30,00 €' }).click() await expect( page.locator(`text="Your card has been declined."`) ).toBeVisible() @@ -68,7 +68,7 @@ test.describe('Payment input block', () => { const zipInput = stripePaymentForm(page).getByPlaceholder('90210') const isZipInputVisible = await zipInput.isVisible() if (isZipInputVisible) await zipInput.fill('12345') - await page.locator(`text="Pay 30€"`).click() + await page.getByRole('button', { name: 'Pay 30,00 €' }).click() await expect(page.locator(`text="Success"`)).toBeVisible() }) }) diff --git a/apps/builder/src/features/blocks/integrations/googleAnalytics/googleAnalytics.spec.ts b/apps/builder/src/features/blocks/integrations/googleAnalytics/googleAnalytics.spec.ts index 97b310c10..0bbce4a23 100644 --- a/apps/builder/src/features/blocks/integrations/googleAnalytics/googleAnalytics.spec.ts +++ b/apps/builder/src/features/blocks/integrations/googleAnalytics/googleAnalytics.spec.ts @@ -23,12 +23,9 @@ test.describe('Google Analytics block', () => { await page.goto(`/typebots/${typebotId}/edit`) await page.click('text=Configure...') await page.fill('input[placeholder="G-123456..."]', 'G-VWX9WG1TNS') - await page.fill('input[placeholder="Example: Typebot"]', 'Typebot') - await page.fill( - 'input[placeholder="Example: Submit email"]', - 'Submit email' - ) + await page.fill('input[placeholder="Example: conversion"]', 'conversion') await page.click('text=Advanced') + await page.fill('input[placeholder="Example: Typebot"]', 'Typebot') await page.fill('input[placeholder="Example: Campaign Z"]', 'Campaign Z') await page.fill('input[placeholder="Example: 0"]', '0') }) diff --git a/apps/builder/src/features/settings/components/MetadataForm.tsx b/apps/builder/src/features/settings/components/MetadataForm.tsx index fd95f9535..f735e5aed 100644 --- a/apps/builder/src/features/settings/components/MetadataForm.tsx +++ b/apps/builder/src/features/settings/components/MetadataForm.tsx @@ -59,7 +59,7 @@ export const MetadataForm = ({ rounded="md" /> - + - + e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()} + w="500px" > Select an image )} - - - + + + + + ) default: diff --git a/apps/builder/src/features/workspace/workspaces.spec.ts b/apps/builder/src/features/workspace/workspaces.spec.ts index c4e97ca18..833c87296 100644 --- a/apps/builder/src/features/workspace/workspaces.spec.ts +++ b/apps/builder/src/features/workspace/workspaces.spec.ts @@ -41,7 +41,9 @@ test.beforeAll(async () => { test('can switch between workspaces and access typebot', async ({ page }) => { await page.goto('/typebots') - await expect(page.locator('text="Pro typebot"')).toBeVisible() + await expect(page.locator('text="Pro typebot"')).toBeVisible({ + timeout: 20000, + }) await page.click('text=Pro workspace') await page.click('text="Starter workspace"') await expect(page.locator('text="Pro typebot"')).toBeHidden() diff --git a/apps/builder/src/pages/_app.tsx b/apps/builder/src/pages/_app.tsx index 1463c9447..1329530eb 100644 --- a/apps/builder/src/pages/_app.tsx +++ b/apps/builder/src/pages/_app.tsx @@ -19,7 +19,6 @@ import { I18nProvider } from '@/locales' import { TypebotProvider } from '@/features/editor/providers/TypebotProvider' import { WorkspaceProvider } from '@/features/workspace/WorkspaceProvider' import { isCloudProdInstance } from '@/helpers/isCloudProdInstance' -import en from '@/locales/en' const { ToastContainer, toast } = createStandaloneToast(customTheme) diff --git a/packages/embeds/js/src/components/Button.tsx b/packages/embeds/js/src/components/Button.tsx index 61b7c4574..9f8773d82 100644 --- a/packages/embeds/js/src/components/Button.tsx +++ b/packages/embeds/js/src/components/Button.tsx @@ -17,7 +17,7 @@ export const Button = (props: Props) => { {...buttonProps} disabled={props.isDisabled || props.isLoading} class={ - 'py-2 px-4 font-semibold focus:outline-none filter hover:brightness-90 active:brightness-75 disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100' + + 'py-2 px-4 font-semibold focus:outline-none filter hover:brightness-90 active:brightness-75 disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 flex justify-center' + (props.variant === 'secondary' ? ' secondary-button' : ' typebot-button') + diff --git a/packages/embeds/js/src/components/Spinner.tsx b/packages/embeds/js/src/components/Spinner.tsx index eb42c1d7c..4769226ee 100644 --- a/packages/embeds/js/src/components/Spinner.tsx +++ b/packages/embeds/js/src/components/Spinner.tsx @@ -3,7 +3,7 @@ import { JSX } from 'solid-js' export const Spinner = (props: JSX.SvgSVGAttributes) => (