From c7e5f3de41cd0405216dad3d2a799e481bc1cee7 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 22 Mar 2024 13:51:52 +0500 Subject: [PATCH 1/9] chore: up events lib version (#4081) --- libs/events/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/events/package.json b/libs/events/package.json index a496032245..421e194b16 100644 --- a/libs/events/package.json +++ b/libs/events/package.json @@ -1,6 +1,6 @@ { "name": "@cowprotocol/events", - "version": "0.1.0", + "version": "0.2.0", "type": "commonjs", "description": "CoW Swap events", "main": "index.js", From 49500dbbc4717f90a348145fdcdd2021b282c1a0 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 22 Mar 2024 14:01:21 +0500 Subject: [PATCH 2/9] chore: up widget-lib version (#4082) --- libs/widget-lib/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/widget-lib/package.json b/libs/widget-lib/package.json index d713015691..94fe1bb0a1 100644 --- a/libs/widget-lib/package.json +++ b/libs/widget-lib/package.json @@ -1,6 +1,6 @@ { "name": "@cowprotocol/widget-lib", - "version": "0.6.0", + "version": "0.7.0", "type": "commonjs", "description": "CoW Swap Widget Library. Allows you to easily embed a CoW Swap widget on your website.", "main": "index.js", @@ -21,6 +21,6 @@ "widget-lib" ], "dependencies": { - "@cowprotocol/events": "^0.1.0" + "@cowprotocol/events": "^0.2.0" } } From 80117aa5ef9fca6d45710231bf6fec9956cd264e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Fri, 22 Mar 2024 14:05:10 +0500 Subject: [PATCH 3/9] chore: revert up widget-lib version --- libs/widget-lib/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/widget-lib/package.json b/libs/widget-lib/package.json index 94fe1bb0a1..d713015691 100644 --- a/libs/widget-lib/package.json +++ b/libs/widget-lib/package.json @@ -1,6 +1,6 @@ { "name": "@cowprotocol/widget-lib", - "version": "0.7.0", + "version": "0.6.0", "type": "commonjs", "description": "CoW Swap Widget Library. Allows you to easily embed a CoW Swap widget on your website.", "main": "index.js", @@ -21,6 +21,6 @@ "widget-lib" ], "dependencies": { - "@cowprotocol/events": "^0.2.0" + "@cowprotocol/events": "^0.1.0" } } From a4e9c5b545125104d699a878633ef3a54d82fb60 Mon Sep 17 00:00:00 2001 From: Leandro Date: Fri, 22 Mar 2024 15:18:56 +0000 Subject: [PATCH 4/9] feat(limit): edit limit order (disabled) (#4015) * refactor: remove unnecessary callback * feat: update alternative order atom type to include isEdit flag * feat: show edit/recreate on alternative limit order modal title * chore: update app-data to 2.1.0 * feat: add replacedOrderUid to AppDataInfoUpdater * feat: add isCreating fn helper * feat: add useReplacedOrderUid hook * feat: add replaced order uid to AppDataUpdater * feat: add edit to pending limit orders * refactor: show edit modal before cancel * refactor: rename showAlternativeModal * refactor: simplify OrderContextMenu alternativeOrderMoldal props * test: fix unittests * feat: add signingScheme to order obj * fix: do not allow limit order edit on onchain orders * fix: update alternative limit price since first change * refactor: pass alternativeOrderModalContext to receipt modal * refactor: unify logic for getting alternativeOrderModalContext * feat: disable limit orders editing * chore: fix build --- apps/cowswap-frontend/.env | 14 +++--- .../hooks/useCategorizeRecentActivity.ts | 4 +- .../src/legacy/state/orders/actions.ts | 3 +- .../src/legacy/state/orders/mocks.ts | 3 +- .../src/legacy/state/orders/reducer.ts | 3 ++ .../src/legacy/utils/trade.ts | 5 +- apps/cowswap-frontend/src/mocks/orderMock.ts | 4 +- .../appData/updater/AppDataInfoUpdater.ts | 15 +++++- .../appData/updater/AppDataUpdater.tsx | 3 ++ .../src/modules/appData/utils/buildAppData.ts | 14 +++++- .../src/modules/appData/utils/fullAppData.ts | 2 +- .../limitOrders/hooks/useUpdateActiveRate.ts | 5 +- .../updaters/AlternativeLimitOrderUpdater.ts | 2 +- .../containers/OrdersReceiptModal/hooks.ts | 48 ++++++++++++++----- .../containers/OrdersReceiptModal/index.tsx | 6 +-- .../containers/OrdersTableWidget/index.tsx | 20 ++------ .../OrderRow/OrderContextMenu.tsx | 20 ++++---- .../OrdersTableContainer/OrderRow/index.tsx | 12 ++--- .../OrdersTableContainer/index.cosmos.tsx | 4 +- .../pure/OrdersTableContainer/orders.mock.ts | 7 ++- .../pure/OrdersTableContainer/types.ts | 5 +- .../ordersTable/pure/ReceiptModal/index.tsx | 20 ++++---- .../ethFlow/steps/signEthFlowOrderStep.ts | 3 +- .../trade/state/alternativeOrder/atoms.ts | 7 ++- .../trade/state/alternativeOrder/const.ts | 5 ++ .../trade/state/alternativeOrder/hooks.ts | 14 +++++- .../trade/state/alternativeOrder/index.ts | 1 + .../useTradeQuotePolling.test.tsx.snap | 8 ++-- .../createTwapOrderTxs.test.ts.snap | 6 +-- .../LimitOrders/AlternativeLimitOrder.tsx | 13 +++-- .../src/utils/orderUtils/parseOrder.ts | 8 +++- package.json | 2 +- yarn.lock | 8 ++-- 33 files changed, 194 insertions(+), 100 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/const.ts diff --git a/apps/cowswap-frontend/.env b/apps/cowswap-frontend/.env index 5a0ae8d809..e7ccb80f4a 100644 --- a/apps/cowswap-frontend/.env +++ b/apps/cowswap-frontend/.env @@ -56,13 +56,13 @@ # To set your own `AppData`, change `REACT_APP_FULL_APP_DATA_` # AppData, build yours at https://explorer.cow.fi/appdata -REACT_APP_FULL_APP_DATA_PRODUCTION='{"version":"1.0.0","appCode":"CoW Swap","environment":"production","metadata":{}}' -REACT_APP_FULL_APP_DATA_ENS='{"version":"1.0.0","appCode":"CoW Swap","environment":"ens","metadata":{}}' -REACT_APP_FULL_APP_DATA_BARN='{"version":"1.0.0","appCode":"CoW Swap","environment":"barn","metadata":{}}' -REACT_APP_FULL_APP_DATA_STAGING='{"version":"1.0.0","appCode":"CoW Swap","environment":"staging","metadata":{}}' -REACT_APP_FULL_APP_DATA_PR='{"version":"1.0.0","appCode":"CoW Swap","environment":"pr","metadata":{}}' -REACT_APP_FULL_APP_DATA_DEVELOPMENT='{"version":"1.0.0","appCode":"CoW Swap","environment":"development","metadata":{}}' -REACT_APP_FULL_APP_DATA_LOCAL='{"version":"1.0.0","appCode":"CoW Swap","environment":"local","metadata":{}}' +REACT_APP_FULL_APP_DATA_PRODUCTION='{"version":"1.1.0","appCode":"CoW Swap","environment":"production","metadata":{}}' +REACT_APP_FULL_APP_DATA_ENS='{"version":"1.1.0","appCode":"CoW Swap","environment":"ens","metadata":{}}' +REACT_APP_FULL_APP_DATA_BARN='{"version":"1.1.0","appCode":"CoW Swap","environment":"barn","metadata":{}}' +REACT_APP_FULL_APP_DATA_STAGING='{"version":"1.1.0","appCode":"CoW Swap","environment":"staging","metadata":{}}' +REACT_APP_FULL_APP_DATA_PR='{"version":"1.1.0","appCode":"CoW Swap","environment":"pr","metadata":{}}' +REACT_APP_FULL_APP_DATA_DEVELOPMENT='{"version":"1.1.0","appCode":"CoW Swap","environment":"development","metadata":{}}' +REACT_APP_FULL_APP_DATA_LOCAL='{"version":"1.1.0","appCode":"CoW Swap","environment":"local","metadata":{}}' diff --git a/apps/cowswap-frontend/src/common/hooks/useCategorizeRecentActivity.ts b/apps/cowswap-frontend/src/common/hooks/useCategorizeRecentActivity.ts index 2808d531e3..7044af0a4a 100644 --- a/apps/cowswap-frontend/src/common/hooks/useCategorizeRecentActivity.ts +++ b/apps/cowswap-frontend/src/common/hooks/useCategorizeRecentActivity.ts @@ -3,13 +3,15 @@ import { useMemo } from 'react' import { UiOrderType } from '@cowprotocol/types' import { useRecentActivity } from 'legacy/hooks/useRecentActivity' -import { Order, OrderStatus, PENDING_STATES } from 'legacy/state/orders/actions' +import { CREATING_STATES, Order, OrderStatus, PENDING_STATES } from 'legacy/state/orders/actions' import { getIsFinalizedOrder } from 'utils/orderUtils/getIsFinalizedOrder' import { getUiOrderType } from 'utils/orderUtils/getUiOrderType' export const isPending = ({ status }: { status: OrderStatus }) => PENDING_STATES.includes(status) +export const isCreating = ({ status }: { status: OrderStatus }) => CREATING_STATES.includes(status) + export function useCategorizeRecentActivity() { // Returns all RECENT (last day) transaction and orders in 2 arrays: pending and confirmed const allRecentActivity = useRecentActivity() diff --git a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts index 9753c76209..4630be0d51 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts @@ -36,7 +36,7 @@ export const CREATING_STATES = [OrderStatus.PRESIGNATURE_PENDING, OrderStatus.CR // - Derived/additional information that is handy for this app // Doesn't have input/output tokens, these are declared in the subtypes of this base type // includes sellAmountBeforeFee as this is required for checking unfillable orders -export interface BaseOrder extends Omit { +export interface BaseOrder extends OrderCreation { id: UID // Unique identifier for the order: 56 bytes encoded as hex without 0x owner: string // Address, without '0x' prefix summary: string // Description of the order, for dapp use only, readable by user @@ -113,6 +113,7 @@ export type OrderInfoApi = Pick< | 'onchainOrderData' | 'class' | 'fullAppData' + | 'signingScheme' > /** diff --git a/apps/cowswap-frontend/src/legacy/state/orders/mocks.ts b/apps/cowswap-frontend/src/legacy/state/orders/mocks.ts index 627b09dce4..1dbedccb25 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/mocks.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/mocks.ts @@ -1,5 +1,5 @@ import { RADIX_DECIMAL } from '@cowprotocol/common-const' -import { OrderClass, OrderKind } from '@cowprotocol/cow-sdk' +import { OrderClass, OrderKind, SigningScheme } from '@cowprotocol/cow-sdk' import { Token } from '@uniswap/sdk-core' import { Order, OrderStatus } from './actions' @@ -60,6 +60,7 @@ export const generateOrder = ({ owner, sellToken, buyToken }: GenerateOrderParam partiallyFillable: false, // hacky typing.. signature: (orderN++).toString().repeat(65 * 2), // 65 bytes encoded as hex without `0x` prefix. v + r + s from the spec + signingScheme: SigningScheme.EIP712, receiver: owner.replace('0x', ''), apiAdditionalInfo: undefined, class: OrderClass.MARKET, diff --git a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts index 6bf4c8cdb1..4e32f45fd1 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts @@ -325,6 +325,8 @@ export default createReducer(initialState, (builder) => isCancelling: isCancelling, class: orderObj.order.class || newOrder.class, // should never replace existing order class openSince: newOrder.openSince || orderObj.order.openSince, + // Necessary since `signingScheme` was added later, and local redux state prior to this change doesn't have it set + signingScheme: newOrder.signingScheme || orderObj.order.signingScheme, status, } : { ...newOrder, validTo } @@ -377,6 +379,7 @@ export default createReducer(initialState, (builder) => onchainOrderData: order.onchainOrderData, class: order.class, fullAppData: order.fullAppData, + signingScheme: order.signingScheme, } addOrderToState(state, chainId, uid, 'fulfilled', orderObject.order, isSafeWallet) diff --git a/apps/cowswap-frontend/src/legacy/utils/trade.ts b/apps/cowswap-frontend/src/legacy/utils/trade.ts index 3365ec43aa..d150682cfa 100644 --- a/apps/cowswap-frontend/src/legacy/utils/trade.ts +++ b/apps/cowswap-frontend/src/legacy/utils/trade.ts @@ -56,6 +56,7 @@ export type UnsignedOrderAdditionalParams = PostOrderParams & { orderId: string summary: string signature: string + signingScheme: SigningScheme isOnChain?: boolean orderCreationHash?: string } @@ -162,6 +163,7 @@ export function mapUnsignedOrderToOrder({ unsignedOrder, additionalParams }: Map allowsOffchainSigning, isOnChain, signature, + signingScheme, sellAmountBeforeFee, orderCreationHash, quoteId, @@ -191,6 +193,7 @@ export function mapUnsignedOrderToOrder({ unsignedOrder, additionalParams }: Map // Signature signature, + signingScheme, // Additional API info apiAdditionalInfo: undefined, @@ -251,7 +254,7 @@ export async function signAndPostOrder(params: PostOrderParams): Promise { fulfilledTransactionHash: '', sellAmountBeforeFee: 10000, signature: '0xsss', + signingScheme: SigningScheme.EIP712, } } diff --git a/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts b/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts index 8355f8c059..fd6dd50f31 100644 --- a/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts +++ b/apps/cowswap-frontend/src/modules/appData/updater/AppDataInfoUpdater.ts @@ -19,6 +19,7 @@ export type UseAppDataParams = { utm: UtmParams | undefined hooks?: AppDataHooks partnerFee?: AppDataPartnerFee + replacedOrderUid?: string } /** @@ -33,6 +34,7 @@ export function AppDataInfoUpdater({ utm, hooks, partnerFee, + replacedOrderUid, }: UseAppDataParams): void { // AppDataInfo, from Jotai const setAppDataInfo = useSetAtom(appDataInfoAtom) @@ -57,6 +59,7 @@ export function AppDataInfoUpdater({ hooks, partnerFee, widget, + replacedOrderUid, } const updateAppData = async (): Promise => { @@ -73,7 +76,17 @@ export function AppDataInfoUpdater({ // Chain the next update to avoid race conditions updateAppDataPromiseRef.current = updateAppDataPromiseRef.current.finally(updateAppData) - }, [appCodeWithWidgetMetadata, chainId, setAppDataInfo, slippageBips, orderClass, utm, hooks, partnerFee]) + }, [ + appCodeWithWidgetMetadata, + chainId, + setAppDataInfo, + slippageBips, + orderClass, + utm, + hooks, + partnerFee, + replacedOrderUid, + ]) } function getEnvByClass(orderClass: string): CowEnv | undefined { diff --git a/apps/cowswap-frontend/src/modules/appData/updater/AppDataUpdater.tsx b/apps/cowswap-frontend/src/modules/appData/updater/AppDataUpdater.tsx index d20fc9a2e8..18bd8de5c2 100644 --- a/apps/cowswap-frontend/src/modules/appData/updater/AppDataUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/appData/updater/AppDataUpdater.tsx @@ -6,6 +6,7 @@ import { Percent } from '@uniswap/sdk-core' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useAppCodeWidgetAware } from 'modules/injectedWidget/hooks/useAppCodeWidgetAware' +import { useReplacedOrderUid } from 'modules/trade/state/alternativeOrder' import { useUtm } from 'modules/utm' import { AppDataHooksUpdater } from './AppDataHooksUpdater' @@ -28,6 +29,7 @@ export const AppDataUpdater = React.memo(({ slippage, orderClass }: AppDataUpdat const hooks = useAppDataHooks() const appCodeWithWidgetMetadata = useAppCodeWidgetAware(appCode) const { partnerFee } = useInjectedWidgetParams() + const replacedOrderUid = useReplacedOrderUid() if (!chainId) return null @@ -40,6 +42,7 @@ export const AppDataUpdater = React.memo(({ slippage, orderClass }: AppDataUpdat utm={utm} hooks={hooks} partnerFee={partnerFee} + replacedOrderUid={replacedOrderUid} /> ) }) diff --git a/apps/cowswap-frontend/src/modules/appData/utils/buildAppData.ts b/apps/cowswap-frontend/src/modules/appData/utils/buildAppData.ts index c2a685e634..251182725f 100644 --- a/apps/cowswap-frontend/src/modules/appData/utils/buildAppData.ts +++ b/apps/cowswap-frontend/src/modules/appData/utils/buildAppData.ts @@ -26,6 +26,7 @@ export type BuildAppDataParams = { hooks?: AppDataHooks widget?: AppDataWidget partnerFee?: AppDataPartnerFee + replacedOrderUid?: string } async function generateAppDataFromDoc( @@ -47,17 +48,28 @@ export async function buildAppData({ hooks, widget, partnerFee, + replacedOrderUid, }: BuildAppDataParams): Promise { const referrerParams = referrerAccount && chainId === SupportedChainId.MAINNET ? { address: referrerAccount } : undefined const quoteParams = { slippageBips } const orderClass = { orderClass: orderClassName } + const replacedOrder = replacedOrderUid ? { uid: replacedOrderUid } : undefined const doc = await metadataApiSDK.generateAppDataDoc({ appCode, environment, - metadata: { referrer: referrerParams, quote: quoteParams, orderClass, utm, hooks, widget, partnerFee }, + metadata: { + referrer: referrerParams, + quote: quoteParams, + orderClass, + utm, + hooks, + widget, + partnerFee, + ...{ replacedOrder }, + }, }) const { fullAppData, appDataKeccak256 } = await generateAppDataFromDoc(doc) diff --git a/apps/cowswap-frontend/src/modules/appData/utils/fullAppData.ts b/apps/cowswap-frontend/src/modules/appData/utils/fullAppData.ts index 7d0ba34493..50a3bde8d3 100644 --- a/apps/cowswap-frontend/src/modules/appData/utils/fullAppData.ts +++ b/apps/cowswap-frontend/src/modules/appData/utils/fullAppData.ts @@ -3,7 +3,7 @@ import { EnvironmentName, environmentName } from '@cowprotocol/common-utils' import { AppDataInfo } from '../types' import { toKeccak256 } from '../utils/buildAppData' -const DEFAULT_FULL_APP_DATA = '{"version":"1.0.0","appCode":"CoW Swap","metadata":{}}' +const DEFAULT_FULL_APP_DATA = '{"version":"1.1.0","appCode":"CoW Swap","metadata":{}}' let appData: AppDataInfo = (() => { const fullAppData = getFullAppDataByEnv(environmentName) diff --git a/apps/cowswap-frontend/src/modules/limitOrders/hooks/useUpdateActiveRate.ts b/apps/cowswap-frontend/src/modules/limitOrders/hooks/useUpdateActiveRate.ts index 99109c686b..1580a15665 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/hooks/useUpdateActiveRate.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/hooks/useUpdateActiveRate.ts @@ -21,7 +21,7 @@ export function useUpdateActiveRate(): UpdateRateCallback { const updateCurrencyAmount = useUpdateCurrencyAmount() const updateRateState = useSetAtom(updateLimitRateAtom) - const { isRateFromUrl: currentIsRateFromUrl, isAlternativeOrderRate: currentIsAlternativeOrderRate } = rateState + const { isRateFromUrl: currentIsRateFromUrl } = rateState return useCallback( (update: RateUpdateParams) => { @@ -34,7 +34,7 @@ export function useUpdateActiveRate(): UpdateRateCallback { if (activeRate) { // Don't update amounts when rate is set from URL. See useSetupLimitOrderAmountsFromUrl() // Don't update amounts when rate is set from AlternativeOrder. See AlternativeLimitOrderUpdater - if (currentIsRateFromUrl || isRateFromUrl || currentIsAlternativeOrderRate || isAlternativeOrderRate) { + if (currentIsRateFromUrl || isRateFromUrl || isAlternativeOrderRate) { return } @@ -58,7 +58,6 @@ export function useUpdateActiveRate(): UpdateRateCallback { updateRateState, orderKind, currentIsRateFromUrl, - currentIsAlternativeOrderRate, updateCurrencyAmount, inputCurrencyAmount, outputCurrencyAmount, diff --git a/apps/cowswap-frontend/src/modules/limitOrders/updaters/AlternativeLimitOrderUpdater.ts b/apps/cowswap-frontend/src/modules/limitOrders/updaters/AlternativeLimitOrderUpdater.ts index a162278f49..48d8d0dfb1 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/updaters/AlternativeLimitOrderUpdater.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/updaters/AlternativeLimitOrderUpdater.ts @@ -39,7 +39,7 @@ export function AlternativeLimitOrderUpdater(): null { } function useUpdateAlternativeRawState(): null { - const alternativeOrder = useAlternativeOrder() + const { order: alternativeOrder } = useAlternativeOrder() || {} const updateRawState = useUpdateLimitOrdersRawState() const updatePartialFillOverride = useSetAtom(partiallyFillableOverrideAtom) const updateSettingsState = useSetAtom(updateLimitOrdersSettingsAtom) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/hooks.ts b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/hooks.ts index 21d4faf170..f5077ec2d8 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/hooks.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/hooks.ts @@ -3,11 +3,11 @@ import { useCallback, useMemo } from 'react' import { Command, UiOrderType } from '@cowprotocol/types' -import { useSetAlternativeOrder } from 'modules/trade/state/alternativeOrder' +import { IS_EDIT_ORDER_ENABLED, useSetAlternativeOrder } from 'modules/trade/state/alternativeOrder' -import { isPending } from 'common/hooks/useCategorizeRecentActivity' +import { isCreating, isPending } from 'common/hooks/useCategorizeRecentActivity' import { getUiOrderType } from 'utils/orderUtils/getUiOrderType' -import { ParsedOrder } from 'utils/orderUtils/parseOrder' +import { isOffchainOrder, ParsedOrder } from 'utils/orderUtils/parseOrder' import { receiptAtom, updateReceiptAtom } from '../../state/orderReceiptAtom' @@ -27,14 +27,40 @@ export function useSelectedOrder(): ParsedOrder | null { return order } -export function useGetShowRecreateModal(order: ParsedOrder | null): Command | null { - const setOrderToRecreate = useSetAlternativeOrder() +export type AlternativeOrderModalContext = { showAlternativeOrderModal: Command; isEdit: boolean } | null - return useMemo( - () => - !order || isPending(order) || getUiOrderType(order) !== UiOrderType.LIMIT - ? null - : () => setOrderToRecreate(order), - [order, setOrderToRecreate] +export function useGetAlternativeOrderModalContext(order: ParsedOrder | null): AlternativeOrderModalContext { + const callback = useGetAlternativeOrderModalContextCallback() + + return useMemo(() => callback(order), [callback, order]) +} + +export function useGetAlternativeOrderModalContextCallback(): ( + order: ParsedOrder | null +) => AlternativeOrderModalContext { + const setAlternativeOrder = useSetAlternativeOrder() + + return useCallback( + (order: ParsedOrder | null) => getAlternativeOrderModalContext(order, setAlternativeOrder), + [setAlternativeOrder] ) } + +function getAlternativeOrderModalContext( + order: ParsedOrder | null, + setAlternativeOrder: ReturnType +): AlternativeOrderModalContext { + const isEdit = order && isPending(order) + + if ( + !order || + isCreating(order) || + getUiOrderType(order) !== UiOrderType.LIMIT || + isEdit === null || + (isEdit && (!isOffchainOrder(order) || !IS_EDIT_ORDER_ENABLED)) + ) { + return null + } else { + return { showAlternativeOrderModal: () => setAlternativeOrder(order, isEdit), isEdit } + } +} diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/index.tsx index 98a186ee9c..44e43a7832 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersReceiptModal/index.tsx @@ -9,7 +9,7 @@ import { useTwapOrderByChildId, useTwapOrderById } from 'modules/twap' import { calculatePrice } from 'utils/orderUtils/calculatePrice' -import { useCloseReceiptModal, useGetShowRecreateModal, useSelectedOrder } from './hooks' +import { useCloseReceiptModal, useGetAlternativeOrderModalContext, useSelectedOrder } from './hooks' import { ReceiptModal } from '../../pure/ReceiptModal' @@ -29,7 +29,7 @@ export function OrdersReceiptModal(props: OrdersReceiptModalProps) { const twapOrder = twapOrderById || twapOrderByChildId const isTwapPartOrder = !!twapOrderByChildId - const showRecreateModal = useGetShowRecreateModal(order) + const alternativeOrderModalContext = useGetAlternativeOrderModalContext(order) if (!chainId || !order) { return null @@ -76,7 +76,7 @@ export function OrdersReceiptModal(props: OrdersReceiptModalProps) { isTwapPartOrder={isTwapPartOrder} isOpen={!!order} onDismiss={closeReceiptModal} - showRecreateModal={showRecreateModal} + alternativeOrderModalContext={alternativeOrderModalContext} /> ) } diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx index 1827547bd8..24375a1f00 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx @@ -2,7 +2,6 @@ import { useAtomValue, useSetAtom } from 'jotai' import { useCallback, useEffect, useMemo } from 'react' import { useTokensAllowances, useTokensBalances } from '@cowprotocol/balances-and-allowances' -import { UiOrderType } from '@cowprotocol/types' import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' import { useLocation, useNavigate } from 'react-router-dom' @@ -13,13 +12,11 @@ import { Order } from 'legacy/state/orders/actions' import { pendingOrdersPricesAtom } from 'modules/orders/state/pendingOrdersPricesAtom' import { useGetSpotPrice } from 'modules/orders/state/spotPricesAtom' import { PendingPermitUpdater, useGetOrdersPermitStatus } from 'modules/permit' -import { useSetAlternativeOrder } from 'modules/trade/state/alternativeOrder' import { useCancelOrder } from 'common/hooks/useCancelOrder' -import { isPending, useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' +import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' import { ordersToCancelAtom, updateOrdersToCancelAtom } from 'common/hooks/useMultipleOrdersCancellation/state' import { CancellableOrder } from 'common/utils/isOrderCancellable' -import { getUiOrderType } from 'utils/orderUtils/getUiOrderType' import { ParsedOrder } from 'utils/orderUtils/parseOrder' import { useGetOrdersToCheckPendingPermit } from './hooks/useGetOrdersToCheckPendingPermit' @@ -37,7 +34,7 @@ import { OrderTableItem, tableItemsToOrders } from '../../utils/orderTableGroupU import { parseOrdersTableUrl } from '../../utils/parseOrdersTableUrl' import { MultipleCancellationMenu } from '../MultipleCancellationMenu' import { OrdersReceiptModal } from '../OrdersReceiptModal' -import { useSelectReceiptOrder } from '../OrdersReceiptModal/hooks' +import { useGetAlternativeOrderModalContextCallback, useSelectReceiptOrder } from '../OrdersReceiptModal/hooks' function getOrdersListByIndex(ordersList: OrdersTableList, id: string): OrderTableItem[] { return id === OPEN_TAB.id ? ordersList.pending : ordersList.history @@ -141,22 +138,13 @@ export function OrdersTableWidget({ [allOrders, cancelOrder] ) - const setOrderToRecreate = useSetAlternativeOrder() - const getShowRecreateModal = useCallback( - (order: ParsedOrder) => { - if (isPending(order) || getUiOrderType(order) !== UiOrderType.LIMIT) { - return null - } - return () => setOrderToRecreate(order) - }, - [setOrderToRecreate] - ) + const getAlternativeOrderModalContext = useGetAlternativeOrderModalContextCallback() const approveOrderToken = useOrdersTableTokenApprove() const orderActions: OrderActions = { getShowCancellationModal, - getShowRecreateModal, + getAlternativeOrderModalContext, selectReceiptOrder, toggleOrderForCancellation, toggleOrdersForCancellation, diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/OrderContextMenu.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/OrderContextMenu.tsx index 736922eed6..8a2400accb 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/OrderContextMenu.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/OrderContextMenu.tsx @@ -3,9 +3,11 @@ import { UI } from '@cowprotocol/ui' import { Menu, MenuButton, MenuItem, MenuList } from '@reach/menu-button' import { transparentize } from 'color2k' -import { FileText, Link2, MoreVertical, Repeat, Trash2 } from 'react-feather' +import { Edit, FileText, Link2, MoreVertical, Repeat, Trash2 } from 'react-feather' import styled from 'styled-components/macro' +import { AlternativeOrderModalContext } from '../../../containers/OrdersReceiptModal/hooks' + export const ContextMenuButton = styled(MenuButton)` background: none; border: 0; @@ -69,14 +71,14 @@ export interface OrderContextMenuProps { openReceipt: Command activityUrl: string | undefined showCancellationModal: Command | null - showRecreateModal: Command | null + alternativeOrderModalContext: AlternativeOrderModalContext } export function OrderContextMenu({ openReceipt, activityUrl, showCancellationModal, - showRecreateModal, + alternativeOrderModalContext, }: OrderContextMenuProps) { return ( @@ -94,18 +96,18 @@ export function OrderContextMenu({ View on explorer )} + {alternativeOrderModalContext && ( + + {alternativeOrderModalContext.isEdit ? : } + {alternativeOrderModalContext.isEdit ? 'Edit' : 'Recreate'} order + + )} {showCancellationModal && ( Cancel order )} - {showRecreateModal && ( - - - Recreate order - - )} ) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx index e9d4652987..2f52c60854 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx @@ -7,7 +7,7 @@ import { getAddress, getEtherscanLink } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { TokenLogo } from '@cowprotocol/tokens' import { Command, UiOrderType } from '@cowprotocol/types' -import { Loader, TokenAmount, TokenSymbol, UI, ButtonSecondary } from '@cowprotocol/ui' +import { ButtonSecondary, Loader, TokenAmount, TokenSymbol, UI } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' import SVG from 'react-inlinesvg' @@ -170,10 +170,10 @@ export function OrderRow({ const showCancellationModal = useMemo(() => { return orderActions.getShowCancellationModal(order) }, [orderActions, order]) - - const showRecreateModal = useMemo(() => { - return orderActions.getShowRecreateModal(order) - }, [orderActions, order]) + const alternativeOrderModalContext = useMemo( + () => orderActions.getAlternativeOrderModalContext(order), + [order, orderActions] + ) const withAllowanceWarning = hasEnoughAllowance === false && hasValidPendingPermit === false const withWarning = @@ -384,7 +384,7 @@ export function OrderRow({ activityUrl={activityUrl} openReceipt={onClick} showCancellationModal={showCancellationModal} - showRecreateModal={showRecreateModal} + alternativeOrderModalContext={alternativeOrderModalContext} /> diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx index 0850d19048..5333477424 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx @@ -1,5 +1,3 @@ -import { Command } from '@cowprotocol/types' - import { BalancesAndAllowances } from 'modules/tokens' import { ParsedOrder } from 'utils/orderUtils/parseOrder' @@ -52,7 +50,7 @@ const orderActions: OrderActions = { approveOrderToken() { console.log('approveOrderToken ') }, - getShowRecreateModal: function (_: ParsedOrder): Command | null { + getAlternativeOrderModalContext: function (_: ParsedOrder): null { console.log(`getShowRecreateModal`) return null }, diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts index b36e537612..68638447dc 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts @@ -1,5 +1,5 @@ import { COW, DAI, GNO, USDC, WETH_GNOSIS_CHAIN } from '@cowprotocol/common-const' -import { OrderClass, OrderKind } from '@cowprotocol/cow-sdk' +import { OrderClass, OrderKind, SigningScheme } from '@cowprotocol/cow-sdk' import { OrderStatus } from 'legacy/state/orders/actions' @@ -28,6 +28,7 @@ export const ordersMock: ParsedOrder[] = [ appData: '', partiallyFillable: false, signature: '', + signingScheme: SigningScheme.EIP712, class: OrderClass.MARKET, kind: OrderKind.SELL, }, @@ -50,6 +51,7 @@ export const ordersMock: ParsedOrder[] = [ appData: '', partiallyFillable: false, signature: '', + signingScheme: SigningScheme.EIP712, class: OrderClass.MARKET, kind: OrderKind.SELL, }, @@ -72,6 +74,7 @@ export const ordersMock: ParsedOrder[] = [ appData: '', partiallyFillable: false, signature: '', + signingScheme: SigningScheme.EIP712, class: OrderClass.MARKET, kind: OrderKind.SELL, }, @@ -94,6 +97,7 @@ export const ordersMock: ParsedOrder[] = [ appData: '', partiallyFillable: false, signature: '', + signingScheme: SigningScheme.EIP712, class: OrderClass.MARKET, kind: OrderKind.SELL, }, @@ -116,6 +120,7 @@ export const ordersMock: ParsedOrder[] = [ appData: '', partiallyFillable: false, signature: '', + signingScheme: SigningScheme.EIP712, class: OrderClass.MARKET, kind: OrderKind.SELL, }, diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/types.ts b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/types.ts index d11738effd..c362d69a3a 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/types.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/types.ts @@ -1,12 +1,13 @@ -import { Command } from '@cowprotocol/types' import { Token } from '@uniswap/sdk-core' import { UseCancelOrderReturn } from 'common/hooks/useCancelOrder' import { ParsedOrder } from 'utils/orderUtils/parseOrder' +import { AlternativeOrderModalContext } from '../../containers/OrdersReceiptModal/hooks' + export interface OrderActions { getShowCancellationModal: (order: ParsedOrder) => UseCancelOrderReturn - getShowRecreateModal: (order: ParsedOrder) => Command | null + getAlternativeOrderModalContext: (order: ParsedOrder) => AlternativeOrderModalContext selectReceiptOrder(order: ParsedOrder): void toggleOrderForCancellation(order: ParsedOrder): void toggleOrdersForCancellation(orders: ParsedOrder[]): void diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx index a2ed1b420b..49889f9f53 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx @@ -2,13 +2,13 @@ import { ExplorerDataType, getExplorerLink, isSellOrder, shortenAddress } from ' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' import { - UI, + BannerOrientation, + CustomRecipientWarningBanner, + ExternalLink, Icon, IconType, - ExternalLink, InlineBanner, - BannerOrientation, - CustomRecipientWarningBanner, + UI, } from '@cowprotocol/ui' import { CurrencyAmount, Fraction, Token } from '@uniswap/sdk-core' @@ -39,6 +39,8 @@ import { StatusField } from './StatusField' import * as styledEl from './styled' import { SurplusField } from './SurplusField' +import { AlternativeOrderModalContext } from '../../containers/OrdersReceiptModal/hooks' + interface ReceiptProps { isOpen: boolean order: ParsedOrder @@ -52,7 +54,7 @@ interface ReceiptProps { limitPrice: Fraction | null executionPrice: Fraction | null estimatedExecutionPrice: Fraction | null - showRecreateModal: Command | null + alternativeOrderModalContext: AlternativeOrderModalContext } const FILLED_COMMON_TOOLTIP = 'How much of the order has been filled.' @@ -105,7 +107,7 @@ export function ReceiptModal({ executionPrice, estimatedExecutionPrice, receiverEnsName, - showRecreateModal, + alternativeOrderModalContext, }: ReceiptProps) { // Check if Custom Recipient Warning Banner should be visible const isCustomRecipientWarningBannerVisible = !useIsReceiverWalletBannerHidden(order.id) @@ -133,8 +135,10 @@ export function ReceiptModal({
Order Receipt - {showRecreateModal && ( - Recreate this order + {alternativeOrderModalContext && ( + + {alternativeOrderModalContext.isEdit ? 'Edit' : 'Recreate'} this order + )}
onDismiss()} /> diff --git a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts index dc028c8148..6167bc9b1c 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts @@ -1,6 +1,6 @@ import { CoWSwapEthFlow } from '@cowprotocol/abis' import { calculateGasMargin } from '@cowprotocol/common-utils' -import { OrderClass, UnsignedOrder } from '@cowprotocol/cow-sdk' +import { OrderClass, SigningScheme, UnsignedOrder } from '@cowprotocol/cow-sdk' import { ContractTransaction } from '@ethersproject/contracts' import { NativeCurrency } from '@uniswap/sdk-core' @@ -82,6 +82,7 @@ export async function signEthFlowOrderStep( class: OrderClass.MARKET, orderId, signature: '', + signingScheme: SigningScheme.EIP1271, summary, quoteId, orderCreationHash: txReceipt.hash, diff --git a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/atoms.ts b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/atoms.ts index da9981a2b8..603399c5f8 100644 --- a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/atoms.ts +++ b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/atoms.ts @@ -4,11 +4,16 @@ import { Order } from 'legacy/state/orders/actions' import { ParsedOrder } from 'utils/orderUtils/parseOrder' +export type AlternativeOrder = { + order: Order | ParsedOrder + isEdit: boolean +} | null + /** * Main atom to control the alternative order modal/form * When it's set, the alternative flow should be displayed */ -export const alternativeOrderAtom = atom(null) +export const alternativeOrderAtom = atom(null) /** * Derived atom that controls the alternative modal visibility diff --git a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/const.ts b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/const.ts new file mode 100644 index 0000000000..385e65724a --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/const.ts @@ -0,0 +1,5 @@ +/** + * Whether Edit Order feature is enabled + * Disabled for now until backend has the capacity to address issues raised on https://www.notion.so/cownation/Canceling-Replace-Orders-Edge-Case-5f1d0d38e3f541e7b454f8e7faa7fee1?pvs=4 + */ +export const IS_EDIT_ORDER_ENABLED = false diff --git a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/hooks.ts b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/hooks.ts index 2788bbf359..413bbfe6db 100644 --- a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/hooks.ts +++ b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/hooks.ts @@ -24,5 +24,17 @@ export function useAlternativeOrder() { export function useSetAlternativeOrder() { const setAlternativeOrder = useSetAtom(alternativeOrderAtom) - return useCallback((order: Order | ParsedOrder) => setAlternativeOrder(order), [setAlternativeOrder]) + return useCallback( + (order: Order | ParsedOrder, isEdit = false) => setAlternativeOrder({ order, isEdit }), + [setAlternativeOrder] + ) +} + +/** + * Returns the id of the order being edited, if it's an edit + */ +export function useReplacedOrderUid() { + const { order, isEdit } = useAlternativeOrder() || {} + + return isEdit ? order?.id : undefined } diff --git a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/index.ts b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/index.ts index 96df2d7471..a995e401f0 100644 --- a/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/index.ts +++ b/apps/cowswap-frontend/src/modules/trade/state/alternativeOrder/index.ts @@ -1,2 +1,3 @@ export * from './atomFactories' +export * from './const' export * from './hooks' diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/__snapshots__/useTradeQuotePolling.test.tsx.snap b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/__snapshots__/useTradeQuotePolling.test.tsx.snap index 6d597be4e1..c085ce13db 100644 --- a/apps/cowswap-frontend/src/modules/tradeQuote/hooks/__snapshots__/useTradeQuotePolling.test.tsx.snap +++ b/apps/cowswap-frontend/src/modules/tradeQuote/hooks/__snapshots__/useTradeQuotePolling.test.tsx.snap @@ -2,8 +2,8 @@ exports[`useTradeQuotePolling() When wallet is NOT connected Then the "useAddress" field in the quote request should be 0x000...0000 1`] = ` { - "appData": "{"version":"1.0.0","appCode":"CoW Swap","metadata":{}}", - "appDataHash": "0xed05aa46a959996a53c8a35b28b0f326a7bde9a3fd45a0836f6c2a17aff007a3", + "appData": "{"version":"1.1.0","appCode":"CoW Swap","metadata":{}}", + "appDataHash": "0x3f13237929334d44bdb32b189d1644965915936e62004fb77ec7bc861cddc7a4", "buyToken": "0x0625aFB445C3B6B7B929342a04A22599fd5dBB59", "from": "0x0000000000000000000000000000000000000000", "kind": "sell", @@ -18,8 +18,8 @@ exports[`useTradeQuotePolling() When wallet is NOT connected Then the "useAddres exports[`useTradeQuotePolling() When wallet is connected Then should put account address into "useAddress" field in the quote request 1`] = ` { - "appData": "{"version":"1.0.0","appCode":"CoW Swap","metadata":{}}", - "appDataHash": "0xed05aa46a959996a53c8a35b28b0f326a7bde9a3fd45a0836f6c2a17aff007a3", + "appData": "{"version":"1.1.0","appCode":"CoW Swap","metadata":{}}", + "appDataHash": "0x3f13237929334d44bdb32b189d1644965915936e62004fb77ec7bc861cddc7a4", "buyToken": "0x0625aFB445C3B6B7B929342a04A22599fd5dBB59", "from": "0x333333f332a06ecb5d20d35da44ba07986d6e203", "kind": "sell", diff --git a/apps/cowswap-frontend/src/modules/twap/services/__snapshots__/createTwapOrderTxs.test.ts.snap b/apps/cowswap-frontend/src/modules/twap/services/__snapshots__/createTwapOrderTxs.test.ts.snap index a806d7126c..564f879ad0 100644 --- a/apps/cowswap-frontend/src/modules/twap/services/__snapshots__/createTwapOrderTxs.test.ts.snap +++ b/apps/cowswap-frontend/src/modules/twap/services/__snapshots__/createTwapOrderTxs.test.ts.snap @@ -7,7 +7,7 @@ exports[`Create TWAP order When sell token is NOT approved AND token needs zero { "handler": "0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5", "salt": "0x00000000000000000000000000000000000000000000000000000015c90b9b2a", - "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000000ed05aa46a959996a53c8a35b28b0f326a7bde9a3fd45a0836f6c2a17aff007a3", + "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000000003f13237929334d44bdb32b189d1644965915936e62004fb77ec7bc861cddc7a4", }, "0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc", "0x", @@ -66,7 +66,7 @@ exports[`Create TWAP order When sell token is NOT approved, then should generate { "handler": "0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5", "salt": "0x00000000000000000000000000000000000000000000000000000015c90b9b2a", - "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000000ed05aa46a959996a53c8a35b28b0f326a7bde9a3fd45a0836f6c2a17aff007a3", + "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000000003f13237929334d44bdb32b189d1644965915936e62004fb77ec7bc861cddc7a4", }, "0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc", "0x", @@ -109,7 +109,7 @@ exports[`Create TWAP order When sell token is approved, then should generate onl { "handler": "0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5", "salt": "0x00000000000000000000000000000000000000000000000000000015c90b9b2a", - "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000000ed05aa46a959996a53c8a35b28b0f326a7bde9a3fd45a0836f6c2a17aff007a3", + "staticInput": "0x0000000000000000000000000625afb445c3b6b7b929342a04a22599fd5dbb59000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000000003f13237929334d44bdb32b189d1644965915936e62004fb77ec7bc861cddc7a4", }, "0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc", "0x", diff --git a/apps/cowswap-frontend/src/pages/LimitOrders/AlternativeLimitOrder.tsx b/apps/cowswap-frontend/src/pages/LimitOrders/AlternativeLimitOrder.tsx index 423c2db4a5..765cb21729 100644 --- a/apps/cowswap-frontend/src/pages/LimitOrders/AlternativeLimitOrder.tsx +++ b/apps/cowswap-frontend/src/pages/LimitOrders/AlternativeLimitOrder.tsx @@ -1,9 +1,7 @@ -import { useCallback } from 'react' - import styled from 'styled-components/macro' import { LimitOrdersWidget } from 'modules/limitOrders' -import { useHideAlternativeOrderModal } from 'modules/trade/state/alternativeOrder' +import { useAlternativeOrder, useHideAlternativeOrderModal } from 'modules/trade/state/alternativeOrder' import { NewModal } from 'common/pure/NewModal' @@ -18,12 +16,17 @@ const Wrapper = styled.div` export function AlternativeLimitOrder() { const hideAlternativeOrderModal = useHideAlternativeOrderModal() + const { isEdit } = useAlternativeOrder() || {} + + if (isEdit === undefined) { + return null + } - const onDismiss = useCallback(() => hideAlternativeOrderModal(), [hideAlternativeOrderModal]) + const title = `${isEdit ? 'Edit' : 'Recreate'} limit order` return ( - + diff --git a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts index a18121cfe9..bab888520c 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts @@ -1,4 +1,4 @@ -import { OrderClass, OrderKind } from '@cowprotocol/cow-sdk' +import { OrderClass, OrderKind, SigningScheme } from '@cowprotocol/cow-sdk' import { Currency, CurrencyAmount, Price, Token } from '@uniswap/sdk-core' import BigNumber from 'bignumber.js' @@ -49,6 +49,7 @@ export interface ParsedOrder { expirationTime: Date composableCowInfo?: ComposableCowInfo fullAppData: Order['fullAppData'] + signingScheme: SigningScheme executionData: ParsedOrderExecutionData } @@ -113,9 +114,14 @@ export const parseOrder = (order: Order): ParsedOrder => { expirationTime, fullAppData: order.fullAppData, executionData, + signingScheme: order.signingScheme, } } export function isParsedOrder(order: Order | ParsedOrder): order is ParsedOrder { return !!(order as ParsedOrder).executionData } + +export function isOffchainOrder(order: Order | ParsedOrder): boolean { + return order.signingScheme === SigningScheme.EIP712 +} diff --git a/package.json b/package.json index 198c631724..091962554d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@apollo/client": "^3.1.5", "@babel/runtime": "^7.17.0", "@coinbase/wallet-sdk": "^3.3.0", - "@cowprotocol/app-data": "^2.0.2", + "@cowprotocol/app-data": "^2.1.0", "@cowprotocol/contracts": "^1.3.1", "@cowprotocol/cow-runner-game": "^0.2.9", "@cowprotocol/cow-sdk": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 9ea6eb2f56..7397f4f98c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2267,10 +2267,10 @@ dependencies: chalk "^4.1.0" -"@cowprotocol/app-data@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@cowprotocol/app-data/-/app-data-2.0.2.tgz#0f8f8b24cef95f5bdce462cf6982a53345066a16" - integrity sha512-NTfFp1DYVXn+cNwKIbBj990bcUHVKkTZ+i9imqUMxyoKv9WTUt1oi+XTGRqPJG3jRUgGSx6IyY47qeUvFtcA2w== +"@cowprotocol/app-data@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@cowprotocol/app-data/-/app-data-2.1.0.tgz#55c95e7ffd3fb0dcfecd0fc64a4273955d1d63d1" + integrity sha512-gOlQxng7X5/aQoz2Eg27OegyKgpZVhOEdUQ9cUsc4OnSPal8F4tmLqefKQQWm4Ktaolk7zzh0kiL1vhWxvszmQ== dependencies: ajv "^8.11.0" cross-fetch "^3.1.5" From 9208b370a6831fe6de438609cc54a9e7e8c43126 Mon Sep 17 00:00:00 2001 From: fairlight <31534717+fairlighteth@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:19:41 +0000 Subject: [PATCH 5/9] Trade mode responsive styling (#4079) * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress * feat: progress --- .../ConfirmationPendingContent/styled.tsx | 2 +- apps/cowswap-frontend/src/cow-react/index.tsx | 5 +- .../legacy/components/AppziButton/index.tsx | 56 ++++++--- .../components/CowBalanceButton/index.tsx | 35 +++--- .../Header/AccountElement/index.tsx | 5 +- .../Header/MobileMenuIcon/index.tsx | 8 +- .../Header/NetworkSelector/index.tsx | 49 ++++---- .../src/legacy/components/Header/index.tsx | 35 ++++-- .../src/legacy/components/Header/styled.tsx | 73 +++++------- .../components/NetworkAlert/NetworkAlert.tsx | 7 ++ .../src/legacy/hooks/useMediaQuery.ts | 2 + .../src/legacy/theme/cowSwapAssets.ts | 12 ++ .../src/legacy/theme/index.tsx | 2 +- .../src/legacy/theme/styled.d.ts | 6 +- .../modules/application/pure/Page/index.tsx | 2 +- .../containers/FortuneWidget/index.tsx | 107 +++++++++++++----- .../modules/mainMenu/pure/MenuTree/index.tsx | 34 ++++-- .../tokensList/pure/ModalHeader/index.tsx | 11 +- .../TradeWidget/TradeWidgetForm.tsx | 4 +- .../modules/wallet/pure/StatusIcon/index.tsx | 3 - .../wallet/pure/Web3StatusInner/index.tsx | 6 +- .../wallet/pure/Web3StatusInner/styled.ts | 13 +-- .../MenuDropdown/MobileMenuIcon/index.tsx | 1 + 23 files changed, 298 insertions(+), 180 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/ConfirmationPendingContent/styled.tsx b/apps/cowswap-frontend/src/common/pure/ConfirmationPendingContent/styled.tsx index 5e720bdef8..bae6e1c696 100644 --- a/apps/cowswap-frontend/src/common/pure/ConfirmationPendingContent/styled.tsx +++ b/apps/cowswap-frontend/src/common/pure/ConfirmationPendingContent/styled.tsx @@ -207,7 +207,7 @@ export const StepsWrapper = styled.div` max-width: 25%; `} - ${({ theme }) => theme.mediaWidth.upToVerySmall` + ${({ theme }) => theme.mediaWidth.upToExtraSmall` max-width: 20%; `} } diff --git a/apps/cowswap-frontend/src/cow-react/index.tsx b/apps/cowswap-frontend/src/cow-react/index.tsx index c6db0749bf..4b5f083fed 100644 --- a/apps/cowswap-frontend/src/cow-react/index.tsx +++ b/apps/cowswap-frontend/src/cow-react/index.tsx @@ -20,6 +20,7 @@ import * as serviceWorkerRegistration from 'serviceWorkerRegistration' import AppziButton from 'legacy/components/AppziButton' import Web3Provider from 'legacy/components/Web3Provider' +import { upToMedium, useMediaQuery } from 'legacy/hooks/useMediaQuery' import { cowSwapStore } from 'legacy/state' import ThemeProvider, { FixedGlobalStyle, ThemedGlobalStyle } from 'legacy/theme' @@ -51,6 +52,8 @@ function Main() { } }, []) + const isUpToMedium = useMediaQuery(upToMedium) + return ( @@ -66,7 +69,7 @@ function Main() { - {!isInjectedWidgetMode && ( + {!isInjectedWidgetMode && !isUpToMedium && ( <> diff --git a/apps/cowswap-frontend/src/legacy/components/AppziButton/index.tsx b/apps/cowswap-frontend/src/legacy/components/AppziButton/index.tsx index b1b32d09c3..4772f7ff78 100644 --- a/apps/cowswap-frontend/src/legacy/components/AppziButton/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/AppziButton/index.tsx @@ -26,7 +26,23 @@ const Wrapper = styled.div` transition: background 0.5s ease-in-out, transform 0.5s ease-in-out; ${({ theme }) => theme.mediaWidth.upToMedium` - background: ${({ theme }) => transparentize(theme.bg2, 0.1)}; + left: initial; + bottom: initial; + position: relative; + width: 100%; + right: initial; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 0px; + margin: 0px; + font-weight: 600; + font-size: 17px; + padding: 15px 10px; + color: inherit; + border-bottom: 1px solid var(--cow-color-text-opacity-10); + height: auto; + background: none; `}; > svg { @@ -40,6 +56,12 @@ const Wrapper = styled.div` ${({ theme }) => theme.mediaWidth.upToMedium` fill: ${({ theme }) => theme.white}; + background: ${({ theme }) => transparentize(theme.bg2, 0.1)}; + --size: 46px; + height: var(--size); + width: var(--size); + transform: none; + border-radius: calc(var(--size) / 3); `}; } @@ -47,31 +69,37 @@ const Wrapper = styled.div` background: ${({ theme }) => transparentize(theme.bg2, 0.1)}; transform: translateY(-3px); + ${({ theme }) => theme.mediaWidth.upToMedium` + background: none; + transform: none; + `}; + > svg { fill: ${({ theme }) => theme.white}; transform: rotate(-360deg); + + ${({ theme }) => theme.mediaWidth.upToMedium` + transform: none; + `}; } } - - ${({ theme }) => theme.mediaWidth.upToMedium` - left: 14px; - height: 38px; - width: 38px; - bottom: 11px; - right: initial; - z-index: 10; - box-shadow: none; - border-width: 3px; - `}; ` -export default function Appzi() { +interface AppziButtonProps { + menuTitle?: string + onClick?: () => void + isUpToMedium?: boolean +} + +export default function Appzi({ menuTitle, onClick, isUpToMedium }: AppziButtonProps) { if (!isAppziEnabled) { return null } return ( - + // + + {menuTitle && {menuTitle}} ) diff --git a/apps/cowswap-frontend/src/legacy/components/CowBalanceButton/index.tsx b/apps/cowswap-frontend/src/legacy/components/CowBalanceButton/index.tsx index a329fd3db5..b0e306a22a 100644 --- a/apps/cowswap-frontend/src/legacy/components/CowBalanceButton/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/CowBalanceButton/index.tsx @@ -25,14 +25,15 @@ export const Wrapper = styled.div<{ isLoading: boolean }>` transition: width var(${UI.ANIMATION_DURATION}) ease-in-out, border var(${UI.ANIMATION_DURATION}) ease-in-out; cursor: pointer; - ${({ theme }) => theme.mediaWidth.upToMedium` - height: 100%; - width: auto; - padding: 6px 12px 6px 8px; + ${({ theme }) => theme.mediaWidth.upToLarge` + position: absolute; + z-index: 1001; + right: 76px; + background: var(${UI.COLOR_PAPER_DARKER}); `}; ${({ theme }) => theme.mediaWidth.upToSmall` - padding: 6px 8px; + right: 66px; `}; &:hover { @@ -77,26 +78,24 @@ interface CowBalanceButtonProps { account?: string | null | undefined chainId: ChainId | undefined onClick?: Command - isUpToSmall?: boolean } -export default function CowBalanceButton({ onClick, isUpToSmall }: CowBalanceButtonProps) { +export default function CowBalanceButton({ onClick }: CowBalanceButtonProps) { const { balance, isLoading } = useCombinedBalance() return ( - {!isUpToSmall && ( - - - - )} + + + + ) } diff --git a/apps/cowswap-frontend/src/legacy/components/Header/AccountElement/index.tsx b/apps/cowswap-frontend/src/legacy/components/Header/AccountElement/index.tsx index 5949942c70..0efc267d2c 100644 --- a/apps/cowswap-frontend/src/legacy/components/Header/AccountElement/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Header/AccountElement/index.tsx @@ -5,6 +5,8 @@ import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' import { TokenAmount } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' +import { upToLarge, useMediaQuery } from 'legacy/hooks/useMediaQuery' + import { useToggleAccountModal } from 'modules/account' import { Web3Status } from 'modules/wallet/containers/Web3Status' @@ -24,10 +26,11 @@ export function AccountElement({ className, standaloneMode, pendingActivities }: const userEthBalance = useNativeCurrencyAmount(chainId, account) const toggleAccountModal = useToggleAccountModal() const nativeToken = NATIVE_CURRENCIES[chainId].symbol + const isUpToLarge = useMediaQuery(upToLarge) return ( account && toggleAccountModal()}> - {standaloneMode !== false && account && !isChainIdUnsupported && userEthBalance && chainId && ( + {standaloneMode !== false && account && !isChainIdUnsupported && userEthBalance && chainId && !isUpToLarge && ( diff --git a/apps/cowswap-frontend/src/legacy/components/Header/MobileMenuIcon/index.tsx b/apps/cowswap-frontend/src/legacy/components/Header/MobileMenuIcon/index.tsx index d73c2fbf42..ea1456c71a 100644 --- a/apps/cowswap-frontend/src/legacy/components/Header/MobileMenuIcon/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Header/MobileMenuIcon/index.tsx @@ -7,10 +7,16 @@ const Wrapper = styled.div<{ isMobileMenuOpen: boolean; height?: number; width?: z-index: 102; display: flex; cursor: pointer; - margin: 0 6px 0 16px; + margin: 0; position: relative; width: ${({ width = 34 }) => `${width}px`}; height: ${({ height = 18 }) => `${height}px`}; + flex: 0 0 auto; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + max-width: 24px; + width: 100%; + `} span { background-color: var(${UI.COLOR_TEXT}); diff --git a/apps/cowswap-frontend/src/legacy/components/Header/NetworkSelector/index.tsx b/apps/cowswap-frontend/src/legacy/components/Header/NetworkSelector/index.tsx index 3a067e214c..059ca3a643 100644 --- a/apps/cowswap-frontend/src/legacy/components/Header/NetworkSelector/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Header/NetworkSelector/index.tsx @@ -54,27 +54,23 @@ const FlyoutMenuContents = styled.div` padding: 16px; ${({ theme }) => theme.mediaWidth.upToSmall` - top: 50px; - box-shadow: 0 0 0 100vh ${({ theme }) => transparentize(theme.black, 0.1)}}; - `}; + top: 50px; + box-shadow: 0 0 0 100vh ${({ theme }) => transparentize(theme.black, 0.1)}}; + `}; & > *:not(:last-child) { margin-bottom: 5px; } ` const SelectorLabel = styled.div` - display: none; + display: block; flex: 1 1 auto; - margin: 0 2px 0 0; - - ${({ theme }) => theme.mediaWidth.upToMedium` - display: none; - `}; + margin: 0; + white-space: nowrap; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - display: block; - margin-right: 4px; - } + ${({ theme }) => theme.mediaWidth.upToSmall` + display: none; + `}; ` const SelectorControls = styled.div<{ isChainIdUnsupported: boolean }>` align-items: center; @@ -82,8 +78,9 @@ const SelectorControls = styled.div<{ isChainIdUnsupported: boolean }>` display: flex; font-weight: 500; justify-content: space-between; + gap: 6px; - :focus { + &:focus { background-color: ${({ theme }) => darken(theme.red1, 0.1)}; } @@ -93,13 +90,6 @@ const SelectorControls = styled.div<{ isChainIdUnsupported: boolean }>` transition: border var(${UI.ANIMATION_DURATION}) ease-in-out; background: transparent; - > img { - width: 24px; - height: 24px; - object-fit: contain; - margin: 0 6px 0 0; - } - &:hover { border: 2px solid ${({ theme }) => transparentize(theme.text, 0.7)}; } @@ -113,13 +103,15 @@ const SelectorControls = styled.div<{ isChainIdUnsupported: boolean }>` `} ` const SelectorLogo = styled.img<{ interactive?: boolean }>` - width: 24px; - height: 24px; + --size: 24px; + width: var(--size); + height: var(--size); margin-right: ${({ interactive }) => (interactive ? 8 : 0)}px; + object-fit: contain; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - margin-right: 8px; - } + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + --size: 21px; + `}; ` const SelectorWrapper = styled.div` display: flex; @@ -130,7 +122,10 @@ const SelectorWrapper = styled.div` } ` const StyledChevronDown = styled(ChevronDown)` - width: 16px; + width: 21px; + height: 21px; + margin: 0 0 0 -3px; + object-fit: contain; ` const NetworkIcon = styled(AlertTriangle)` margin-left: 0.25rem; diff --git a/apps/cowswap-frontend/src/legacy/components/Header/index.tsx b/apps/cowswap-frontend/src/legacy/components/Header/index.tsx index 55bf885e85..fee19e0e58 100644 --- a/apps/cowswap-frontend/src/legacy/components/Header/index.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Header/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react' import { toggleDarkModeAnalytics } from '@cowprotocol/analytics' import { CHRISTMAS_THEME_ENABLED } from '@cowprotocol/common-const' +import { addBodyClass, removeBodyClass } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { useWalletInfo } from '@cowprotocol/wallet' @@ -10,9 +11,9 @@ import { useNavigate } from 'react-router-dom' import CowBalanceButton from 'legacy/components/CowBalanceButton' import { NetworkSelector } from 'legacy/components/Header/NetworkSelector' -import { upToLarge, upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' +import { upToLarge, upToExtraSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' import { useDarkModeManager } from 'legacy/state/user/hooks' -import { cowSwapLogo, winterThemeHat } from 'legacy/theme/cowSwapAssets' +import { cowSwapLogo, cowSwapIcon, winterThemeHat } from 'legacy/theme/cowSwapAssets' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { MainMenuContext } from 'modules/mainMenu' @@ -56,11 +57,19 @@ export default function Header() { const navigate = useNavigate() const isUpToLarge = useMediaQuery(upToLarge) - const isUpToSmall = useMediaQuery(upToSmall) + const isUpToExtraSmall = useMediaQuery(upToExtraSmall) const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const handleMobileMenuOnClick = useCallback(() => { - isUpToLarge && setIsMobileMenuOpen(!isMobileMenuOpen) + if (isUpToLarge) { + setIsMobileMenuOpen(!isMobileMenuOpen) + + if (!isMobileMenuOpen) { + addBodyClass('noScroll') + } else { + removeBodyClass('noScroll') + } + } }, [isUpToLarge, isMobileMenuOpen]) const tradeMenuContext = useMemo(() => { @@ -103,7 +112,7 @@ export default function Header() { {injectedWidgetParams.logoUrl ? ( ) : ( - + )} @@ -117,19 +126,27 @@ export default function Header() { )} - {} + { + + } {!injectedWidgetParams.hideNetworkSelector && } - {!isChainIdUnsupported && ( + {!isChainIdUnsupported && (isMobileMenuOpen || !isUpToLarge) && ( navigate('/account')} + onClick={() => { + navigate('/account') + handleMobileMenuOnClick() + }} account={account} chainId={chainId} - isUpToSmall={isUpToSmall} /> )} diff --git a/apps/cowswap-frontend/src/legacy/components/Header/styled.tsx b/apps/cowswap-frontend/src/legacy/components/Header/styled.tsx index 2302ae8e51..f6e5742755 100644 --- a/apps/cowswap-frontend/src/legacy/components/Header/styled.tsx +++ b/apps/cowswap-frontend/src/legacy/components/Header/styled.tsx @@ -16,16 +16,19 @@ export const TitleMod = styled.a` align-items: center; pointer-events: auto; justify-self: flex-start; + + &:hover { + cursor: pointer; + } + ${({ theme }) => theme.mediaWidth.upToSmall` justify-self: center; `}; - :hover { - cursor: pointer; - } ` export const HeaderLinksMod = styled(Row)` justify-content: center; + ${({ theme }) => theme.mediaWidth.upToMedium` display: none; `}; @@ -36,17 +39,6 @@ export const HeaderControlsUni = styled.div` flex-direction: row; align-items: center; justify-self: flex-end; - - ${({ theme }) => theme.mediaWidth.upToMedium` - flex-direction: row; - justify-content: space-between; - justify-self: center; - max-width: 960px; - padding: 1rem; - z-index: 1; - height: 72px; - border-radius: 12px 12px 0 0; - `}; ` export const StyledNavLinkUni = styled(NavLink)` @@ -129,20 +121,6 @@ export const HeaderFrame = styled.div<{ showBackground: boolean }>` `}; ` -export const HeaderElementUni = styled.div` - display: flex; - align-items: center; - - ${({ theme }) => theme.mediaWidth.upToMedium` - flex-direction: row-reverse; - align-items: center; - `}; - - ${({ theme }) => theme.mediaWidth.upToVerySmall` - width: 115px; - `}; -` - export const StyledNavLink = styled(StyledNavLinkUni)` transition: color var(${UI.ANIMATION_DURATION}) ease-in-out; color: inherit; @@ -170,37 +148,35 @@ export const HeaderControls = styled(HeaderControlsUni)` `}; ` -export const HeaderElement = styled(HeaderElementUni)` - border-radius: 0; +export const HeaderElement = styled.div` + display: flex; + align-items: center; gap: 12px; - ${({ theme }) => theme.mediaWidth.upToMedium` - flex-direction: row; - justify-content: flex-end; + ${({ theme }) => theme.mediaWidth.upToTiny` position: fixed; bottom: 0; left: 0; width: 100%; - border-radius: 0; - height: 64px; - background-color: var(${UI.COLOR_PAPER}); - border-top: 1px solid ${({ theme }) => theme.grey1}; - backdrop-filter: blur(21px); - padding: 10px 16px; - gap: 8px; - `} + background: var(${UI.COLOR_PAPER}); + padding: 5px; + justify-content: flex-end; + `}; ` export const Wrapper = styled.div<{ isMobileMenuOpen: boolean }>` width: 100%; ${HeaderFrame} { - padding: 16px; display: flex; - grid-template-rows: max-content; + padding: 16px; + gap: 16px; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + gap: 10px; + `}; ${({ theme, isMobileMenuOpen }) => theme.mediaWidth.upToLarge` - grid-template-columns: unset; ${ isMobileMenuOpen && @@ -355,6 +331,10 @@ export const HeaderLinks = styled(HeaderLinksMod)<{ isMobileMenuOpen: boolean }> gap 36px; opacity: 0.7; `}; + + ${({ theme }) => theme.mediaWidth.upToMedium` + width: 100%; + `}; } ${MenuTitle} { @@ -423,8 +403,8 @@ export const LogoImage = styled.div<{ isMobileMenuOpen?: boolean }>` position: relative; ${({ theme }) => theme.mediaWidth.upToSmall` - height: 34px; - width: 106px; + height: 30px; + width: auto; `} ${({ theme, isMobileMenuOpen }) => theme.mediaWidth.upToLarge` @@ -432,6 +412,7 @@ export const LogoImage = styled.div<{ isMobileMenuOpen?: boolean }>` isMobileMenuOpen && css` height: 34px; + width: auto; ` } `} diff --git a/apps/cowswap-frontend/src/legacy/components/NetworkAlert/NetworkAlert.tsx b/apps/cowswap-frontend/src/legacy/components/NetworkAlert/NetworkAlert.tsx index 1b57ab1c3b..ee93ad63ab 100644 --- a/apps/cowswap-frontend/src/legacy/components/NetworkAlert/NetworkAlert.tsx +++ b/apps/cowswap-frontend/src/legacy/components/NetworkAlert/NetworkAlert.tsx @@ -32,9 +32,16 @@ const BodyText = styled.div` } ` const RootWrapper = styled.div` + display: flex; + flex-flow: column wrap; position: relative; margin-top: 16px; color: inherit; + gap: 10px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 0 10px; + `} ` const SHOULD_SHOW_ALERT = { diff --git a/apps/cowswap-frontend/src/legacy/hooks/useMediaQuery.ts b/apps/cowswap-frontend/src/legacy/hooks/useMediaQuery.ts index 05215c23c9..19b89e3920 100644 --- a/apps/cowswap-frontend/src/legacy/hooks/useMediaQuery.ts +++ b/apps/cowswap-frontend/src/legacy/hooks/useMediaQuery.ts @@ -19,6 +19,8 @@ export const useMediaQuery = (query: string) => { } export const upToSmall = `(max-width: ${MEDIA_WIDTHS.upToSmall}px)` +export const upToExtraSmall = `(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)` +export const upToTiny = `(max-width: ${MEDIA_WIDTHS.upToTiny}px)` export const MediumAndUp = `(min-width: ${MEDIA_WIDTHS.upToSmall + 1}px)` export const isMediumOnly = `(min-width: ${MEDIA_WIDTHS.upToSmall + 1}px) and (max-width: ${MEDIA_WIDTHS.upToMedium}px)` export const upToMedium = `(max-width: ${MEDIA_WIDTHS.upToMedium}px)` diff --git a/apps/cowswap-frontend/src/legacy/theme/cowSwapAssets.ts b/apps/cowswap-frontend/src/legacy/theme/cowSwapAssets.ts index 022207adbe..4b91dcd931 100644 --- a/apps/cowswap-frontend/src/legacy/theme/cowSwapAssets.ts +++ b/apps/cowswap-frontend/src/legacy/theme/cowSwapAssets.ts @@ -14,6 +14,18 @@ export function cowSwapLogo(darkMode: boolean): string { return darkMode ? darkModeLogo : lightModeLogo } +export function cowSwapIcon(darkMode: boolean): string { + // Regular Dark mode icon + const darkModeIcon = ` + + ` + + // Regular Light mode icon + const lightModeIcon = `` + + return darkMode ? darkModeIcon : lightModeIcon +} + // Footer cows image // Winter Theme: Footer CoWs export function footerImage(darkMode: boolean): string { diff --git a/apps/cowswap-frontend/src/legacy/theme/index.tsx b/apps/cowswap-frontend/src/legacy/theme/index.tsx index b2c0850294..3c900a9f7f 100644 --- a/apps/cowswap-frontend/src/legacy/theme/index.tsx +++ b/apps/cowswap-frontend/src/legacy/theme/index.tsx @@ -25,13 +25,13 @@ import { Colors } from './styled' export type TextProps = Omit & { override?: boolean } export const MEDIA_WIDTHS = { + upToTiny: 320, upToExtraSmall: 500, upToSmall: 720, upToMedium: 960, upToLarge: 1280, upToLargeAlt: 1390, upToExtraLarge: 2560, - upToVerySmall: 500, } // Migrating to a standard z-index system https://getbootstrap.com/docs/5.0/layout/z-index/ diff --git a/apps/cowswap-frontend/src/legacy/theme/styled.d.ts b/apps/cowswap-frontend/src/legacy/theme/styled.d.ts index 95b208ed37..e5959dbe20 100644 --- a/apps/cowswap-frontend/src/legacy/theme/styled.d.ts +++ b/apps/cowswap-frontend/src/legacy/theme/styled.d.ts @@ -51,7 +51,7 @@ interface ColorsUniswap { primary3: Color primary4: Color primary5: Color - + // pinks secondary1: Color secondary2: Color @@ -68,7 +68,6 @@ interface ColorsUniswap { blue1: Color blue2: Color blue4: Color - } // Override colors @@ -260,9 +259,8 @@ declare module 'styled-components' { color?: string } mediaWidth: { + upToTiny: ThemedCssFunction upToExtraSmall: ThemedCssFunction - // MOD - upToVerySmall: ThemedCssFunction upToSmall: ThemedCssFunction upToMedium: ThemedCssFunction upToLarge: ThemedCssFunction diff --git a/apps/cowswap-frontend/src/modules/application/pure/Page/index.tsx b/apps/cowswap-frontend/src/modules/application/pure/Page/index.tsx index 58bd8893a4..23b06ae562 100644 --- a/apps/cowswap-frontend/src/modules/application/pure/Page/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/pure/Page/index.tsx @@ -22,7 +22,7 @@ export const Title = styled.h1` text-shadow: ${({ theme }) => theme.textShadow1}; font-weight: 500; - ${({ theme }) => theme.mediaWidth.upToVerySmall` + ${({ theme }) => theme.mediaWidth.upToExtraSmall` font-size: 24px; `} ` diff --git a/apps/cowswap-frontend/src/modules/fortune/containers/FortuneWidget/index.tsx b/apps/cowswap-frontend/src/modules/fortune/containers/FortuneWidget/index.tsx index 5dc573e356..a3da2177ba 100644 --- a/apps/cowswap-frontend/src/modules/fortune/containers/FortuneWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/fortune/containers/FortuneWidget/index.tsx @@ -5,12 +5,12 @@ import { useCallback, useMemo, useRef, useState } from 'react' import { openFortuneCookieAnalytics, shareFortuneTwitterAnalytics } from '@cowprotocol/analytics' import fortuneCookieImage from '@cowprotocol/assets/cow-swap/fortune-cookie.png' import twitterImage from '@cowprotocol/assets/cow-swap/twitter.svg' -import { useInterval } from '@cowprotocol/common-hooks' import { addBodyClass, removeBodyClass } from '@cowprotocol/common-utils' import { ExternalLink } from '@cowprotocol/ui' import { UI } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' +import ReactDOM from 'react-dom' import { X } from 'react-feather' import SVG from 'react-inlinesvg' import styled from 'styled-components/macro' @@ -48,9 +48,23 @@ const FortuneButton = styled.div<{ isDailyFortuneChecked: boolean }>` ${({ theme }) => theme.mediaWidth.upToMedium` --size: 52px; - left: 65px; + position: relative; right: initial; - bottom: 0; + bottom: initial; + transform: none; + animation: none; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 0px; + margin: 0px; + font-weight: 600; + font-size: 17px; + padding: 15px 10px; + color: inherit; + border-bottom: 1px solid var(${UI.COLOR_TEXT_OPACITY_10}); + height: auto; `} &::before { @@ -69,7 +83,8 @@ const FortuneButton = styled.div<{ isDailyFortuneChecked: boolean }>` z-index: -1; ${({ theme }) => theme.mediaWidth.upToMedium` - box-shadow: none; + content: none; + display: none; `} } @@ -81,10 +96,22 @@ const FortuneButton = styled.div<{ isDailyFortuneChecked: boolean }>` width: var(--size); height: var(--size); transition: transform var(${UI.ANIMATION_DURATION}) ease-in-out; + + ${({ theme }) => theme.mediaWidth.upToMedium` + --size: 46px; + `} } &:hover::after { transform: scale(1.4); + + ${({ theme }) => theme.mediaWidth.upToMedium` + transform: none; + `} + } + + > span { + display: block; } @keyframes floating { @@ -186,6 +213,11 @@ const FortuneTitle = styled.h2` font-weight: 700; color: inherit; + ${({ theme }) => theme.mediaWidth.upToMedium` + font-size: 16px; + margin: 16px auto 34px; + `} + > i { font-size: 16px; text-transform: uppercase; @@ -210,8 +242,8 @@ const FortuneText = styled.h3` background: ${({ theme }) => theme.white}; // small device - ${({ theme }) => theme.mediaWidth.upToSmall` - font-size: 26px; + ${({ theme }) => theme.mediaWidth.upToMedium` + font-size: 21px; `} &:before { @@ -264,6 +296,12 @@ const HeaderElement = styled.div` left: 0; height: 56px; z-index: 10; + + ${({ theme }) => theme.mediaWidth.upToMedium` + height: 48px; + justify-content: center; + background: var(${UI.COLOR_PAPER_DARKEST}); + `} ` const StyledCloseIcon = styled(X)` @@ -275,7 +313,9 @@ const StyledCloseIcon = styled(X)` margin: 0 0 0 auto; ${({ theme }) => theme.mediaWidth.upToSmall` - --size: 42px; + --size: 34px; + width: 100%; + margin: 0; `} &:hover { @@ -288,7 +328,12 @@ const StyledCloseIcon = styled(X)` } ` -export function FortuneWidget() { +interface FortuneWidgetProps { + menuTitle?: string + isMobileMenuOpen?: boolean +} + +export function FortuneWidget({ menuTitle, isMobileMenuOpen }: FortuneWidgetProps) { const { openFortune } = useAtomValue(fortuneStateAtom) const lastCheckedFortune = useAtomValue(lastCheckedFortuneAtom) const updateOpenFortune = useSetAtom(updateOpenFortuneAtom) @@ -296,10 +341,6 @@ export function FortuneWidget() { const openRandomFortune = useOpenRandomFortune() const [isNewFortuneOpen, setIsNewFortuneOpen] = useState(false) const [isFortunedShared, setIsFortunedShared] = useState(false) - - const [today, setToday] = useState(new Date()) - useInterval(() => setToday(new Date()), 2_000) - const checkboxRef = useRef(null) // TODO: add text @@ -311,23 +352,28 @@ export function FortuneWidget() { if (!lastCheckedFortune) return false const lastCheckedFortuneDate = new Date(lastCheckedFortune.checkTimestamp) + const today = new Date() return ( lastCheckedFortuneDate.getUTCFullYear() === today.getUTCFullYear() && lastCheckedFortuneDate.getUTCMonth() === today.getUTCMonth() && lastCheckedFortuneDate.getUTCDate() === today.getUTCDate() ) - }, [lastCheckedFortune, today]) + }, [lastCheckedFortune]) const closeModal = useCallback(() => { updateOpenFortune(null) setIsNewFortuneOpen(false) - removeBodyClass('noScroll') + + // only remove body class if isMobileMenuOpen is false + if (!isMobileMenuOpen) { + removeBodyClass('noScroll') + } if (checkboxRef.current?.checked) { setIsFortunesFeatureDisabled(true) } - }, [updateOpenFortune, checkboxRef, setIsFortunesFeatureDisabled]) + }, [updateOpenFortune, checkboxRef, setIsFortunesFeatureDisabled, isMobileMenuOpen]) const openFortuneModal = useCallback(() => { setIsFortunedShared(false) @@ -354,24 +400,18 @@ export function FortuneWidget() { if (isFortunesFeatureDisabled && isDailyFortuneChecked && !openFortune) return null - return ( + const PortalContent = () => ( <> {openFortune && ( - Close + - {isNewFortuneOpen ? ( - <> - CoW Fortune of the day - - ) : ( - <> - Already seen today's fortune?
Return tomorrow for a fresh one! - - )} + {isNewFortuneOpen + ? 'CoW Fortune of the day' + : "Already seen today's fortune? Return tomorrow for a fresh one!"}
{openFortune.text} @@ -381,16 +421,13 @@ export function FortuneWidget() { href={`https://twitter.com/intent/tweet?text=${twitterText}`} > - - Share on Twitter - + Share on Twitter {!isNewFortuneOpen && !isFortunedShared && ( @@ -401,8 +438,16 @@ export function FortuneWidget() {
)} - ) + + return ( + <> + + {menuTitle && {menuTitle}} + + {ReactDOM.createPortal(, document.body)} + + ) } diff --git a/apps/cowswap-frontend/src/modules/mainMenu/pure/MenuTree/index.tsx b/apps/cowswap-frontend/src/modules/mainMenu/pure/MenuTree/index.tsx index cbfc0ec067..dbb61363ef 100644 --- a/apps/cowswap-frontend/src/modules/mainMenu/pure/MenuTree/index.tsx +++ b/apps/cowswap-frontend/src/modules/mainMenu/pure/MenuTree/index.tsx @@ -3,10 +3,13 @@ import IMAGE_SUN from '@cowprotocol/assets/cow-swap/sun.svg' import SVG from 'react-inlinesvg' +import AppziButton from 'legacy/components/AppziButton' import { HeaderLinks as Wrapper, StyledNavLink } from 'legacy/components/Header/styled' import MenuDropdown from 'legacy/components/MenuDropdown' import { MenuSection, MenuTitle } from 'legacy/components/MenuDropdown/styled' +import { upToMedium, useMediaQuery } from 'legacy/hooks/useMediaQuery' +import { FortuneWidget } from 'modules/fortune/containers/FortuneWidget' import { CustomItem, DropDownItem, @@ -21,6 +24,7 @@ import { import { parameterizeTradeRoute } from 'modules/trade/utils/parameterizeTradeRoute' import { RoutesValues } from 'common/constants/routes' +import { FeatureGuard } from 'common/containers/FeatureGuard' import { MenuBadge, StyledExternalLink } from './styled' @@ -93,6 +97,7 @@ function DarkModeButton({ context }: DarkModeButtonProps) { const { darkMode, toggleDarkMode, handleMobileMenuOnClick } = context const description = `${darkMode ? 'Sun/light' : 'Moon/dark'} mode icon` const label = (darkMode ? 'Light' : 'Dark') + ' Mode' + return (