From 1623b37a1b50499473cf82a7aa0dd8f73f63a415 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 10 Apr 2024 13:26:45 +0500 Subject: [PATCH] feat(widget): use theme colors from URL (#4188) * feat(widget): use theme colors from URL * fix: dark theme for skeleton * chore: fix useColorMode * chore: rename isCowSwapWidgetPalette * refactor: use json instead of keys * fix: update color palette --- apps/cowswap-frontend/index.html | 23 +++++++++++++ .../src/legacy/theme/index.tsx | 9 ++++- .../application/containers/App/Updaters.tsx | 2 -- .../hooks/useInjectedWidgetPalette.ts | 25 +++++++++----- .../hooks/useInjectedWidgetParams.ts | 4 +-- .../state/injectedWidgetParamsAtom.ts | 6 ++-- .../updaters/InjectedWidgetUpdater.tsx | 4 +-- .../utils/validateWidgetParams.ts | 8 ++--- .../pure/OrdersTableContainer/index.tsx | 4 +-- .../src/modules/sounds/utils/sound.ts | 4 +-- .../src/app/configurator/consts.ts | 6 ++-- .../hooks/useColorPaletteManager.ts | 14 +++++--- .../hooks/useWidgetParamsAndSettings.ts | 27 ++++++++------- .../src/app/configurator/types.ts | 15 ++------ .../embedDialog/utils/sanitizeParameters.ts | 6 ++-- .../src/theme/hooks/useColorMode.ts | 17 +++++++--- libs/widget-lib/src/cowSwapWidget.ts | 20 +++++++---- libs/widget-lib/src/index.ts | 1 + libs/widget-lib/src/themeUtils.ts | 7 ++++ libs/widget-lib/src/types.ts | 34 ++++++++++++------- libs/widget-lib/src/urlUtils.ts | 28 +++++++++++---- 21 files changed, 172 insertions(+), 92 deletions(-) create mode 100644 libs/widget-lib/src/themeUtils.ts diff --git a/apps/cowswap-frontend/index.html b/apps/cowswap-frontend/index.html index 3f9d75ef88..8a183b0279 100644 --- a/apps/cowswap-frontend/index.html +++ b/apps/cowswap-frontend/index.html @@ -102,6 +102,15 @@ flex-flow: column wrap; } + #skeleton.skeleton-dark { + --colorBorder: rgb(236 241 248 / 15%); + --colorShimmerColor1: rgba(236, 241, 248, 0) 0; + --colorShimmerColor2: rgba(236, 241, 248, 0.1) 20%; + --colorShimmerColor3: rgba(255, 255, 255, 0.2) 50%; + --colorShimmerColor4: rgba(236, 241, 248, 0.1) 80%; + --colorShimmerColor5: rgba(236, 241, 248, 0) 100%; + } + @media (prefers-color-scheme: dark) { #skeleton { --colorBorder: rgb(236 241 248 / 15%); @@ -200,6 +209,20 @@ + +
diff --git a/apps/cowswap-frontend/src/legacy/theme/index.tsx b/apps/cowswap-frontend/src/legacy/theme/index.tsx index 3c900a9f7f..4685666eee 100644 --- a/apps/cowswap-frontend/src/legacy/theme/index.tsx +++ b/apps/cowswap-frontend/src/legacy/theme/index.tsx @@ -19,6 +19,8 @@ import { import { useInjectedWidgetPalette } from 'modules/injectedWidget' +import { ThemeFromUrlUpdater } from 'common/updaters/ThemeFromUrlUpdater' + import { mapWidgetTheme } from './mapWidgetTheme' import { Colors } from './styled' @@ -189,7 +191,12 @@ export default function ThemeProvider({ children }: { children?: React.ReactNode return defaultTheme }, [darkMode, injectedWidgetTheme]) - return {children} + return ( + <> + + {children} + + ) } export const FixedGlobalStyle = FixedGlobalStyleBase diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx index 5f85638600..31fd0db0a2 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx @@ -29,7 +29,6 @@ import { } from 'common/updaters/orders' import { SpotPricesUpdater } from 'common/updaters/orders/SpotPricesUpdater' import { SentryUpdater } from 'common/updaters/SentryUpdater' -import { ThemeFromUrlUpdater } from 'common/updaters/ThemeFromUrlUpdater' import { UserUpdater } from 'common/updaters/UserUpdater' export function Updaters() { @@ -60,7 +59,6 @@ export function Updaters() { - diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetPalette.ts b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetPalette.ts index 63b364424c..485eef70cb 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetPalette.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetPalette.ts @@ -1,14 +1,23 @@ -import type { CowSwapWidgetPalette } from '@cowprotocol/widget-lib' +import { useMemo } from 'react' -import { useInjectedWidgetParams } from './useInjectedWidgetParams' +import { CowSwapWidgetPaletteParams } from '@cowprotocol/widget-lib' + +import { useLocation } from 'react-router-dom' // The theme palette provided by a consumer -export function useInjectedWidgetPalette(): Partial | undefined { - const state = useInjectedWidgetParams() +export function useInjectedWidgetPalette(): Partial | undefined { + const { search } = useLocation() - return isCowSwapWidgetPallet(state.theme) ? state.theme : undefined -} + return useMemo(() => { + const searchParams = new URLSearchParams(search) + const palette = searchParams.get('palette') + + if (!palette) return undefined -function isCowSwapWidgetPallet(palette: any): palette is CowSwapWidgetPalette { - return palette && typeof palette === 'object' + try { + return JSON.parse(decodeURIComponent(palette)) + } catch (e) { + console.error('Failed to parse palette from URL', e) + } + }, [search]) } diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts index 899b117e35..d16206c0e0 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts @@ -1,10 +1,10 @@ import { useAtomValue } from 'jotai' -import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' import { injectedWidgetParamsAtom } from '../state/injectedWidgetParamsAtom' -export function useInjectedWidgetParams(): Partial { +export function useInjectedWidgetParams(): Partial { const { params } = useAtomValue(injectedWidgetParamsAtom) return params diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts index 7108e0ff48..3349e02cf9 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts @@ -1,10 +1,10 @@ import { atom } from 'jotai' -import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' -export type WidgetParamsErrors = Partial<{ [key in keyof CowSwapWidgetParams]: string[] | undefined }> +export type WidgetParamsErrors = Partial<{ [key in keyof CowSwapWidgetAppParams]: string[] | undefined }> -export const injectedWidgetParamsAtom = atom<{ params: Partial; errors: WidgetParamsErrors }>({ +export const injectedWidgetParamsAtom = atom<{ params: Partial; errors: WidgetParamsErrors }>({ params: {}, errors: {}, }) diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx index a5b6c4d86d..26990b7edf 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef } from 'react' import { useFeatureFlags } from '@cowprotocol/common-hooks' import { deepEqual } from '@cowprotocol/common-utils' import { - CowSwapWidgetParams, + CowSwapWidgetAppParams, listenToMessageFromWindow, postMessageToWindow, stopListeningWindowListener, @@ -36,7 +36,7 @@ const cacheMessages = (event: MessageEvent) => { messagesCache[method] = event.data } -const paramsWithoutPartnerFee = (params: CowSwapWidgetParams) => { +const paramsWithoutPartnerFee = (params: CowSwapWidgetAppParams) => { const { partnerFee: _, ...rest } = params return rest diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/utils/validateWidgetParams.ts b/apps/cowswap-frontend/src/modules/injectedWidget/utils/validateWidgetParams.ts index 48a4d41fb1..43af6708d8 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/utils/validateWidgetParams.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/utils/validateWidgetParams.ts @@ -1,16 +1,16 @@ -import { CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' import { validatePartnerFee } from './validatePartnerFee' import { WidgetParamsErrors } from '../state/injectedWidgetParamsAtom' -type Keys = keyof CowSwapWidgetParams +type Keys = keyof CowSwapWidgetAppParams -const VALIDATIONS: Partial<{ [key in Keys]: (param: CowSwapWidgetParams[key]) => string[] | undefined }> = { +const VALIDATIONS: Partial<{ [key in Keys]: (param: CowSwapWidgetAppParams[key]) => string[] | undefined }> = { partnerFee: validatePartnerFee, } -export function validateWidgetParams(params: CowSwapWidgetParams): WidgetParamsErrors { +export function validateWidgetParams(params: CowSwapWidgetAppParams): WidgetParamsErrors { const keys = Object.keys(params) as Keys[] return keys.reduce((acc, key) => { diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx index 21230a4719..dbccd4a010 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx @@ -5,7 +5,7 @@ import imageConnectWallet from '@cowprotocol/assets/cow-swap/wallet-plus.svg' import { isInjectedWidget } from '@cowprotocol/common-utils' import { ExternalLink } from '@cowprotocol/ui' import { UI, CowSwapSafeAppLink, MY_ORDERS_ID } from '@cowprotocol/ui' -import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import type { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' import { Trans } from '@lingui/macro' import SVG from 'react-inlinesvg' @@ -175,7 +175,7 @@ interface OrdersProps extends OrdersTabsProps, OrdersTableProps { pendingActivities: string[] children?: ReactNode orderType: TabOrderTypes - injectedWidgetParams: Partial + injectedWidgetParams: Partial } export function OrdersTableContainer({ diff --git a/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts b/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts index 15545de2fc..cb886bc82b 100644 --- a/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts +++ b/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts @@ -1,12 +1,12 @@ import { CHRISTMAS_THEME_ENABLED } from '@cowprotocol/common-const' import { jotaiStore } from '@cowprotocol/core' -import { CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' import { injectedWidgetParamsAtom } from 'modules/injectedWidget/state/injectedWidgetParamsAtom' type SoundType = 'SEND' | 'SUCCESS' | 'ERROR' type Sounds = Record -type WidgetSounds = keyof NonNullable +type WidgetSounds = keyof NonNullable const COW_SOUNDS: Sounds = { SEND: CHRISTMAS_THEME_ENABLED ? '/audio/send-winterTheme.mp3' : '/audio/send.mp3', diff --git a/apps/widget-configurator/src/app/configurator/consts.ts b/apps/widget-configurator/src/app/configurator/consts.ts index b226227e55..7c94c23862 100644 --- a/apps/widget-configurator/src/app/configurator/consts.ts +++ b/apps/widget-configurator/src/app/configurator/consts.ts @@ -1,5 +1,5 @@ import { CowEventListeners, CowEvents, ToastMessageType } from '@cowprotocol/events' -import { TradeType, TokenInfo } from '@cowprotocol/widget-lib' +import { TradeType, TokenInfo, CowSwapWidgetPaletteParams } from '@cowprotocol/widget-lib' import { TokenListItem } from './types' @@ -36,7 +36,7 @@ export const DEFAULT_TOKEN_LISTS: TokenListItem[] = [ ] // TODO: Move default palette to a new lib that only exposes the palette colors. // This wayit can be consumed by both the configurator and the widget. -export const DEFAULT_LIGHT_PALETTE = { +export const DEFAULT_LIGHT_PALETTE: CowSwapWidgetPaletteParams = { primary: '#052b65', background: '#FFFFFF', paper: '#FFFFFF', @@ -48,7 +48,7 @@ export const DEFAULT_LIGHT_PALETTE = { success: '#007B28', } -export const DEFAULT_DARK_PALETTE = { +export const DEFAULT_DARK_PALETTE: CowSwapWidgetPaletteParams = { primary: '#0d5ed9', background: '#303030', paper: '#0c264b', diff --git a/apps/widget-configurator/src/app/configurator/hooks/useColorPaletteManager.ts b/apps/widget-configurator/src/app/configurator/hooks/useColorPaletteManager.ts index 2b90334a6e..718072bb9e 100644 --- a/apps/widget-configurator/src/app/configurator/hooks/useColorPaletteManager.ts +++ b/apps/widget-configurator/src/app/configurator/hooks/useColorPaletteManager.ts @@ -7,6 +7,12 @@ import { ColorPalette } from '../types' const LOCAL_STORAGE_KEY_NAME = 'COW_WIDGET_PALETTE_' +const getCachedPalette = (mode: PaletteMode) => { + const cache = localStorage.getItem(`${LOCAL_STORAGE_KEY_NAME}${mode}`) + + return cache ? JSON.parse(cache) : null +} + export interface ColorPaletteManager { defaultPalette: ColorPalette colorPalette: ColorPalette @@ -19,7 +25,7 @@ export function useColorPaletteManager(mode: PaletteMode): ColorPaletteManager { return mode === 'dark' ? DEFAULT_DARK_PALETTE : DEFAULT_LIGHT_PALETTE }, [mode]) - const [colorPalette, updateColorPalette] = useState(defaultPalette) + const [colorPalette, updateColorPalette] = useState(getCachedPalette(mode) || defaultPalette) const persistPalette = useCallback( (colorPalette: ColorPalette) => { @@ -44,11 +50,9 @@ export function useColorPaletteManager(mode: PaletteMode): ColorPaletteManager { // Restore palette from localStorage when mode changes useEffect(() => { - const savedPalette = localStorage.getItem(`${LOCAL_STORAGE_KEY_NAME}${mode}`) - - const newPalette = savedPalette ? JSON.parse(savedPalette) : defaultPalette + const newPalette = getCachedPalette(mode) - updateColorPalette(newPalette) + updateColorPalette(newPalette || defaultPalette) }, [mode, defaultPalette]) return { defaultPalette, colorPalette, setColorPalette, resetColorPalette } diff --git a/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts b/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts index d664c0c4ce..a535639b0c 100644 --- a/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts +++ b/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts @@ -56,18 +56,21 @@ export function useWidgetParams(configuratorState: ConfiguratorState): CowSwapWi sell: { asset: sellToken, amount: sellTokenAmount ? sellTokenAmount.toString() : undefined }, buy: { asset: buyToken, amount: buyTokenAmount?.toString() }, enabledTradeTypes, - theme: { - baseTheme: theme, - primary: themeColors.primary, - background: themeColors.background, - paper: themeColors.paper, - text: themeColors.text, - danger: themeColors.danger, - warning: themeColors.warning, - alert: themeColors.alert, - info: themeColors.info, - success: themeColors.success, - }, + theme: + JSON.stringify(customColors) === JSON.stringify(defaultColors) + ? theme + : { + baseTheme: theme, + primary: themeColors.primary, + background: themeColors.background, + paper: themeColors.paper, + text: themeColors.text, + danger: themeColors.danger, + warning: themeColors.warning, + alert: themeColors.alert, + info: themeColors.info, + success: themeColors.success, + }, standaloneMode, disableToastMessages, diff --git a/apps/widget-configurator/src/app/configurator/types.ts b/apps/widget-configurator/src/app/configurator/types.ts index b9b0860a0c..1c721586bd 100644 --- a/apps/widget-configurator/src/app/configurator/types.ts +++ b/apps/widget-configurator/src/app/configurator/types.ts @@ -1,21 +1,10 @@ import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import type { TradeType } from '@cowprotocol/widget-lib' +import { CowSwapWidgetPaletteColors, TradeType } from '@cowprotocol/widget-lib' import { PaletteMode } from '@mui/material' -export type ColorKeys = - | 'primary' - | 'background' - | 'paper' - | 'text' - | 'danger' - | 'warning' - | 'alert' - | 'info' - | 'success' - export type ColorPalette = { - [key in ColorKeys]: string + [key in CowSwapWidgetPaletteColors]: string } export interface TokenListItem { diff --git a/apps/widget-configurator/src/app/embedDialog/utils/sanitizeParameters.ts b/apps/widget-configurator/src/app/embedDialog/utils/sanitizeParameters.ts index 2451b4df65..d459a6f68d 100644 --- a/apps/widget-configurator/src/app/embedDialog/utils/sanitizeParameters.ts +++ b/apps/widget-configurator/src/app/embedDialog/utils/sanitizeParameters.ts @@ -1,6 +1,6 @@ -import { CowSwapWidgetPalette, CowSwapWidgetParams } from '@cowprotocol/widget-lib' +import { CowSwapWidgetPalette, CowSwapWidgetPaletteColors, CowSwapWidgetParams } from '@cowprotocol/widget-lib' -import { ColorKeys, ColorPalette } from '../../configurator/types' +import { ColorPalette } from '../../configurator/types' import { SANITIZE_PARAMS } from '../const' export function sanitizeParameters(params: CowSwapWidgetParams, defaultPalette: ColorPalette) { @@ -18,7 +18,7 @@ function sanitizePalette(params: CowSwapWidgetParams, defaultPalette: ColorPalet const palette = params.theme const paletteDiff = Object.keys(palette).reduce((acc, key: string) => { - const colorKey = key as ColorKeys + const colorKey = key as CowSwapWidgetPaletteColors if (defaultPalette[colorKey] !== palette[colorKey]) { acc[colorKey] = palette[colorKey] diff --git a/apps/widget-configurator/src/theme/hooks/useColorMode.ts b/apps/widget-configurator/src/theme/hooks/useColorMode.ts index ea03153f75..1af4a0fe96 100644 --- a/apps/widget-configurator/src/theme/hooks/useColorMode.ts +++ b/apps/widget-configurator/src/theme/hooks/useColorMode.ts @@ -5,21 +5,30 @@ import useMediaQuery from '@mui/material/useMediaQuery' import { ColorModeParams } from '../ColorModeContext' +const THEME_STORAGE_KEY = 'widget-cfg-theme' + +const getThemeFromCache = () => localStorage.getItem(THEME_STORAGE_KEY) as PaletteMode + export function useColorMode(): ColorModeParams { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') - const [mode, setMode] = useState(prefersDarkMode ? 'dark' : 'light') + const [mode, setMode] = useState(getThemeFromCache() || (prefersDarkMode ? 'dark' : 'light')) + + const updateMode = (mode: PaletteMode) => { + setMode(mode) + localStorage.setItem(THEME_STORAGE_KEY, mode) + } return useMemo( () => ({ mode, setMode: (mode: PaletteMode) => { - setMode(mode) + updateMode(mode) }, toggleColorMode: () => { - setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')) + updateMode(mode === 'light' ? 'dark' : 'light') }, setAutoMode: () => { - setMode(prefersDarkMode ? 'dark' : 'light') + updateMode(prefersDarkMode ? 'dark' : 'light') }, }), [mode, prefersDarkMode] diff --git a/libs/widget-lib/src/cowSwapWidget.ts b/libs/widget-lib/src/cowSwapWidget.ts index 31559fc53b..4f38f37dbe 100644 --- a/libs/widget-lib/src/cowSwapWidget.ts +++ b/libs/widget-lib/src/cowSwapWidget.ts @@ -7,7 +7,7 @@ import { WidgetMethodsEmit, WidgetMethodsListen, } from './types' -import { buildTradeAmountsQuery, buildWidgetPath, buildWidgetUrl } from './urlUtils' +import { buildWidgetPath, buildWidgetUrl, buildWidgetUrlQuery } from './urlUtils' import { IframeCowEventEmitter } from './IframeCowEventEmitter' import { WindowListener, listenToMessageFromWindow, postMessageToWindow, stopListeningWindowListener } from './messages' import { IframeSafeSdkBridge } from './IframeSafeSdkBridge' @@ -42,6 +42,7 @@ export interface CowSwapWidgetHandler { export function createCowSwapWidget(container: HTMLElement, props: CowSwapWidgetProps): CowSwapWidgetHandler { const { params, provider: providerAux, listeners } = props let provider = providerAux + let currentParams = params // 1. Create a brand new iframe const iframe = createIframe(params) @@ -70,14 +71,17 @@ export function createCowSwapWidget(container: HTMLElement, props: CowSwapWidget let iframeRpcProviderBridge = updateProvider(iframeWindow, null, provider) // 7. Schedule the uploading of the params, once the iframe is loaded - iframe.addEventListener('load', () => updateParams(iframeWindow, params, provider)) + iframe.addEventListener('load', () => updateParams(iframeWindow, currentParams, provider)) // 8. Listen for messages from the iframe const iframeSafeSdkBridge = new IframeSafeSdkBridge(window, iframeWindow) // 9. Return the handler, so the widget, listeners, and provider can be updated return { - updateParams: (newParams: CowSwapWidgetParams) => updateParams(iframeWindow, newParams, provider), + updateParams: (newParams: CowSwapWidgetParams) => { + currentParams = newParams + updateParams(iframeWindow, currentParams, provider) + }, updateListeners: (newListeners?: CowEventListeners) => iFrameCowEventEmitter.updateListeners(newListeners), updateProvider: (newProvider) => { provider = newProvider @@ -160,16 +164,18 @@ function updateParams(contentWindow: Window, params: CowSwapWidgetParams, provid const hasProvider = !!provider const pathname = buildWidgetPath(params) - const search = buildTradeAmountsQuery(params).toString() + const search = buildWidgetUrlQuery(params).toString() + + // Omit theme from appParams + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { theme, ...appParams } = params postMessageToWindow(contentWindow, WidgetMethodsListen.UPDATE_PARAMS, { urlParams: { pathname, search, }, - appParams: { - ...params, - }, + appParams, hasProvider, }) } diff --git a/libs/widget-lib/src/index.ts b/libs/widget-lib/src/index.ts index 2eb20eb688..09007a1bb4 100644 --- a/libs/widget-lib/src/index.ts +++ b/libs/widget-lib/src/index.ts @@ -2,3 +2,4 @@ export { createCowSwapWidget } from './cowSwapWidget' export type { CowSwapWidgetHandler } from './cowSwapWidget' export * from './types' export * from './messages' +export * from './themeUtils' diff --git a/libs/widget-lib/src/themeUtils.ts b/libs/widget-lib/src/themeUtils.ts new file mode 100644 index 0000000000..a22d9aa5c0 --- /dev/null +++ b/libs/widget-lib/src/themeUtils.ts @@ -0,0 +1,7 @@ +import { CowSwapTheme, CowSwapWidgetPalette } from './types' + +export function isCowSwapWidgetPalette( + palette: CowSwapTheme | CowSwapWidgetPalette | undefined +): palette is CowSwapWidgetPalette { + return Boolean(palette && typeof palette === 'object') +} diff --git a/libs/widget-lib/src/types.ts b/libs/widget-lib/src/types.ts index c649b1b6f7..470a5ce87f 100644 --- a/libs/widget-lib/src/types.ts +++ b/libs/widget-lib/src/types.ts @@ -108,18 +108,23 @@ export type TokenInfo = { logoURI?: string } -export interface CowSwapWidgetPalette { - baseTheme: CowSwapTheme - primary: string - background: string - paper: string - text: string - danger: string - warning: string - alert: string - info: string - success: string -} +export const WIDGET_PALETTE_COLORS = [ + 'primary', + 'background', + 'paper', + 'text', + 'danger', + 'warning', + 'alert', + 'info', + 'success', +] as const + +export type CowSwapWidgetPaletteColors = (typeof WIDGET_PALETTE_COLORS)[number] + +export type CowSwapWidgetPaletteParams = { [K in CowSwapWidgetPaletteColors]: string } + +export type CowSwapWidgetPalette = { baseTheme: CowSwapTheme } & CowSwapWidgetPaletteParams export interface CowSwapWidgetSounds { /** @@ -297,12 +302,15 @@ export interface WidgetMethodsListenPayloadMap { export type WidgetMethodsEmitPayloads = WidgetMethodsEmitPayloadMap[WidgetMethodsEmit] export type WidgetMethodsListenPayloads = WidgetMethodsListenPayloadMap[WidgetMethodsListen] +export type CowSwapWidgetAppParams = Omit + export interface UpdateParamsPayload { urlParams: { pathname: string + // Contains theme and other query params search: string } - appParams: CowSwapWidgetParams + appParams: CowSwapWidgetAppParams hasProvider: boolean } diff --git a/libs/widget-lib/src/urlUtils.ts b/libs/widget-lib/src/urlUtils.ts index 44871b2348..3c78144601 100644 --- a/libs/widget-lib/src/urlUtils.ts +++ b/libs/widget-lib/src/urlUtils.ts @@ -1,13 +1,13 @@ import { CowSwapWidgetParams, TradeType } from './types' +import { isCowSwapWidgetPalette } from './themeUtils' const EMPTY_TOKEN = '_' export function buildWidgetUrl(params: Partial): string { const host = typeof params.baseUrl === 'string' ? params.baseUrl : 'https://swap.cow.fi' const path = buildWidgetPath(params) - const query = buildTradeAmountsQuery(params) - return host + '/#' + path + '?' + query + return host + '/#' + path + '?' + buildWidgetUrlQuery(params) } export function buildWidgetPath(params: Partial): string { @@ -18,10 +18,15 @@ export function buildWidgetPath(params: Partial): string { return `/${chainId}/widget/${tradeType}/${assetsPath}` } -export function buildTradeAmountsQuery(params: Partial): URLSearchParams { - const { sell, buy, theme } = params +export function buildWidgetUrlQuery(params: Partial): URLSearchParams { const query = new URLSearchParams() + return addThemePaletteToQuery(addTradeAmountsToQuery(query, params), params) +} + +function addTradeAmountsToQuery(query: URLSearchParams, params: Partial): URLSearchParams { + const { sell, buy } = params + if (sell?.amount) { query.append('sellAmount', sell.amount) } @@ -30,8 +35,19 @@ export function buildTradeAmountsQuery(params: Partial): UR query.append('buyAmount', buy.amount) } - if (theme) { - query.append('theme', typeof theme === 'string' ? theme : theme.baseTheme) + return query +} + +function addThemePaletteToQuery(query: URLSearchParams, params: Partial): URLSearchParams { + const theme = params.theme + + if (!theme) return query + + if (isCowSwapWidgetPalette(theme)) { + query.append('palette', encodeURIComponent(JSON.stringify(theme))) + query.append('theme', theme.baseTheme) + } else { + query.append('theme', theme) } return query