From 930c0ebd1ced8a828372bd7ee16b42e615d6402d Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Tue, 16 Apr 2024 08:47:56 +0300 Subject: [PATCH] feat: popup amount notation (#226) * Fetch exchange rates and convert rate of pay * Fix conversion * Fetch rate of pay from storage * Amount conversion * Rename to `rateOfPay` * Rename to `rateOfPay` --- src/background/services/background.ts | 7 ++ src/background/services/storage.ts | 55 ++-------- src/popup/components/PayWebsiteForm.tsx | 4 +- src/popup/lib/context.tsx | 21 +++- src/popup/lib/messages.ts | 9 ++ src/popup/lib/utils.ts | 5 + src/popup/pages/Home.tsx | 134 ++++++++++-------------- src/popup/pages/Settings.tsx | 2 +- src/shared/messages.ts | 8 +- src/shared/types.ts | 4 +- 10 files changed, 114 insertions(+), 135 deletions(-) diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 6a36099e..f9453d16 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -55,6 +55,13 @@ export class Background { ) throw new Error('Not implemented') + case PopupToBackgroundAction.UPDATE_RATE_OF_PAY: + return success( + await this.storage.set({ + rateOfPay: message.payload.rateOfPay + }) + ) + default: return } diff --git a/src/background/services/storage.ts b/src/background/services/storage.ts index 6d589c9d..d71ede18 100644 --- a/src/background/services/storage.ts +++ b/src/background/services/storage.ts @@ -1,11 +1,6 @@ import { DEFAULT_RATE_OF_PAY, DEFAULT_INTERVAL_MS } from '@/background/config' import { Logger } from '@/shared/logger' -import type { - PopupStore, - Storage, - StorageKey, - WebsiteData -} from '@/shared/types' +import type { PopupStore, Storage, StorageKey } from '@/shared/types' import { type Browser } from 'webextension-polyfill' const defaultStorage = { @@ -52,58 +47,20 @@ export class StorageService { } } + // TODO: Exception list (post-v1) - return data for the current website async getPopupData(): Promise { - // TODO: Improve URL management - const [{ url: tabUrl }] = await this.browser.tabs.query({ - active: true, - currentWindow: true - }) const data = await this.get([ 'enabled', 'connected', 'amount', - 'exceptionList', + 'rateOfPay', + 'minRateOfPay', + 'maxRateOfPay', 'walletAddress', 'publicKey' ]) - const website: WebsiteData = { - url: '', - amount: { value: '0', interval: DEFAULT_INTERVAL_MS } - } - - if (tabUrl) { - let url = '' - try { - const parsedUrl = new URL(tabUrl) - if (parsedUrl.protocol !== 'https:') { - throw new Error('Only https websites allowed') - } - url = `${parsedUrl.origin}${parsedUrl.pathname}` - } catch (e) { - this.logger.error(e.message) - /** noop */ - } - - website.url = url - if (data.exceptionList && data.exceptionList[url]) { - website.amount = data.exceptionList[url] - } else { - website.amount = { - value: DEFAULT_RATE_OF_PAY, - interval: DEFAULT_INTERVAL_MS - } - } - } - - return { - enabled: data.enabled, - connected: data.connected, - amount: data.amount, - walletAddress: data.walletAddress, - publicKey: data.publicKey, - website - } + return data } async keyPairExists(): Promise { diff --git a/src/popup/components/PayWebsiteForm.tsx b/src/popup/components/PayWebsiteForm.tsx index eab66d8c..db20bff3 100644 --- a/src/popup/components/PayWebsiteForm.tsx +++ b/src/popup/components/PayWebsiteForm.tsx @@ -13,7 +13,7 @@ interface PayWebsiteFormProps { export const PayWebsiteForm = () => { const { - state: { walletAddress, website } + state: { walletAddress } } = React.useContext(PopupStateContext) const { register, @@ -34,7 +34,7 @@ export const PayWebsiteForm = () => { addOn={getCurrencySymbol(walletAddress.assetCode)} label={
- Pay {website.url} + Pay URL
} placeholder="0.00" diff --git a/src/popup/lib/context.tsx b/src/popup/lib/context.tsx index 95d87f13..c893d943 100644 --- a/src/popup/lib/context.tsx +++ b/src/popup/lib/context.tsx @@ -4,7 +4,8 @@ import { DeepNonNullable, PopupStore } from '@/shared/types' export enum ReducerActionType { SET_DATA = 'SET_DATA', - TOGGLE_WM = 'TOGGLE_WM' + TOGGLE_WM = 'TOGGLE_WM', + UPDATE_RATE_OF_PAY = 'UPDATE_RATE_OF_PAY' } export type PopupState = Required> @@ -28,7 +29,17 @@ interface ToggleWMAction extends ReducerActionMock { type: ReducerActionType.TOGGLE_WM } -export type ReducerActions = SetDataAction | ToggleWMAction +interface UpdateRateOfPayAction extends ReducerActionMock { + type: ReducerActionType.UPDATE_RATE_OF_PAY + data: { + rateOfPay: string + } +} + +export type ReducerActions = + | SetDataAction + | ToggleWMAction + | UpdateRateOfPayAction export const PopupStateContext = React.createContext( {} as PopupContext @@ -45,6 +56,12 @@ const reducer = (state: PopupState, action: ReducerActions): PopupState => { enabled: !state.enabled } } + case ReducerActionType.UPDATE_RATE_OF_PAY: { + return { + ...state, + rateOfPay: action.data.rateOfPay + } + } default: return state } diff --git a/src/popup/lib/messages.ts b/src/popup/lib/messages.ts index 1dede4cf..3556b5b8 100644 --- a/src/popup/lib/messages.ts +++ b/src/popup/lib/messages.ts @@ -37,6 +37,15 @@ export const toggleWM = async () => { }) } +export const updateRateOfPay = async ( + payload: PopupToBackgroundActionPayload[PopupToBackgroundAction.UPDATE_RATE_OF_PAY] +) => { + return await message.send({ + action: PopupToBackgroundAction.UPDATE_RATE_OF_PAY, + payload + }) +} + export const payWebsite = async ( payload: PopupToBackgroundActionPayload[PopupToBackgroundAction.PAY_WEBSITE] ) => { diff --git a/src/popup/lib/utils.ts b/src/popup/lib/utils.ts index cf0d9913..610b45cf 100644 --- a/src/popup/lib/utils.ts +++ b/src/popup/lib/utils.ts @@ -24,3 +24,8 @@ export const transformBalance = (amount: string, scale: number): string => { export function charIsNumber(char?: string) { return !!(char || '').match(/\d|\./) } + +export function roundWithPrecision(num: number, precision: number) { + const multiplier = Math.pow(10, precision) + return Math.round(num * multiplier) / multiplier +} diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx index 533df5f6..f589726e 100644 --- a/src/popup/pages/Home.tsx +++ b/src/popup/pages/Home.tsx @@ -1,13 +1,47 @@ import React from 'react' -import { PopupStateContext } from '@/popup/lib/context' +import { PopupStateContext, ReducerActionType } from '@/popup/lib/context' import { WarningSign } from '@/popup/components/Icons' -import { PayWebsiteForm } from '@/popup/components/PayWebsiteForm' +import { Slider } from '../components/ui/Slider' +import { updateRateOfPay } from '../lib/messages' +import { Label } from '../components/ui/Label' +import { getCurrencySymbol, roundWithPrecision } from '../lib/utils' export const Component = () => { const { - state: { enabled, website } + state: { + enabled, + rateOfPay, + minRateOfPay, + maxRateOfPay, + walletAddress + }, + dispatch } = React.useContext(PopupStateContext) + const rate = React.useMemo(() => { + const r = Number(rateOfPay) / 10 ** walletAddress.assetScale + if (roundWithPrecision(r, 2) > 0) { + return r.toFixed(2) + } + + return r.toExponential() + }, [rateOfPay, walletAddress.assetScale]) + + // TODO: Use a debounce + const onRateChange = async (event: React.ChangeEvent) => { + const rateOfPay = event.currentTarget.value + const response = await updateRateOfPay({ + rateOfPay + }) + if (!response.success) return + dispatch({ + type: ReducerActionType.UPDATE_RATE_OF_PAY, + data: { + rateOfPay + } + }) + } + if (!enabled) { return (
@@ -19,79 +53,25 @@ export const Component = () => { ) } - if (website.url === '') { - return ( -
- -

- This website does not support Web Monetization. -

+ return ( +
+
+ + +
+ + {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour + +
- ) - } - - return - // const { - // data: { wmEnabled, rateOfPay, amount, amountType }, - // setData, - // } = usePopup() - // const [tipAmount, setTipAmount] = useState('') - // const updateRateOfPay = async (event: any) => { - // setData(prevState => ({ ...prevState, rateOfPay: event.target.value })) - // } - // const updateStreamType = async (event: any) => { - // setData(prevState => ({ - // ...prevState, - // amountType: { ...prevState.amountType, recurring: event.target.checked }, - // })) - // } - // if (!wmEnabled) { - // return ( - //
- // - //

Web Monetization has been turned off.

- //
- // ) - // } - // return ( - //
- //
- //
Current rate of pay
- // - //
- // {!amountType.recurring ? '0c' : formatCurrency(rateOfPay)} per hour - // Remaining balance: ${amount} - //
- //
- //
- // - // Continuous payments stream - //
- //
- //
- // - // setTipAmount(event.target.value)} - // icon={} - // /> - //
- // - //
- // ) +
+ ) } diff --git a/src/popup/pages/Settings.tsx b/src/popup/pages/Settings.tsx index a6b28938..6ed90990 100644 --- a/src/popup/pages/Settings.tsx +++ b/src/popup/pages/Settings.tsx @@ -5,7 +5,7 @@ import React from 'react' export const Component = () => { const { state } = React.useContext(PopupStateContext) - console.log(state) + if (state.connected) { return } else { diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 64421396..4cc30cb8 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -26,7 +26,8 @@ export enum PopupToBackgroundAction { CONNECT_WALLET = 'CONNECT_WALLET', DISCONNECT_WALLET = 'DISCONNECT_WALLET', TOGGLE_WM = 'TOGGLE_WM', - PAY_WEBSITE = 'PAY_WEBSITE' + PAY_WEBSITE = 'PAY_WEBSITE', + UPDATE_RATE_OF_PAY = 'UPDATE_RATE_OF_PAY' } export interface ConnectWalletPayload { @@ -39,12 +40,17 @@ export interface PayWebsitePayload { amount: string } +export interface UpdateRateOfPayPayload { + rateOfPay: string +} + export interface PopupToBackgroundActionPayload { [PopupToBackgroundAction.GET_CONTEXT_DATA]: undefined [PopupToBackgroundAction.CONNECT_WALLET]: ConnectWalletPayload [PopupToBackgroundAction.DISCONNECT_WALLET]: undefined [PopupToBackgroundAction.TOGGLE_WM]: undefined [PopupToBackgroundAction.PAY_WEBSITE]: PayWebsitePayload + [PopupToBackgroundAction.UPDATE_RATE_OF_PAY]: UpdateRateOfPayPayload } export type PopupToBackgroundMessage = { diff --git a/src/shared/types.ts b/src/shared/types.ts index 433e0b56..2fd7ed7b 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -60,9 +60,7 @@ export type StorageKey = keyof Storage export type PopupStore = Omit< Storage, 'privateKey' | 'keyId' | 'exceptionList' | 'token' | 'grant' -> & { - website: WebsiteData -} +> export type DeepNonNullable = { [P in keyof T]?: NonNullable