diff --git a/README.md b/README.md
index 95fa75ac3c..9e7e176887 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Alby is open-source and currently in alpha stage. Our goal is to create the best
We have a channel on the [bitcoin.design](https://bitcoin.design/) Slack community [#lightning-browser-extension](https://bitcoindesign.slack.com/archives/C02591ADXM2) and a [Telegram group](https://t.me/getAlby). Come and join us!
-We also do a bi-weekly call on Thursday at [13:00 UTC](https://everytimezone.com/s/436cf0d2) on [Jitsi](https://meet.fulmo.org/AlbyCommunityCall)
+We also do a bi-weekly call on Thursday at [15:00 UTC](https://everytimezone.com/s/436cf0d2) on [Jitsi](https://meet.fulmo.org/AlbyCommunityCall)
## Browser Support
diff --git a/jest.setup.js b/jest.setup.js
index 8dbe4dbe47..f3ec823769 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -7,6 +7,11 @@ import "@testing-library/jest-dom";
// https://github.com/mswjs/examples/tree/master/examples/rest-react
import { server } from "./tests/unit/helpers/server";
+import { TextEncoder, TextDecoder } from 'util'
+global.TextEncoder = TextEncoder
+global.TextDecoder = TextDecoder
+
+
// fix "This script should only be loaded in a browser extension." e.g. https://github.com/mozilla/webextension-polyfill/issues/218
if (!chrome.runtime.id) chrome.runtime.id = "history-delete";
diff --git a/package.json b/package.json
index 2fe5f6763f..d6405400ed 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightning-browser-extension",
- "version": "1.22.1",
+ "version": "1.24.0",
"description": "Lightning browser extension",
"private": true,
"repository": "https://github.com/bumi/lightning-browser-extension.git",
@@ -37,32 +37,35 @@
"dependencies": {
"@bitcoin-design/bitcoin-icons-react": "^0.1.9",
"@headlessui/react": "^1.7.7",
- "@noble/secp256k1": "^1.7.0",
+ "lnc-web": "git+https://github.com/bumi/lnc-web.git#remove-window-dependency",
+ "@noble/secp256k1": "^1.7.1",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@vespaiach/axios-fetch-adapter": "^0.3.0",
+ "avvvatars-react": "^0.4.2",
"axios": "^0.27.2",
"bech32": "^2.0.0",
"bolt11": "^1.4.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.7",
- "dexie": "^3.2.2",
+ "dexie": "^3.2.3",
"elliptic": "^6.5.4",
- "html5-qrcode": "^2.3.1",
- "i18next": "^22.4.6",
+ "html5-qrcode": "^2.3.4",
+ "i18next": "^22.4.9",
"i18next-browser-languagedetector": "^7.0.1",
- "lnmessage": "^0.0.14",
+ "lnmessage": "^0.0.18",
"lodash.merge": "^4.6.2",
"lodash.pick": "^4.4.0",
+ "lodash.snakecase": "^4.1.1",
"pubsub-js": "^1.9.4",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.2.0",
- "react-i18next": "^12.1.1",
+ "react-i18next": "^12.1.4",
"react-loading-skeleton": "^3.1.0",
"react-modal": "^3.16.1",
- "react-qr-code": "^2.0.8",
- "react-router-dom": "^6.6.1",
+ "react-qr-code": "^2.0.11",
+ "react-router-dom": "^6.7.0",
"react-toastify": "^9.1.1",
"stream": "^0.0.2",
"tailwindcss": "^3.2.4",
@@ -71,28 +74,29 @@
"zustand": "^3.7.2"
},
"devDependencies": {
- "@commitlint/cli": "^17.3.0",
- "@commitlint/config-conventional": "^17.3.0",
+ "@commitlint/cli": "^17.4.1",
+ "@commitlint/config-conventional": "^17.4.2",
"@jest/types": "^29.3.1",
- "@playwright/test": "^1.29.1",
- "@swc/core": "^1.3.24",
+ "@playwright/test": "^1.29.2",
+ "@swc/core": "^1.3.27",
"@swc/jest": "^0.2.24",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
- "@types/chrome": "^0.0.206",
+ "@types/chrome": "^0.0.210",
"@types/crypto-js": "^4.1.1",
"@types/elliptic": "^6.4.14",
"@types/lodash.merge": "^4.6.7",
"@types/lodash.pick": "^4.4.0",
+ "@types/lodash.snakecase": "^4.1.1",
"@types/pubsub-js": "^1.8.3",
"@types/react-dom": "^18.0.10",
"@types/react-modal": "^3.13.1",
"@types/uuid": "^9.0.0",
- "@types/webextension-polyfill": "^0.9.2",
- "@typescript-eslint/eslint-plugin": "^5.45.1",
- "@typescript-eslint/parser": "^5.45.1",
+ "@types/webextension-polyfill": "^0.10.0",
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
+ "@typescript-eslint/parser": "^5.48.0",
"autoprefixer": "^10.4.13",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
@@ -102,28 +106,28 @@
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2",
"del-cli": "^5.0.0",
- "eslint": "^8.30.0",
- "eslint-config-prettier": "^8.5.0",
- "eslint-plugin-import": "^2.26.0",
- "eslint-plugin-react": "^7.31.11",
+ "eslint": "^8.32.0",
+ "eslint-config-prettier": "^8.6.0",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-react": "^7.32.1",
"eslint-plugin-react-hooks": "^4.6.0",
- "fake-indexeddb": "^3.1.8",
+ "fake-indexeddb": "^4.0.1",
"filemanager-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.5.0",
- "husky": "^8.0.2",
+ "husky": "^8.0.3",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
- "jest-webextension-mock": "^3.8.7",
+ "jest-webextension-mock": "^3.8.8",
"lint-staged": "^13.1.0",
"mini-css-extract-plugin": "^2.7.2",
- "msw": "^0.49.2",
- "postcss": "^8.4.20",
+ "msw": "^1.0.0",
+ "postcss": "^8.4.21",
"postcss-cli": "^10.1.0",
"postcss-loader": "^7.0.2",
"pptr-testing-library": "^0.7.0",
- "prettier": "^2.8.1",
+ "prettier": "^2.8.3",
"process": "^0.11.10",
- "puppeteer": "^19.4.1",
+ "puppeteer": "^19.5.2",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"stream-browserify": "^3.0.0",
diff --git a/src/app/components/AccountMenu/index.tsx b/src/app/components/AccountMenu/index.tsx
index 913f14984c..618fa2b802 100644
--- a/src/app/components/AccountMenu/index.tsx
+++ b/src/app/components/AccountMenu/index.tsx
@@ -1,11 +1,11 @@
import {
AddressBookIcon,
CaretDownIcon,
+ CheckIcon,
PlusIcon,
} from "@bitcoin-design/bitcoin-icons-react/filled";
-import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
-import { WalletIcon } from "@bitcoin-design/bitcoin-icons-react/outline";
-import { useState, useEffect } from "react";
+import Avvvatars from "avvvatars-react";
+import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Skeleton from "react-loading-skeleton";
import { useNavigate } from "react-router-dom";
@@ -73,10 +73,10 @@ function AccountMenu({ showOptions = true }: Props) {
}
return (
-
-
-
-
+
+
{t("screen_reader")}
-
+
{t("title")}
{Object.keys(accounts).map((accountId) => {
@@ -126,14 +126,16 @@ function AccountMenu({ showOptions = true }: Props) {
disabled={loading}
title={account.name}
>
-
-
+
+
{account.name}
{accountId === authAccount?.id && (
diff --git a/src/app/components/AllowanceMenu/index.tsx b/src/app/components/AllowanceMenu/index.tsx
index 81bfe5aa43..b46dd66963 100644
--- a/src/app/components/AllowanceMenu/index.tsx
+++ b/src/app/components/AllowanceMenu/index.tsx
@@ -2,14 +2,14 @@ import { GearIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/outline";
import Setting from "@components/Setting";
import Toggle from "@components/form/Toggle";
-import { useState, useEffect } from "react";
import type { FormEvent } from "react";
+import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Modal from "react-modal";
import { toast } from "react-toastify";
import { useSettings } from "~/app/context/SettingsContext";
import msg from "~/common/lib/msg";
-import type { Allowance } from "~/types";
+import type { Allowance, Permission } from "~/types";
import Button from "../Button";
import Menu from "../Menu";
@@ -33,8 +33,47 @@ function AllowanceMenu({ allowance, onEdit, onDelete }: Props) {
const [budget, setBudget] = useState("");
const [lnurlAuth, setLnurlAuth] = useState(false);
const [fiatAmount, setFiatAmount] = useState("");
+
+ const [originalPermissions, setOriginalPermissions] = useState<
+ Permission[] | null
+ >(null);
+ const [permissions, setPermissions] = useState(null);
+ const [isLoadingPermissions, setIsLoadingPermissions] = useState(true);
+
const { t } = useTranslation("components", { keyPrefix: "allowance_menu" });
const { t: tCommon } = useTranslation("common");
+ const { t: tPermissions } = useTranslation("permissions");
+
+ const hasPermissions = !isLoadingPermissions && !!permissions?.length;
+
+ const enableSubmit =
+ parseInt(budget) !== allowance.totalBudget ||
+ lnurlAuth !== allowance.lnurlAuth ||
+ getChangedPermissionsIds().length;
+
+ useEffect(() => {
+ const fetchPermissions = async () => {
+ try {
+ const permissionResponse = await msg.request<{
+ permissions: Permission[];
+ }>("listPermissions", {
+ id: allowance.id,
+ });
+
+ const permissions: Permission[] = permissionResponse.permissions;
+
+ setOriginalPermissions(permissions);
+ setPermissions(permissions);
+ } catch (e) {
+ console.error(e);
+ if (e instanceof Error) toast.error(e.message);
+ } finally {
+ setIsLoadingPermissions(false);
+ }
+ };
+
+ !permissions && fetchPermissions();
+ }, [allowance.id, permissions]);
useEffect(() => {
if (budget !== "" && showFiat) {
@@ -65,15 +104,39 @@ function AllowanceMenu({ allowance, onEdit, onDelete }: Props) {
setIsOpen(false);
}
+ function getChangedPermissionsIds(): number[] {
+ if (!permissions || !originalPermissions) return [];
+ const ids = permissions
+ .filter((prm, i) => prm.enabled !== originalPermissions[i].enabled)
+ .map((prm) => prm.id);
+ return ids;
+ }
+
async function updateAllowance() {
- await msg.request("updateAllowance", {
- id: allowance.id,
- totalBudget: parseInt(budget),
- lnurlAuth,
- });
-
- onEdit && onEdit();
- closeModal();
+ try {
+ await msg.request("updateAllowance", {
+ id: allowance.id,
+ totalBudget: parseInt(budget),
+ lnurlAuth,
+ });
+
+ const changedIds = getChangedPermissionsIds();
+
+ if (changedIds.length) {
+ await msg.request("deletePermissionsById", {
+ ids: changedIds,
+ });
+ }
+
+ /* DB is updated, let´s update the original permissions
+ to the updated permission in local state too */
+ setOriginalPermissions(permissions);
+
+ onEdit && onEdit();
+ closeModal();
+ } catch (e) {
+ console.error(e);
+ }
}
return (
@@ -115,6 +178,7 @@ function AllowanceMenu({ allowance, onEdit, onDelete }: Props) {
contentLabel={t("edit_allowance.screen_reader")}
overlayClassName="bg-black bg-opacity-25 fixed inset-0 flex justify-center items-center p-5"
className="rounded-lg bg-white w-full max-w-lg"
+ style={{ content: { maxHeight: "90vh" } }}
>
@@ -131,7 +195,10 @@ function AllowanceMenu({ allowance, onEdit, onDelete }: Props) {
updateAllowance();
}}
>
-
+
setBudget(e.target.value)}
/>
-
- setLnurlAuth(!lnurlAuth)}
- />
-
+
+ setLnurlAuth(!lnurlAuth)}
+ />
+
+
+
+ {hasPermissions && (
+
+
+ {t("edit_permissions")}
+
+
+ {permissions.map((permission) => (
+ <>
+ lnd.getinfo
+ nostr/nip04decrypt --> nostr.nip04decrypt */
+ >
+ {
+ setPermissions(
+ permissions.map((prm) =>
+ prm.id === permission.id
+ ? { ...prm, enabled: !prm.enabled }
+ : prm
+ )
+ );
+ }}
+ />
+
+ >
+ ))}
+
+
+ )}
@@ -161,10 +277,7 @@ function AllowanceMenu({ allowance, onEdit, onDelete }: Props) {
type="submit"
label={tCommon("actions.save")}
primary
- disabled={
- parseInt(budget) === allowance.totalBudget &&
- lnurlAuth === allowance.lnurlAuth
- }
+ disabled={!enableSubmit}
/>
diff --git a/src/app/components/BalanceCard/index.test.tsx b/src/app/components/BalanceCard/index.test.tsx
deleted file mode 100644
index 43775d6494..0000000000
--- a/src/app/components/BalanceCard/index.test.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
-
-import type { Props } from "./index";
-import BalanceCard from "./index";
-
-const props: Props = {
- alias: "100",
- crypto: "200",
- fiat: "300",
-};
-
-describe("ConfirmPayment", () => {
- test("render", async () => {
- render(
-
-
-
- );
-
- expect(screen.getByText("100")).toBeInTheDocument();
- expect(screen.getByText("200")).toBeInTheDocument();
- expect(screen.getByText("300")).toBeInTheDocument();
- });
-});
diff --git a/src/app/components/BalanceCard/index.tsx b/src/app/components/BalanceCard/index.tsx
deleted file mode 100644
index eb2f736aac..0000000000
--- a/src/app/components/BalanceCard/index.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import Skeleton from "react-loading-skeleton";
-
-export type Props = {
- alias: string;
- crypto: string;
- fiat: string;
-};
-
-const skeletonStyle = {
- opacity: 0.5,
-};
-
-function BalanceCard({ alias, crypto, fiat }: Props) {
- return (
-
-
- {alias || }
-
-
- {crypto || }
-
-
- {fiat || }
-
-
- );
-}
-
-export default BalanceCard;
diff --git a/src/app/components/Button/index.tsx b/src/app/components/Button/index.tsx
index 34b5900012..71e26a0766 100644
--- a/src/app/components/Button/index.tsx
+++ b/src/app/components/Button/index.tsx
@@ -31,6 +31,7 @@ const Button = forwardRef(
outline = false,
loading = false,
flex = false,
+ className,
}: Props,
ref: Ref
) => {
@@ -54,7 +55,8 @@ const Button = forwardRef(
"hover:bg-gray-50 dark:hover:bg-surface-16dp",
disabled ? "cursor-default opacity-60" : "cursor-pointer",
flex && "flex-1",
- "inline-flex justify-center items-center font-medium rounded-md shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-orange-bitcoin transition duration-150"
+ "inline-flex justify-center items-center font-medium rounded-md shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-orange-bitcoin transition duration-150",
+ !!className && className
)}
onClick={onClick}
disabled={disabled}
diff --git a/src/app/components/Card/index.tsx b/src/app/components/Card/index.tsx
index 5f1eeb7436..fdc8e2d096 100644
--- a/src/app/components/Card/index.tsx
+++ b/src/app/components/Card/index.tsx
@@ -14,14 +14,16 @@ export default function Card({
currency,
}: Props) {
return (
-
+
{alias}
{satoshis}
-
- {fiat} {currency}
-
+ {fiat && currency && (
+
+ {fiat} {currency}
+
+ )}
);
}
diff --git a/src/app/components/CloseableCard/index.test.tsx b/src/app/components/CloseableCard/index.test.tsx
new file mode 100644
index 0000000000..0bc0452546
--- /dev/null
+++ b/src/app/components/CloseableCard/index.test.tsx
@@ -0,0 +1,23 @@
+import { render, screen } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
+
+import type { Props } from "./index";
+import CloseableCard from "./index";
+
+const props: Props = {
+ title: "Card Title",
+ description: "Card description",
+ handleClose: () => ({}),
+};
+
+describe("CloseableCard", () => {
+ test("render label", async () => {
+ render(
+
+
+
+ );
+
+ expect(await screen.findByText("Card Title")).toBeInTheDocument();
+ });
+});
diff --git a/src/app/components/CloseableCard/index.tsx b/src/app/components/CloseableCard/index.tsx
new file mode 100644
index 0000000000..8568444d5e
--- /dev/null
+++ b/src/app/components/CloseableCard/index.tsx
@@ -0,0 +1,44 @@
+import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
+
+export type Props = {
+ title: string;
+ description: string | JSX.Element | (JSX.Element | string)[];
+ buttons?: JSX.Element[];
+ handleClose: () => void;
+};
+
+export default function CloseableCard({
+ title,
+ description,
+ buttons,
+ handleClose,
+}: Props) {
+ const uiDescription = Array.isArray(description) ? (
+
+ {description.map((text, index) => (
+ - {text}
+ ))}
+
+ ) : (
+
+ {description}
+
+ );
+ return (
+
+
+
{title}
+
+ {uiDescription}
+
+ {!!buttons?.length && (
+
{buttons}
+ )}
+
+ );
+}
diff --git a/src/app/components/ConnectorForm/index.tsx b/src/app/components/ConnectorForm/index.tsx
index efeab321d4..8f874f9f60 100644
--- a/src/app/components/ConnectorForm/index.tsx
+++ b/src/app/components/ConnectorForm/index.tsx
@@ -29,17 +29,48 @@ function ConnectorForm({
const { t: tCommon } = useTranslation("common");
const navigate = useNavigate();
+ const media = (
+
+ {video ? (
+
+
+
+ ) : (
+ <>
+
+

+

+
+ >
+ )}
+
+ );
+
return (