diff --git a/packages/core/README.md b/packages/core/README.md index 6f29c395e..5a194b29b 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -147,7 +147,12 @@ unsubscribe() ``` ```typescript + export type NotifyOptions = { + desktop: Notify + mobile: Notify +} +export type Notify = { enabled: boolean // default: true /** * Callback that receives all transaction events @@ -158,8 +163,15 @@ export type NotifyOptions = { transactionHandler?: ( event: EthereumTransactionData ) => TransactionHandlerReturn + position: CommonPositions } +export type CommonPositions = +| 'topRight' +| 'bottomRight' +| 'bottomLeft' +| 'topLeft' + export type TransactionHandlerReturn = CustomNotification | boolean | void export type CustomNotification = Partial> @@ -285,15 +297,31 @@ const onboard = Onboard({ }, apiKey: 'xxx387fb-bxx1-4xxc-a0x3-9d37e426xxxx' notify: { - enabled: true, - transactionHandler: transaction => { - console.log({ transaction }) - if (transaction.eventCode === 'txPool') { - return { - type: 'success', - message: 'Your transaction from #1 DApp is in the mempool', + desktop: { + enabled: true, + transactionHandler: transaction => { + console.log({ transaction }) + if (transaction.eventCode === 'txPool') { + return { + type: 'success', + message: 'Your transaction from #1 DApp is in the mempool', + } } - } + }, + position: 'bottomLeft' + }, + mobile: { + enabled: true, + transactionHandler: transaction => { + console.log({ transaction }) + if (transaction.eventCode === 'txPool') { + return { + type: 'success', + message: 'Your transaction from #1 DApp is in the mempool', + } + } + }, + position: 'topRight' } }, accountCenter: { @@ -420,7 +448,7 @@ type AppState = { accountCenter: AccountCenter walletModules: WalletModule[] locale: Locale - notify: NotifyOptions + notify: Notify notifications: Notification[] } diff --git a/packages/core/package.json b/packages/core/package.json index 570fabb2e..61e9ed797 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/core", - "version": "2.3.1-alpha.3", + "version": "2.3.1-alpha.4", "scripts": { "build": "rollup -c", "dev": "rollup -c -w", diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 32bed8402..4c37bda6a 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -13,7 +13,8 @@ export const APP_INITIAL_STATE: AppState = { }, notify: { enabled: true, - transactionHandler: () => {} + transactionHandler: () => {}, + position: 'topRight' }, notifications: [], locale: '' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 100433ff0..cbd7a22ff 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,10 +4,10 @@ import disconnectWallet from './disconnect' import setChain from './chain' import { state } from './store' import { reset$ } from './streams' -import { validateInitOptions } from './validation' +import { validateInitOptions, validateNotify, validateNotifyOptions } from './validation' import initI18N from './i18n' import App from './views/Index.svelte' -import type { InitOptions, OnboardAPI } from './types' +import type { InitOptions, OnboardAPI, Notify } from './types' import { APP_INITIAL_STATE } from './constants' import { configuration, updateConfiguration } from './configuration' @@ -22,7 +22,7 @@ import { import updateBalances from './updateBalances' -const API = { +const API: OnboardAPI = { connectWallet, disconnectWallet, setChain, @@ -51,7 +51,7 @@ export type { AppState, CustomNotification, Notification, - NotifyOptions, + Notify, UpdateNotification } from './types' @@ -104,7 +104,50 @@ function init(options: InitOptions): OnboardAPI { // update notify if (typeof notify !== undefined) { - updateNotify(notify) + if ('desktop' in notify || 'mobile' in notify) { + const error = validateNotifyOptions(notify) + + if (error) { + throw error + } + + if ( + (!notify.desktop || (notify.desktop && !notify.desktop.position)) && + accountCenter && + accountCenter.desktop && + accountCenter.desktop.position + ) { + notify.desktop.position = accountCenter.desktop.position + } + if ( + (!notify.mobile || (notify.mobile && !notify.mobile.position)) && + accountCenter && + accountCenter.mobile && + accountCenter.mobile.position + ) { + notify.mobile.position = accountCenter.mobile.position + } + let notifyUpdate: Partial + if (device.type === 'mobile' && notify.mobile) { + notifyUpdate = { + ...APP_INITIAL_STATE.notify, + ...notify.mobile + } + } else if (notify.desktop) { + notifyUpdate = { + ...APP_INITIAL_STATE.notify, + ...notify.desktop + } + } + updateNotify(notifyUpdate) + } else { + const error = validateNotify(notify as Notify) + + if (error) { + throw error + } + updateNotify(notify as Notify) + } } if (svelteInstance) { diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index d7f9061d7..af1304ac6 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -16,7 +16,6 @@ import type { UpdateAccountCenterAction, UpdateWalletAction, WalletState, - NotifyOptions, UpdateNotifyAction, Notification, AddNotificationAction, @@ -24,7 +23,8 @@ import type { UpdateAllWalletsAction, CustomNotification, UpdateNotification, - CustomNotificationUpdate + CustomNotificationUpdate, + Notify } from '../types' import { @@ -33,11 +33,11 @@ import { validateNotification, validateCustomNotification, validateCustomNotificationUpdate, - validateNotifyOptions, validateString, validateWallet, validateWalletInit, - validateUpdateBalances + validateUpdateBalances, + validateNotify } from '../validation' import { @@ -156,8 +156,8 @@ export function updateAccountCenter( dispatch(action as UpdateAccountCenterAction) } -export function updateNotify(update: Partial): void { - const error = validateNotifyOptions(update) +export function updateNotify(update: Partial): void { + const error = validateNotify(update) if (error) { throw error diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 46ab2f973..14724a3dd 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -46,7 +46,7 @@ export interface InitOptions { /** * Transaction notification options */ - notify?: Partial + notify?: Partial | Partial } export interface OnboardAPI { @@ -63,7 +63,7 @@ export interface OnboardAPI { interface ExposedActions { setWalletModules: (wallets: WalletInit[]) => void setLocale: (locale: string) => void - updateNotify: (update: Partial) => void + updateNotify: (update: Partial) => void customNotification: ( updatedNotification: CustomNotification ) => { @@ -141,7 +141,7 @@ export interface AppState { wallets: WalletState[] accountCenter: AccountCenter locale: Locale - notify: NotifyOptions + notify: Notify notifications: Notification[] } @@ -156,11 +156,15 @@ export type Locale = string export type i18nOptions = Record export type i18n = typeof en -export type AccountCenterPosition = - | 'topRight' - | 'bottomRight' - | 'bottomLeft' - | 'topLeft' +export type CommonPositions = +| 'topRight' +| 'bottomRight' +| 'bottomLeft' +| 'topLeft' + +export type AccountCenterPosition = CommonPositions + +export type NotificationPosition = CommonPositions export type AccountCenter = { enabled: boolean @@ -174,7 +178,7 @@ export type AccountCenterOptions = { mobile: Omit } -export type NotifyOptions = { +export type Notify = { /** * Defines whether whether to subscribe to transaction events or not * default: true @@ -189,6 +193,17 @@ export type NotifyOptions = { transactionHandler: ( event: EthereumTransactionData ) => TransactionHandlerReturn + /** + * Position of notifications that defaults to the same position as the + * Account Center (if enabled) of the top right if AC is disabled + * and notifications are enabled (enabled by default with API key) + */ + position?: NotificationPosition +} + +export type NotifyOptions = { + desktop: Notify + mobile: Notify } export type Notification = { @@ -279,7 +294,7 @@ export type SetLocaleAction = { export type UpdateNotifyAction = { type: 'update_notify' - payload: Partial + payload: Partial } export type AddNotificationAction = { diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index a31081cf5..bbdf8f134 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -17,7 +17,8 @@ import type { NotifyOptions, Notification, CustomNotification, - CustomNotificationUpdate + CustomNotificationUpdate, + Notify } from './types' const chainId = Joi.string().pattern(/^0x[0-9a-fA-F]+$/) @@ -128,7 +129,7 @@ const walletInit = Joi.array().items(Joi.function()).required() const locale = Joi.string() -const accountCenterPosition = Joi.string().valid( +const commonPositions = Joi.string().valid( 'topRight', 'bottomRight', 'bottomLeft', @@ -137,7 +138,13 @@ const accountCenterPosition = Joi.string().valid( const notify = Joi.object({ transactionHandler: Joi.function(), - enabled: Joi.boolean() + enabled: Joi.boolean(), + position: commonPositions +}) + +const notifyOptions = Joi.object({ + desktop: notify, + mobile: notify }) const initOptions = Joi.object({ @@ -150,15 +157,15 @@ const initOptions = Joi.object({ desktop: Joi.object({ enabled: Joi.boolean(), minimal: Joi.boolean(), - position: accountCenterPosition + position: commonPositions }), mobile: Joi.object({ enabled: Joi.boolean(), minimal: Joi.boolean(), - position: accountCenterPosition + position: commonPositions }) }), - notify + notify: [notifyOptions, notify] }) const connectOptions = Joi.object({ @@ -183,7 +190,7 @@ const setChainOptions = Joi.object({ const accountCenter = Joi.object({ enabled: Joi.boolean(), - position: accountCenterPosition, + position: commonPositions, expanded: Joi.boolean(), minimal: Joi.boolean() }) @@ -287,10 +294,14 @@ export function validateLocale(data: string): ValidateReturn { return validate(locale, data) } +export function validateNotify(data: Partial): ValidateReturn { + return validate(notify, data) +} + export function validateNotifyOptions( data: Partial ): ValidateReturn { - return validate(notify, data) + return validate(notifyOptions, data) } export function validateTransactionHandlerReturn( diff --git a/packages/core/src/views/Index.svelte b/packages/core/src/views/Index.svelte index c92b75ea9..adcb618dc 100644 --- a/packages/core/src/views/Index.svelte +++ b/packages/core/src/views/Index.svelte @@ -17,8 +17,7 @@ const notify$ = state .select('notify') .pipe(startWith(state.get().notify), shareReplay(1)) - - const accountCenterPositions = { + const positioningDefaults = { topLeft: 'top: 0; left: 0;', topRight: 'top: 0; right: 0;', bottomRight: 'bottom: 0; right: 0;', @@ -281,19 +280,47 @@ {/if} -{#if ($notify$.enabled || $accountCenter$.enabled) && $wallets$.length} +{#if $notify$.enabled && $accountCenter$.enabled && $wallets$.length}
- {#if $notify$.enabled && $accountCenter$.position.includes('bottom')} - + {#if $notify$.position.includes('bottom') && $accountCenter$.position.includes('bottom') && (device.type === 'mobile' || $accountCenter$.position === $notify$.position)} + {/if} +
+ +
+ {#if $notify$.position.includes('top') && $accountCenter$.position.includes('top') && (device.type === 'mobile' || $accountCenter$.position === $notify$.position)} + + {/if} +
+{/if} +{#if $accountCenter$.enabled && (!$notify$.enabled || ($notify$.position !== $accountCenter$.position && device.type !== 'mobile')) && $wallets$.length} +
{/if}
- - {#if $notify$.enabled && $accountCenter$.position.includes('top')} - - {/if} +
+{/if} +{#if $notify$.enabled && (!$accountCenter$.enabled || ($notify$.position !== $accountCenter$.position && device.type !== 'mobile') || ($notify$.position.includes('top') && $accountCenter$.position.includes('bottom')) || ($notify$.position.includes('bottom') && $accountCenter$.position.includes('top'))) && $wallets$.length} +
+
{/if} diff --git a/packages/core/src/views/notify/Index.svelte b/packages/core/src/views/notify/Index.svelte index 272722e02..5e5f31213 100644 --- a/packages/core/src/views/notify/Index.svelte +++ b/packages/core/src/views/notify/Index.svelte @@ -6,12 +6,16 @@ import { state } from '../../store' import { shareReplay, startWith } from 'rxjs/operators' import Notification from './Notification.svelte' + import { configuration } from '../../configuration' + + const { device } = configuration const accountCenter$ = state .select('accountCenter') .pipe(startWith(state.get().accountCenter), shareReplay(1)) export let position: string + export let sharedContainer: boolean let x: number let y: number @@ -116,7 +120,13 @@ style={`${ position.includes('top') ? 'justify-content:flex-start;' : '' }; max-height: calc(100vh - ${ - !$accountCenter$.expanded ? '82px' : '412px' + $accountCenter$.expanded + ? '412px' + : sharedContainer && device.type !== 'mobile' + ? '82px' + : !sharedContainer && device.type === 'mobile' + ? '108px' + : '24px' })`} > {#each $notifications$ as notification (notification.key)} diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index b540aad32..43ada5350 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -166,7 +166,7 @@ // // example customizing account center accountCenter: { desktop: { - position: 'topRight', + position: 'topLeft', enabled: true, minimal: false } @@ -182,29 +182,33 @@ } }, notify: { - enabled: true, - transactionHandler: transaction => { - console.log({ transaction }) - // if (transaction.eventCode === 'txConfirmed') { - // return { - // type: 'error', - // message: 'Your in the pool, hope you brought a towel!', - // autoDismiss: 0, - // id: '123', - // key: '321', - // onClick: () => - // window.open(`https://rinkeby.etherscan.io/tx/${transaction.hash}`) - // } - // } - // if (transaction.eventCode === 'txPool') { - // return { - // type: 'hint', - // message: 'Your in the pool, hope you brought a towel!', - // autoDismiss: 0, - // link: `https://ropsten.etherscan.io/tx/${transaction.hash}` - // } - // } - } + desktop: { + enabled: true, + transactionHandler: transaction => { + console.log({ transaction }) + // if (transaction.eventCode === 'txConfirmed') { + // return { + // type: 'error', + // message: 'Your in the pool, hope you brought a towel!', + // autoDismiss: 0, + // id: '123', + // key: '321', + // onClick: () => + // window.open(`https://rinkeby.etherscan.io/tx/${transaction.hash}`) + // } + // } + // if (transaction.eventCode === 'txPool') { + // return { + // type: 'hint', + // message: 'Your in the pool, hope you brought a towel!', + // autoDismiss: 0, + // link: `https://ropsten.etherscan.io/tx/${transaction.hash}` + // } + // } + }, + position: 'topRight' + }, + }, // Sign up for your free api key at www.Blocknative.com apiKey: 'xxxxxx-bf21-42ec-a093-9d37e426xxxx' @@ -227,7 +231,7 @@ } let toAddress - const sendTransaction = async (provider) => { + const sendTransaction = async provider => { const ethersProvider = new ethers.providers.Web3Provider(provider, 'any') const signer = ethersProvider.getSigner() @@ -464,9 +468,7 @@ placeholder="0x..." bind:value={toAddress} /> - diff --git a/packages/react/README.md b/packages/react/README.md index a376afe8e..c7f2f5aed 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -345,7 +345,7 @@ type UseNotifications = (): [ dismiss: () => void update: UpdateNotification }, - (update: Partial) => void + (update: Partial) => void ] type Notification = { @@ -377,7 +377,7 @@ interface UpdateNotification { update: UpdateNotification } } -type NotifyOptions = { +type Notify = { /** * Defines whether to subscribe to transaction events or not * default: true @@ -396,7 +396,7 @@ type NotifyOptions = { const [ notifications, // the list of all notifications that update when notifications are added, updated or removed customNotification, // a function that takes a customNotification object and allows custom notifications to be shown to the user, returns an update and dismiss callback - updateNotify // a function that takes a NotifyOptions object to allow updating of the properties + updateNotify // a function that takes a Notify object to allow updating of the properties ] = useNotifications() // View notifications as they come in if you would like to handle them independent of the notification display diff --git a/packages/react/package.json b/packages/react/package.json index e6f5f18b5..aa7c4c805 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/react", - "version": "2.2.1-alpha.3", + "version": "2.2.1-alpha.4", "description": "Collection of React Hooks for web3-onboard", "module": "dist/index.js", "browser": "dist/index.js", @@ -23,7 +23,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/core": "^2.3.1-alpha.3", + "@web3-onboard/core": "^2.3.1-alpha.4", "@web3-onboard/common": "^2.1.3-alpha.1", "use-sync-external-store": "1.0.0" }, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 219a21d57..ea2a80e86 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -13,7 +13,7 @@ import type { AppState, CustomNotification, Notification, - NotifyOptions, + Notify, UpdateNotification } from '@web3-onboard/core' import type { Chain, WalletInit } from '@web3-onboard/common' @@ -155,7 +155,7 @@ export const useNotifications = (): [ dismiss: () => void update: UpdateNotification }, - (update: Partial) => void + (update: Partial) => void ] => { if (!web3Onboard) throw new Error(HOOK_ERROR_MESSAGE) diff --git a/packages/vue/package.json b/packages/vue/package.json index 2b37f5d97..393951a16 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/vue", - "version": "2.1.1-alpha.2", + "version": "2.1.1-alpha.3", "description": "Vue Composable for web3-onboard", "module": "dist/index.js", "browser": "dist/index.js", @@ -24,7 +24,7 @@ "@vueuse/core": "^8.4.2", "@vueuse/rxjs": "^8.2.0", "@web3-onboard/common": "^2.1.3-alpha.1", - "@web3-onboard/core": "^2.3.1-alpha.3", + "@web3-onboard/core": "^2.3.1-alpha.4", "vue-demi": "^0.12.4" }, "peerDependencies": {