Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add currency switcher #164

Merged
merged 2 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/components/BitcoinConnectElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ export class BitcoinConnectElement extends InternalElement {
this._modalOpen = store.getState().modalOpen;

// TODO: handle unsubscribe
store.subscribe((store) => {
this._connected = store.connected;
this._connecting = store.connecting;
this._connectorName = store.connectorName;
this._appName = store.appName;
this._filters = store.filters;
this._error = store.error;
this._route = store.route;
this._modalOpen = store.modalOpen;
store.subscribe((currentState) => {
this._connected = currentState.connected;
this._connecting = currentState.connecting;
this._connectorName = currentState.connectorName;
this._appName = currentState.appName;
this._filters = currentState.filters;
this._error = currentState.error;
this._route = currentState.route;
this._modalOpen = currentState.modalOpen;
});
}

Expand Down
57 changes: 48 additions & 9 deletions src/components/bc-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {BitcoinConnectElement} from './BitcoinConnectElement.js';
import {withTwind} from './twind/withTwind.js';
import {classes} from './css/classes.js';
import store from '../state/store.js';
import {fiat} from '@getalby/lightning-tools';

/**
* Displays the balance of the connected wallet (could be sats or fiat)
Expand All @@ -13,15 +14,26 @@ export class Balance extends withTwind()(BitcoinConnectElement) {
@state()
_balance: string | undefined;

@state()
_balanceSats: number | undefined;

@state() _selectedCurrency: string | undefined;

constructor() {
super();

this._loadBalance();

store.subscribe((currentStore, prevStore) => {
this._selectedCurrency = store.getState().currency;
store.subscribe((currentState, prevState) => {
this._selectedCurrency = currentState.currency;
if (currentState.currency !== prevState.currency) {
this._convertBalance();
}

if (
currentStore.connected !== prevStore.connected &&
currentStore.connected
currentState.connected !== prevState.connected &&
currentState.connected
) {
this._loadBalance();
}
Expand All @@ -35,10 +47,37 @@ export class Balance extends withTwind()(BitcoinConnectElement) {
'text-brand-mixed'
]}"
>
<span class="font-mono">${this._balance || 0} </span>&nbsp;sats</span
<span class="font-mono">${this._balance || 'Loading...'} </span></span
>`;
}

private async _convertBalance() {
if (!this._balanceSats) {
return;
}

if (this._selectedCurrency && this._selectedCurrency !== 'sats') {
try {
const fiatValue = await fiat.getFiatValue({
satoshi: this._balanceSats,
currency: this._selectedCurrency,
});
const convertedValue = parseFloat(fiatValue.toFixed(2));
this._balance = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: this._selectedCurrency,
}).format(convertedValue);
} catch (error) {
console.error(error);
}
} else {
this._balance =
this._balanceSats.toLocaleString(undefined, {
useGrouping: true,
}) + ' sats';
}
}

private _loadBalance() {
(async () => {
try {
Expand All @@ -54,11 +93,11 @@ export class Balance extends withTwind()(BitcoinConnectElement) {
'The current WebLN provider does not support getBalance'
);
}
const balance = await provider.getBalance();

this._balance = balance?.balance.toLocaleString(undefined, {
useGrouping: true,
});
const balanceResponse = await provider.getBalance();
if (balanceResponse) {
this._balanceSats = balanceResponse.balance;
this._convertBalance();
}
} catch (error) {
this._balance = '⚠️';
// FIXME: better error handling
Expand Down
97 changes: 97 additions & 0 deletions src/components/bc-currency-switcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {customElement, state} from 'lit/decorators.js';
import {BitcoinConnectElement} from './BitcoinConnectElement';
import {withTwind} from './twind/withTwind';
import {html} from 'lit';
import './internal/bci-button';
import './bc-connector-list';
import './bc-balance';
import {classes} from './css/classes';
import store from '../state/store';
import {crossIcon} from './icons/crossIcon';

@customElement('bc-currency-switcher')
export class CurrencySwitcher extends withTwind()(BitcoinConnectElement) {
@state() _isSwitchingCurrency = false;
@state() _selectedCurrency: string | undefined;

constructor() {
super();
this._selectedCurrency = store.getState().currency;

// TODO: handle unsubscribe
store.subscribe((currentState) => {
this._selectedCurrency = currentState.currency;
});
}

override render() {
const currencies: {name: string; value: string}[] = [
{name: 'SATS', value: 'sats'},
];

try {
// In case Intl.supportedValuesOf('currency') is unsupported,
// a fallback will be used
currencies.push(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...((Intl as any).supportedValuesOf('currency') as string[]).map(
(value) => ({name: value, value})
)
);
} catch (error) {
currencies.push({
name: 'USD',
value: 'USD',
});
}
return html`<div class="flex justify-center items-center gap-2">
${this._isSwitchingCurrency
? html`
<br />
<select
class="${classes[
'text-brand-mixed'
]} bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
@change=${(e: Event) => this._handleCurrencyChange(e)}
>
${currencies.map(
(currency) => html`
<option
value=${currency.value}
?selected=${this._selectedCurrency === currency.value}
>
${currency.name}
</option>
`
)}
</select>
<div
class="${classes.interactive} ${classes['text-neutral-tertiary']}"
@click=${this._toggleSelectVisibility}
>
${crossIcon}
</div>
`
: html`<div
class="${classes.interactive}"
@click=${this._toggleSelectVisibility}
>
<slot></slot>
</div>`}
</div>`;
}

private _toggleSelectVisibility() {
this._isSwitchingCurrency = !this._isSwitchingCurrency;
}
private _handleCurrencyChange(e: Event) {
const selectedCurrency = (e.target as HTMLSelectElement).value;
store.getState().setCurrency(selectedCurrency);
}
}

declare global {
interface HTMLElementTagNameMap {
'bc-currency-switcher': CurrencySwitcher;
}
}
5 changes: 4 additions & 1 deletion src/components/bc-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {classes} from './css/classes';
import {disconnectSection} from './templates/disconnectSection';
import './bc-balance';
import store from '../state/store';
import './bc-currency-switcher';

// TODO: split up this component into disconnected and connected
@customElement('bc-start')
Expand Down Expand Up @@ -39,7 +40,9 @@ export class Start extends withTwind()(BitcoinConnectElement) {
]}"
>Balance</span
>
<bc-balance class="text-2xl"></bc-balance>`
<bc-currency-switcher>
<bc-balance class="text-2xl"></bc-balance>
</bc-currency-switcher>`
: html` <span
class="text-lg font-medium mt-4 -mb-4 ${classes[
'text-neutral-secondary'
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export * from './components/bc-button';
export * from './components/bc-pay-button';
export * from './components/bc-modal';
export * from './components/bc-connector-list';
export * from './components/bc-balance';
export * from './components/bc-currency-switcher';
export * from './components/pages/bc-send-payment';
export * from './components/connectors/index';
export * from './components/flows/bc-connect';
Expand Down
5 changes: 5 additions & 0 deletions src/state/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ function loadConfig() {
const config = JSON.parse(configJson) as ConnectorConfig;
store.getState().connect(config);
}

const currency = window.localStorage.getItem('bc:currency');
if (currency) {
store.getState().setCurrency(currency);
}
}

function addEventListeners() {
Expand Down
12 changes: 12 additions & 0 deletions src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ interface Store {
readonly error: string | undefined;
readonly modalOpen: boolean;
readonly provider: WebLNProvider | undefined;
readonly currency: string | undefined;

connect(config: ConnectorConfig): void;
disconnect(): void;
Expand All @@ -47,6 +48,7 @@ interface Store {
setError(error: string | undefined): void;
clearRouteHistory(): void;
setModalOpen(modalOpen: boolean): void;
setCurrency(currency: string | undefined): void;

// provider functions
// getBalance(): Promise<number | undefined>;
Expand All @@ -57,6 +59,7 @@ const store = createStore<Store>((set, get) => ({
route: '/start',
routeHistory: [],
modalOpen: false,
currency: undefined,
showBalance: undefined,
connected: false,
connecting: false,
Expand Down Expand Up @@ -146,6 +149,15 @@ const store = createStore<Store>((set, get) => ({
setError: (error) => {
set({error});
},
setCurrency: (currency) => {
if (currency) {
window.localStorage.setItem('bc:currency', currency);
} else {
window.localStorage.removeItem('bc:currency');
}
set({currency});
},

/*async getBalance() {
try {
if (!window.webln) {
Expand Down