+
diff --git a/src/popup/components/ui/Code.tsx b/src/popup/components/ui/Code.tsx
index 71998828..0e112751 100644
--- a/src/popup/components/ui/Code.tsx
+++ b/src/popup/components/ui/Code.tsx
@@ -11,7 +11,7 @@ export const Code = ({ value, className, ...props }: CodeProps) => {
return (
(function Slider(
aria-disabled={disabled ?? false}
aria-invalid={!!errorMessage}
aria-describedby={errorMessage}
- value={disabled ? 0 : value}
+ value={value}
onChange={onChange}
{...props}
/>
diff --git a/src/popup/components/ui/Switch.tsx b/src/popup/components/ui/Switch.tsx
index 24146770..054d86cd 100644
--- a/src/popup/components/ui/Switch.tsx
+++ b/src/popup/components/ui/Switch.tsx
@@ -22,6 +22,9 @@ const switchVariants = cva(
'peer-checked:before:left-4',
],
},
+ disabled: {
+ true: 'opacity-75',
+ },
},
defaultVariants: {
size: 'default',
@@ -33,12 +36,13 @@ export interface SwitchProps
extends VariantProps
,
React.HTMLAttributes {
checked?: boolean;
+ disabled?: boolean;
label?: string;
onChange?: (e: React.ChangeEvent) => void;
}
export const Switch = forwardRef(function Switch(
- { size, label, className, onChange = () => {}, ...props },
+ { size, label, className, disabled = false, onChange = () => {}, ...props },
ref,
) {
return (
@@ -49,10 +53,11 @@ export const Switch = forwardRef(function Switch(
type="checkbox"
checked={props.checked}
onChange={onChange}
+ disabled={disabled}
{...props}
className="peer pointer-events-none absolute -translate-x-[100%] opacity-0"
/>
-
+
{label ? {label} : null}
);
diff --git a/src/popup/index.css b/src/popup/index.css
index 4c8f633b..10acdfca 100644
--- a/src/popup/index.css
+++ b/src/popup/index.css
@@ -6,6 +6,8 @@
:root {
/* Text colors */
--text-primary: 59 130 246;
+ --text-secondary: 86 183 181;
+ --text-secondary-dark: 52 152 152;
--text-weak: 100 116 139;
--text-medium: 51 65 85;
--text-strong: 15 23 42;
diff --git a/src/popup/lib/hooks.ts b/src/popup/lib/hooks.ts
index 78d258ca..7ae850c6 100644
--- a/src/popup/lib/hooks.ts
+++ b/src/popup/lib/hooks.ts
@@ -13,7 +13,10 @@ import React from 'react';
export function useLocalStorage(
key: string,
defaultValue: T,
- { maxAge = 1000 * 24 * 60 * 60 }: Partial<{ maxAge: number }> = {},
+ {
+ maxAge = 1000 * 24 * 60 * 60,
+ validate = () => true,
+ }: Partial<{ maxAge: number; validate: (value: T) => boolean }> = {},
) {
const hasLocalStorage = typeof localStorage !== 'undefined';
maxAge *= 1000;
@@ -33,7 +36,11 @@ export function useLocalStorage(
try {
const data = JSON.parse(storedValue);
- if (isWellFormed(data) && data.expiresAt > Date.now()) {
+ if (
+ isWellFormed(data) &&
+ data.expiresAt > Date.now() &&
+ validate(data.value)
+ ) {
return data.value;
} else {
localStorage.removeItem(key);
diff --git a/src/popup/pages/Home.tsx b/src/popup/pages/Home.tsx
index 4615375d..4a4d4aef 100644
--- a/src/popup/pages/Home.tsx
+++ b/src/popup/pages/Home.tsx
@@ -1,68 +1,19 @@
import React from 'react';
-import { usePopupState, useMessage, useTranslation } from '@/popup/lib/context';
-import { WarningSign } from '@/popup/components/Icons';
-import { Slider } from '../components/ui/Slider';
-import { Label } from '../components/ui/Label';
-import {
- formatNumber,
- getCurrencySymbol,
- roundWithPrecision,
-} from '../lib/utils';
-import { PayWebsiteForm } from '../components/PayWebsiteForm';
+import { Link } from 'react-router-dom';
+import { usePopupState, useTranslation } from '@/popup/lib/context';
+import { Settings } from '@/popup/components/Icons';
+import { formatNumber, roundWithPrecision } from '../lib/utils';
+import { PayWebsiteForm } from '@/popup/components/PayWebsiteForm';
import { NotMonetized } from '@/popup/components/NotMonetized';
-import { debounceAsync } from '@/shared/helpers';
-import { Switch } from '../components/ui/Switch';
+import { formatCurrency } from '@/shared/helpers';
+import { ROUTES_PATH } from '@/popup/Popup';
export const Component = () => {
const t = useTranslation();
- const message = useMessage();
const {
- state: {
- enabled,
- rateOfPay,
- minRateOfPay,
- maxRateOfPay,
- balance,
- walletAddress,
- tab,
- },
- dispatch,
+ state: { tab },
} = usePopupState();
- const rate = React.useMemo(() => {
- const r = Number(rateOfPay) / 10 ** walletAddress.assetScale;
- const roundedR = roundWithPrecision(r, walletAddress.assetScale);
-
- return formatNumber(roundedR, walletAddress.assetScale, true);
- }, [rateOfPay, walletAddress.assetScale]);
-
- const remainingBalance = React.useMemo(() => {
- const val = Number(balance) / 10 ** walletAddress.assetScale;
- const rounded = roundWithPrecision(val, walletAddress.assetScale);
- return formatNumber(rounded, walletAddress.assetScale, true);
- }, [balance, walletAddress.assetScale]);
-
- const updateRateOfPay = React.useRef(
- debounceAsync(async (rateOfPay: string) => {
- const response = await message.send('UPDATE_RATE_OF_PAY', { rateOfPay });
- if (!response.success) {
- // TODO: Maybe reset to old state, but not while user is active (avoid
- // sluggishness in UI)
- }
- }, 1000),
- );
-
- const onRateChange = async (event: React.ChangeEvent) => {
- const rateOfPay = event.currentTarget.value;
- dispatch({ type: 'UPDATE_RATE_OF_PAY', data: { rateOfPay } });
- void updateRateOfPay.current(rateOfPay);
- };
-
- const onChangeWM = () => {
- message.send('TOGGLE_WM');
- dispatch({ type: 'TOGGLE_WM', data: {} });
- };
-
if (tab.status !== 'monetized') {
switch (tab.status) {
case 'all_sessions_invalid':
@@ -80,46 +31,69 @@ export const Component = () => {
}
return (
-
- {enabled ? (
-
-
-
-
-
- {rate} {getCurrencySymbol(walletAddress.assetCode)} per hour
-
-
- Remaining balance: {getCurrencySymbol(walletAddress.assetCode)}
- {remainingBalance}
-
-
+
+
+ Pay as you browse
+ Support content you love
+
+
+
+
+ );
+};
+
+const InfoBanner = () => {
+ const {
+ state: { rateOfPay, balance, walletAddress },
+ } = usePopupState();
+
+ const rate = React.useMemo(() => {
+ const r = Number(rateOfPay) / 10 ** walletAddress.assetScale;
+ const roundedR = roundWithPrecision(r, walletAddress.assetScale);
+
+ return formatCurrency(
+ formatNumber(roundedR, walletAddress.assetScale, true),
+ walletAddress.assetCode,
+ walletAddress.assetScale,
+ );
+ }, [rateOfPay, walletAddress.assetCode, walletAddress.assetScale]);
+
+ const remainingBalance = React.useMemo(() => {
+ const val = Number(balance) / 10 ** walletAddress.assetScale;
+ const rounded = roundWithPrecision(val, walletAddress.assetScale);
+ return formatCurrency(
+ formatNumber(rounded, walletAddress.assetScale, true),
+ walletAddress.assetCode,
+ walletAddress.assetScale,
+ );
+ }, [balance, walletAddress.assetCode, walletAddress.assetScale]);
+
+ return (
+
+
+
+
- Hourly rate
+ - {rate}
- ) : (
-
-
-
- Web Monetization has been turned off.
-
+
+
- Balance
+ - {remainingBalance}
- )}
-
-
-
+
- {tab.url ?
: null}
+
+
+ To adjust your budget or rate of pay, click on{' '}
+
+
+
);
};
diff --git a/src/popup/pages/Settings.tsx b/src/popup/pages/Settings.tsx
index 4133de42..d620d8c3 100644
--- a/src/popup/pages/Settings.tsx
+++ b/src/popup/pages/Settings.tsx
@@ -1,9 +1,85 @@
import React from 'react';
-import { WalletInformation } from '@/popup/components/WalletInformation';
+import { useLocation } from 'react-router-dom';
+import * as Tabs from '@radix-ui/react-tabs';
+import { WalletInformation } from '@/popup/components/Settings/WalletInformation';
+import { BudgetScreen } from '@/popup/components/Settings/Budget';
+import { RateOfPayScreen } from '@/popup/components/Settings/RateOfPay';
+import { cn } from '@/shared/helpers';
import { usePopupState } from '@/popup/lib/context';
+import { useLocalStorage } from '@/popup/lib/hooks';
+
+const TABS = [
+ { id: 'wallet', title: 'Wallet' },
+ { id: 'budget', title: 'Budget' },
+ { id: 'wmRate', title: 'Rate' },
+];
+
+const isValidTabId = (id: string) => {
+ return TABS.some((e) => e.id === id);
+};
export const Component = () => {
- const { state } = usePopupState();
+ const {
+ state: { balance, grants, publicKey, walletAddress },
+ } = usePopupState();
+ const location = useLocation();
+ const [storedTabId, setStoredTabId] = useLocalStorage(
+ 'settings.tabId',
+ TABS[0].id,
+ { maxAge: 10 * 60 * 1000, validate: isValidTabId },
+ );
+ const tabIdFromState =
+ location.state?.tabId && isValidTabId(location.state?.tabId)
+ ? (location.state.tabId as string)
+ : null;
+ const [currentTabId, setCurrentTabId] = React.useState(
+ tabIdFromState ?? storedTabId ?? TABS[0].id,
+ );
+
+ return (
+ {
+ setCurrentTabId(id);
+ setStoredTabId(id);
+ }}
+ >
+
+ {TABS.map(({ id, title }) => (
+
+ {title}
+
+ ))}
+
+
+
+
+
+
+
+
+
- return ;
+
+
+
+
+ );
};
diff --git a/src/shared/helpers.ts b/src/shared/helpers.ts
index 2a4ea470..7a5005be 100644
--- a/src/shared/helpers.ts
+++ b/src/shared/helpers.ts
@@ -18,12 +18,17 @@ export const cn = (...inputs: CxOptions) => {
return twMerge(cx(inputs));
};
-export const formatCurrency = (value: any): string => {
- if (value < 1) {
- return `${Math.round(value * 100)}c`;
- } else {
- return `$${parseFloat(value).toFixed(2)}`;
- }
+export const formatCurrency = (
+ value: string | number,
+ currency: string,
+ maximumFractionDigits = 2,
+ locale?: string,
+): string => {
+ return new Intl.NumberFormat(locale, {
+ style: 'currency',
+ currency,
+ maximumFractionDigits,
+ }).format(Number(value));
};
const isWalletAddress = (o: any): o is WalletAddress => {
diff --git a/tailwind.config.ts b/tailwind.config.ts
index b40ced6a..48ab8629 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -16,6 +16,8 @@ module.exports = {
},
textColor: {
primary: 'rgb(var(--text-primary) / )',
+ secondary: 'rgb(var(--text-secondary) / )',
+ 'secondary-dark': 'rgb(var(--text-secondary-dark) / )',
weak: 'rgb(var(--text-weak) / )',
medium: 'rgb(var(--text-medium) / )',
strong: 'rgb(var(--text-strong) / )',
diff --git a/tests/e2e/simple.spec.ts b/tests/e2e/simple.spec.ts
index 3add4e6c..bc6330b1 100644
--- a/tests/e2e/simple.spec.ts
+++ b/tests/e2e/simple.spec.ts
@@ -38,7 +38,4 @@ test('should monetize site with single wallet address', async ({
await expect(popup.getByRole('button', { name: 'Send now' })).toBeVisible();
expect(await popup.getByRole('textbox').all()).toHaveLength(1);
-
- await expect(popup.getByLabel('Continuous payment stream')).toBeVisible();
- expect(await popup.getByRole('switch').all()).toHaveLength(1);
});