From 1930c82f847b8080b1baf3e38eb95c4fa8027fb1 Mon Sep 17 00:00:00 2001 From: Adarsh Rawat Date: Sat, 12 Aug 2023 12:44:15 +0530 Subject: [PATCH 01/18] Fix: Updated "Bitcoin Beach Wallet" connector --- src/app/router/connectorRoutes.tsx | 18 +++++++++--------- .../screens/connectors/ConnectGaloy/index.tsx | 16 +++++++--------- src/i18n/locales/cs/translation.json | 6 +++--- src/i18n/locales/da/translation.json | 6 +++--- src/i18n/locales/de/translation.json | 6 +++--- src/i18n/locales/en/translation.json | 6 +++--- src/i18n/locales/eo/translation.json | 2 +- src/i18n/locales/es/translation.json | 6 +++--- src/i18n/locales/fa/translation.json | 6 +++--- src/i18n/locales/fi/translation.json | 6 +++--- src/i18n/locales/fr/translation.json | 4 ++-- src/i18n/locales/hi/translation.json | 6 +++--- src/i18n/locales/id/translation.json | 6 +++--- src/i18n/locales/it/translation.json | 6 +++--- src/i18n/locales/ja/translation.json | 6 +++--- src/i18n/locales/mr/translation.json | 6 +++--- src/i18n/locales/nl/translation.json | 2 +- src/i18n/locales/pl/translation.json | 6 +++--- src/i18n/locales/pt_BR/translation.json | 6 +++--- src/i18n/locales/ro/translation.json | 6 +++--- src/i18n/locales/ru/translation.json | 2 +- src/i18n/locales/sv/translation.json | 6 +++--- src/i18n/locales/tl/translation.json | 2 +- src/i18n/locales/uk/translation.json | 2 +- src/i18n/locales/zh_Hans/translation.json | 6 +++--- src/i18n/locales/zh_Hant/translation.json | 6 +++--- static/assets/icons/galoy_bitcoin_beach.png | Bin 20401 -> 0 bytes static/assets/icons/galoy_blink.png | Bin 0 -> 34904 bytes webpack.config.js | 6 +++--- 29 files changed, 80 insertions(+), 82 deletions(-) delete mode 100644 static/assets/icons/galoy_bitcoin_beach.png create mode 100644 static/assets/icons/galoy_blink.png diff --git a/src/app/router/connectorRoutes.tsx b/src/app/router/connectorRoutes.tsx index 7b8967ee00..e9e782b491 100644 --- a/src/app/router/connectorRoutes.tsx +++ b/src/app/router/connectorRoutes.tsx @@ -20,8 +20,8 @@ import btcpay from "/static/assets/icons/btcpay.svg"; import citadel from "/static/assets/icons/citadel.png"; import core_ln from "/static/assets/icons/core_ln.svg"; import eclair from "/static/assets/icons/eclair.jpg"; -import galoyBitcoinBeach from "/static/assets/icons/galoy_bitcoin_beach.png"; import galoyBitcoinJungle from "/static/assets/icons/galoy_bitcoin_jungle.png"; +import galoyBlink from "/static/assets/icons/galoy_blink.png"; import kolliderLogo from "/static/assets/icons/kollider.png"; import lightning_node from "/static/assets/icons/lightning_node.png"; import lightning_terminal from "/static/assets/icons/lightning_terminal.png"; @@ -63,7 +63,7 @@ interface ConnectorRoute extends Route { } const galoyPaths: { [key: string]: keyof typeof galoyUrls } = { - bitcoinBeach: "galoy-bitcoin-beach", + blink: "galoy-blink", bitcoinJungle: "galoy-bitcoin-jungle", }; @@ -163,11 +163,11 @@ const connectorMap: { [key: string]: ConnectorRoute } = { logo: kolliderLogo, children: kolliderConnectorRoutes, }, - [galoyPaths.bitcoinBeach]: { - path: galoyPaths.bitcoinBeach, - element: , - title: i18n.t("translation:choose_connector.bitcoin_beach.title"), - logo: galoyBitcoinBeach, + [galoyPaths.blink]: { + path: galoyPaths.blink, + element: , + title: i18n.t("translation:choose_connector.blink.title"), + logo: galoyBlink, }, [galoyPaths.bitcoinJungle]: { path: galoyPaths.bitcoinJungle, @@ -262,7 +262,7 @@ function getConnectorRoutes(): ConnectorRoute[] { connectorMap["lnd-hub-bluewallet"], connectorMap["eclair"], connectorMap["btcpay"], - connectorMap[galoyPaths.bitcoinBeach], + connectorMap[galoyPaths.blink], connectorMap[galoyPaths.bitcoinJungle], getDistribution("citadel"), getDistribution("umbrel"), @@ -306,4 +306,4 @@ function renderRoutes(routes: (ChildRoute | ConnectorRoute)[]) { }); } -export { getConnectorRoutes, ConnectorRoute, renderRoutes }; +export { ConnectorRoute, getConnectorRoutes, renderRoutes }; diff --git a/src/app/screens/connectors/ConnectGaloy/index.tsx b/src/app/screens/connectors/ConnectGaloy/index.tsx index d007c8ae90..4052f8a4c3 100644 --- a/src/app/screens/connectors/ConnectGaloy/index.tsx +++ b/src/app/screens/connectors/ConnectGaloy/index.tsx @@ -9,18 +9,16 @@ import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; import msg from "~/common/lib/msg"; -import galoyBitcoinBeach from "/static/assets/icons/galoy_bitcoin_beach.png"; import galoyBitcoinJungle from "/static/assets/icons/galoy_bitcoin_jungle.png"; +import galoyBlink from "/static/assets/icons/galoy_blink.png"; export const galoyUrls = { - "galoy-bitcoin-beach": { - i18nPrefix: "bitcoin_beach", - label: "Bitcoin Beach Wallet", - website: "https://galoy.io/bitcoin-beach-wallet/", - logo: galoyBitcoinBeach, - url: - process.env.BITCOIN_BEACH_GALOY_URL || - "https://api.mainnet.galoy.io/graphql/", + "galoy-blink": { + i18nPrefix: "blink", + label: "Blink Wallet", + website: "https://www.blink.sv/", + logo: galoyBlink, + url: process.env.BLINK_GALOY_URL || "https://api.mainnet.galoy.io/graphql/", }, "galoy-bitcoin-jungle": { i18nPrefix: "bitcoin_jungle", diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 65471a881c..904968be8e 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -167,10 +167,10 @@ "placeholder": "onion-adresa-vaseho-uzlu:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink", "page": { - "title": "Připojení k <0>Bitcoin Beach Wallet" + "title": "Připojení k <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/da/translation.json b/src/i18n/locales/da/translation.json index 74b4495e46..237ef08c6e 100644 --- a/src/i18n/locales/da/translation.json +++ b/src/i18n/locales/da/translation.json @@ -208,10 +208,10 @@ "placeholder": "din-nodes-onion-adresse:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Opret forbindelse til <0>Bitcoin Beach Wallet" + "title": "Opret forbindelse til <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 85c80cb261..8da61ec31f 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -173,10 +173,10 @@ }, "title": "Start9" }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Verbinden mit <0>Bitcoin Beach Wallet" + "title": "Verbinden mit <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index ba7b237d15..eb78fe34a6 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -218,10 +218,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Connect to <0>Bitcoin Beach Wallet" + "title": "Connect to <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/eo/translation.json b/src/i18n/locales/eo/translation.json index 6d6bfd2cee..209be4e390 100644 --- a/src/i18n/locales/eo/translation.json +++ b/src/i18n/locales/eo/translation.json @@ -166,7 +166,7 @@ "placeholder": "" } }, - "bitcoin_beach": { + "blink": { "title": "", "page": { "title": "" diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 6018a3df5a..638d178bc2 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -145,10 +145,10 @@ "placeholder": "tu-nodo-direccion onion:puerto" } }, - "bitcoin_beach": { - "title": "Billetera de Bitcoin Beach", + "blink": { + "title": "Billetera de Blink", "page": { - "title": "Conecta a <0>La cartera Bitcoin Beach" + "title": "Conecta a <0>La cartera Blink" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/fa/translation.json b/src/i18n/locales/fa/translation.json index 85f5013214..9d55814d8a 100644 --- a/src/i18n/locales/fa/translation.json +++ b/src/i18n/locales/fa/translation.json @@ -218,10 +218,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "کیف پول Bitcoin Beach", + "blink": { + "title": "کیف پول Blink", "page": { - "title": "اتصال به <0>Bitcoin Beach Wallet" + "title": "اتصال به <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/fi/translation.json b/src/i18n/locales/fi/translation.json index 4895b6acd6..e86eac22fe 100644 --- a/src/i18n/locales/fi/translation.json +++ b/src/i18n/locales/fi/translation.json @@ -166,10 +166,10 @@ "placeholder": "solmusi-sipuli-osoite:portti" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach -lompakko", + "blink": { + "title": "Bitcoin Blink -lompakko", "page": { - "title": "Yhdistä <0>Bitcoin Beach -lompakkoon" + "title": "Yhdistä <0>Blink -lompakkoon" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 425ede57cf..0424797753 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -208,10 +208,10 @@ "placeholder": "adresse-de-votre-node-onion:port" } }, - "bitcoin_beach": { + "blink": { "title": "Portefeuille de plage Bitcoin", "page": { - "title": "Connectez-vous à <0>Bitcoin Beach Wallet" + "title": "Connectez-vous à <0>Bitcoin Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/hi/translation.json b/src/i18n/locales/hi/translation.json index 4592a58d37..755cb4e964 100644 --- a/src/i18n/locales/hi/translation.json +++ b/src/i18n/locales/hi/translation.json @@ -215,10 +215,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "<0>Bitcoin Beach Wallet" + "title": "<0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/id/translation.json b/src/i18n/locales/id/translation.json index a71c83bc1f..54431b1765 100644 --- a/src/i18n/locales/id/translation.json +++ b/src/i18n/locales/id/translation.json @@ -214,10 +214,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Sambungkan dengan <0>Bitcoin Beach Wallet" + "title": "Sambungkan dengan <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 24079fee5d..022a7372d0 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -167,10 +167,10 @@ "placeholder": "indirizzo-onion-del-tuo-nodo:porta" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Connetti il tuo <0>Bitcoin Beach Wallet" + "title": "Connetti il tuo <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index c3269653b3..38c90007b4 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -215,11 +215,11 @@ }, "title": "RaspiBlitz" }, - "bitcoin_beach": { + "blink": { "page": { - "title": "<0>Bitcoin Beach Walletに接続" + "title": "<0>Blink Walletに接続" }, - "title": "Bitcoin Beach Wallet" + "title": "Blink Wallet" }, "title": "ライトニングウォレットを接続", "description": "外部のライトニングウォレットまたはノードに接続する", diff --git a/src/i18n/locales/mr/translation.json b/src/i18n/locales/mr/translation.json index 88451ef422..6e340a86b2 100644 --- a/src/i18n/locales/mr/translation.json +++ b/src/i18n/locales/mr/translation.json @@ -208,10 +208,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "<0>Bitcoin Beach Wallet ला कनेक्ट करा" + "title": "<0>Blink Wallet ला कनेक्ट करा" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/nl/translation.json b/src/i18n/locales/nl/translation.json index cac80045b5..1090ba41c2 100644 --- a/src/i18n/locales/nl/translation.json +++ b/src/i18n/locales/nl/translation.json @@ -167,7 +167,7 @@ "placeholder": "" } }, - "bitcoin_beach": { + "blink": { "title": "", "page": { "title": "" diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 226692343c..fc1e3c3f7e 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -208,10 +208,10 @@ "placeholder": "adres-onion-twojego-wezla:port" } }, - "bitcoin_beach": { - "title": "Portfel Bitcoin Beach", + "blink": { + "title": "Portfel Blink", "page": { - "title": "Podłącz do <0>Bitcoin Beach Wallet" + "title": "Podłącz do <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/pt_BR/translation.json b/src/i18n/locales/pt_BR/translation.json index 4c0113d317..cf783b98de 100644 --- a/src/i18n/locales/pt_BR/translation.json +++ b/src/i18n/locales/pt_BR/translation.json @@ -145,10 +145,10 @@ "placeholder": "seu-endereço-servidor-onion:porta" } }, - "bitcoin_beach": { - "title": "Carteira Bitcoin Beach", + "blink": { + "title": "Carteira Blink", "page": { - "title": "Conecte-se na <0>Carteira Bitcoin Beach" + "title": "Conecte-se na <0>Carteira Blink" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/ro/translation.json b/src/i18n/locales/ro/translation.json index 3086eae740..d6c3094d3e 100644 --- a/src/i18n/locales/ro/translation.json +++ b/src/i18n/locales/ro/translation.json @@ -208,10 +208,10 @@ "placeholder": "adresa-onion-a-nodului-tau:portul" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "Conectare la <0>Bitcoin Beach Wallet" + "title": "Conectare la <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index 35a642e604..02173cfa48 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -208,7 +208,7 @@ "placeholder": "" } }, - "bitcoin_beach": { + "blink": { "title": "", "page": { "title": "" diff --git a/src/i18n/locales/sv/translation.json b/src/i18n/locales/sv/translation.json index 94e1f9b696..5947a74d90 100644 --- a/src/i18n/locales/sv/translation.json +++ b/src/i18n/locales/sv/translation.json @@ -173,10 +173,10 @@ "placeholder": "din-onion-nod-adress:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach plånbok", + "blink": { + "title": "Blink plånbok", "page": { - "title": "Anslut <0>Bitcoin Beach Wallet" + "title": "Anslut <0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/src/i18n/locales/tl/translation.json b/src/i18n/locales/tl/translation.json index d94ad7cb02..594e1378fa 100644 --- a/src/i18n/locales/tl/translation.json +++ b/src/i18n/locales/tl/translation.json @@ -166,7 +166,7 @@ "placeholder": "" } }, - "bitcoin_beach": { + "blink": { "title": "", "page": { "title": "" diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index d83980887f..5761fbf433 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -208,7 +208,7 @@ "placeholder": "" } }, - "bitcoin_beach": { + "blink": { "title": "", "page": { "title": "" diff --git a/src/i18n/locales/zh_Hans/translation.json b/src/i18n/locales/zh_Hans/translation.json index fdde975a45..927f735fef 100644 --- a/src/i18n/locales/zh_Hans/translation.json +++ b/src/i18n/locales/zh_Hans/translation.json @@ -122,11 +122,11 @@ "placeholder": "config=http://your-btc-pay.org/lnd-config/212121/lnd.config" } }, - "bitcoin_beach": { + "blink": { "page": { - "title": "连接<0>比特币海滩钱包" + "title": "连接<0>眨眼钱包" }, - "title": "比特币海滩钱包" + "title": "眨眼钱包" }, "commando": { "config": { diff --git a/src/i18n/locales/zh_Hant/translation.json b/src/i18n/locales/zh_Hant/translation.json index 26de60a38f..0a729d7cf9 100644 --- a/src/i18n/locales/zh_Hant/translation.json +++ b/src/i18n/locales/zh_Hant/translation.json @@ -215,10 +215,10 @@ "placeholder": "your-node-onion-address:port" } }, - "bitcoin_beach": { - "title": "Bitcoin Beach Wallet", + "blink": { + "title": "Blink Wallet", "page": { - "title": "連接<0>Bitcoin Beach Wallet" + "title": "連接<0>Blink Wallet" } }, "bitcoin_jungle": { diff --git a/static/assets/icons/galoy_bitcoin_beach.png b/static/assets/icons/galoy_bitcoin_beach.png deleted file mode 100644 index 409c2f1429a045c4b08fad12b5c7887c5f46c14f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20401 zcmc$_dpuN8_b`4Yq#~3;?z&LPErc+XBne4U?j`q_CNUVpbVHFTiXv1*5t6|$#wf!i zNeE%wW?T|8Lt-$P-yYBNe&2uJ&-=&w^WmJm*I9e5y)Jw0wbwcBpS82vxzh`BHqD2KuOK$9+}yN$ZOcl;IuN~S zHD=S>(--qjx;;DLpJREs+{rH0B6#)IW&ol3nb3mwzdq^l?1W#ArB{~am8TYgnU+_d zpYUD1yE*+tz!Qt06pIkt@#}ZZ(MiWcSK~J)978GSjodpHsi-j&Y?1=t)3;@Bn`3U7 z$6(E4W6iL^CXX>@w^ko)Mw{J1!Q&(0abfWLp{9vQ)8r77N7qbJR+Bde8b9$fPWLf> ze&y(k=V$%T8x(pT&G9gLDXT$uGkWc0_;&U2W*4Kpi-!4*21O1A?=AHS_68+(2BmgK zKG+`laOOza=_BQgasaF=*A&`3^AEwL3I)+SIk%4`_FSu9{ZY+KVl!T0Nk> zSF0CXRW$p-HNibnQFHLQM*wJpUpbADT8uTnp-o?Vxkc_ULr$Loy7Gn$c_W6rF+%}9 zs&r}${3;$BRkR#k%iRLnd$0ahIz6U*X6*M%kP&8rg8NSnTMFJAN6v}OkLa2eYw=(AQ9Q|l$uC8SvU zHB&o}rS*yht~v!QUad$Z?^F-72vN2`~gT1 zzt7{HSig>~qhQ;>q)K^Hng2m1w+%fUzj_{mY`m;b96KKYn;sBK%X5)1|8)2GcJ*I8 zUvjd#`<;dR7b*89U?6Jz|N9fY@&6(JvrjzL>B-RkuU1Z7ww>elcw1*D&KDD?F}E-i z?ktxPc!>TQdiJ$2XindD{u$i`+MhlDDE4hu3to5dy8bdi{x3AY9g@FVxa?*>2+MOHgxP6}MSci; zUs|K@RG@UD}c5A8L7hD0Zq4uqj$sb10N4!F^ADT3fRM?{z*Ns5C4`1|A?7q zE`0~rr|C`<8)*OEJw=6(s?Se_dd)9AqK>sYA;+Al>ibZAV4*7oS|K3*eLM&`4c6}& z+<;XyDyX(Dn(pzh}R>3G2zsPmGf zE@U!iHx7B-JBoOdG$in{*)@Q{fxcu$ZiISp+@a|K;dAOlsknBgpyl^nqMU4Po(i1& zGQC8u(!R?4XoXbh;_3Mo4cfa62}EP3Q`p4B#P5G%6rpH&&iCcIEfD;=gJF%piU(3l zegGCzPLL+|v@4j?5(jZC$n9F!e7gcRQDs$e07j^l>7Mi}gxo^hY8ym!p{ynnxlTz4 zVpB=K-?0-UZB+kl1V_5F=ksKcmg_Bp!MK(h(ao@V_R;)5_**U>xSi=ASBx#i_?RUS{ZDv z3IF*R%!4)uzyx-|V@R2W**ep53X)O&_6w>oLX-NuDr@CO>jiE+UaS2YEa0IeVfH3g;4V#`VoPd7KwiTUOZy_CbY+Z2Y zbJD7j8wLu((FX@0a*`l~L_}ewQCQDJw5Rm@^*I!a&Q#a6@s-+7_S(rY0ZOj%dI&Jt z*RZ%?h(NRrm=h@={o05nKJ0&81zS_z2(4&i+H@8Ehj6o_CyMupkhR6?? z5P@vtABy#)ghMn0ZA`Bdm1+zOcE8SGUH=V8t)o-i0B9zJmYBV95g6+M+6rT~1v-aB z_+stx8tVi!Tm+c+J3JU}QBZH5^V0W7ql2wtXQQ1}D1@H*wgtk$i1 zJr-$v9vIP?7~##@yII9RDUjC*TEF~on*eqjF#I3xK}cpB=ZMxt060VIXQLk*5d>xy z7a#rjAv6^kEr_MY`scCl*OzVpk0mjP@!Rt=CeW!yQiOjj)^j7&OptCBpMl#60hi6J z(-OeCGX|IWTAP;VbPF$9@?BL46T1aENg=huVx$g0CNb}y)&WcGp%0D)Pwc*(YS7sn~#8_S_c%FLzP- z;eMH6@eSAkh6lbEWrh@%*~ytFe;w==n%W%>tB)$G+_-p$FLkEu@WaE9BbD?D7PE~3 zOF+8{tQ>>1ooIo!|#zSQA4!duReyU($UkvnI9?ij^|gPe2=yWB>1)@QRB(z!k7%g$$)fZdJyE|$EJv#&(*VCjvXCu_v zNcxg96eKz$3_Fh{sKuyUVvAfLUoQnwXm>kji|2=^bNQ;~G%L{=Z=gh-jS%8M4p(UE zAhu9~Ly>;(C_YmP`+1L`R_B!!f#uA1_(QR4)vkk6;xpwko3UmF;Zx`_*xEkm*3gDL@eHGSH9WM6xN5QoN*ew#A*$K`zUPH4kxeKCav$>S@_EQ*rO-Nvc z0VMreg@_$LuQ&`gi{|M5FB|LV5P1ufpNy_!hizO0`vjdJ9Xo!8cQ;Sz>v>=_2Lv{j zIu{A;RAyDzJ4YSAA!Qz%fX;%&EJD2P=0XW*Qk~kyMY9cWw;*}9VS_#g>nEBpjJ7_3 zl{t=*SuPydg92MaOw)wI#|$M-UJmvOmpExg71Pz#osLy^e9-mw1hwRe$q!EEq{nAd z-n)W@A5Wf*Rfs}Ylh8cj#VcPk6%8#Q?8iepF$SBX{kbpvOa@_C0?!+x^zL{c>67R8 z9gqY{rCmsGU|#1JR5yD z@PovHrSHa&7gM^I>rti1{Peee5(Z*2!`u;dfcR@i#*h=x38$OW{Hj_rU`RA#M*svN z+rqI*nST!V-OMe>V(bs<%DjGuOJ%oOx;h@IQaM?NT# zIB?7N%IW!;cHZXWy>bQgX|E<2v;%1{g?=T=eCUr)EH5vgo15Ds44rKn7&dndQ%9dd zyM>Rm4CbxU>GY0uC1j_7nP$anhUJtJ&;@ARJ-07h`-m8VBmwPgx9|*G|2S!HG2j5VFdozUKjsdO}<&H9-14A7a2pM}AC+#3!PW zlMpmXkW=)}f0AoDD;M}8d{&}M0lKbn;~y!Zv(DSJukp^Q9t9|Y*n2DAF2`FhcU4o7 zdCUz3)V+YwvVYBjgO$;L+1#m>tQZY-xclAW!ny#S;qp`D}!dF5L+?G&KDA z22%EKC~KU8t<9757d__!uj9o7_nh&C1Vb)Tq&UKhN`GbG`CnT!PYNnI!6Z-`mFJ_d zRVT6=sC`Pz`|Tkh*tr+cG2>xD594ubRz#VdCPV>cRx+lFT)Os%G1Gn!ecKNO!r0gX zq?Epf|AJb)$enmbc{JRs03kV>R|--tntlBly!bl|FTSs4C-4ilaK@rAZp3>PsO6VT z8w*T(CkJiA;tQsvKIsI4hADTj+offM;TNAvy3$K+yt@ISvwAq5Hx%J6Z8}%nGGDxq zKMU|M8-~s$jO2#l?E@W2Kz^eA5c+|3G(I_8M_*sxw4p&1jO*mQ&2F&bc%4 z%EI4|iTi?moM7e&NG;pe$%8vK-boUtSfbQ->5!qT}rU&UW?V9M#p{IcE83MLJ zTkBqR-&pj2>i)g}%tL~6zLE3%?IXTUr893E?FGSi3fXc`WH#AM=k%@mnZxg_uSa>V=f`{^sF z^iE*j*zON$qnKA88m#7wPWE@Lqoxn~MQt&O)=OR(NFk9-?NHFMM$Awd`^u{y4OR?) zX*6B=B^bNF;V5u5qyF+{Wzuuq}7sd zVP`#5fY&@7fcI5cAb-AslqI;(VyWeh04Q2YWSFHKGK|n62M~P^;&veGwmjQ zY?k7Q^ew``tnb*xt$NXVEhU|ricaRh@}d~e#T*jxbn@ty&--7<>6&FeXHMwo>U!IR zC+Z)P_4So#NPB^Kc&XD-rRS|nm~LBv>5DJFx$$2WnPD;drOPvAqyEn1mA^$$LL4ET zLwedfQx<{erO-5?&nm2ax5x#N4b&8a^G^HG9X(VJRb?z8{U6DUJd&F>kng^#3P~Jc z6-E#Y{1+EzkH+KfeN-chK!VBWGB&$=rmMCu9{2SLsdONOtO-dNvGUojihN(gErkC~ zgUp8s{q^EM4WpxTD@Sro`?`jQhjDxj7m2f5Lit$7^!08MLH1xDtn>xM$K-JRyt#RyJ6n@wm6`?fs1g z(2tL$xoi$C0#ANMooP>Ve^}Tf1VzhTcsKp#R&14U1NCp|xKpe}g%$d+Dl}}yYUcdO zkBog;Y4CG79@qF0hr{s>ssQGNqrw@tnzkyDi=yy2)U}<^Oz^KiYuqzMuRL3euH#YH z`ojxOY`_vwtFW~<({Z@`=_AbkqjgUWK{)K>#Lf+pXB()fD8pzxpHms2xFEcktbu!) zPMU16at*J*aK-L}GqSKwsf`zK;6H7^^*icp-f8XCgD4oAwRLRo?XnOZewH)S{)Ch^;$1dPM6rD}1?sEr z-ygZb88eGMv*s91XDMPgJ9Tr?<$)8nXP>cqfC4^vX_%`Zs$|>Sl?PNU!Re?o8@tT6 z%+=HK0@%$#1h4wWnRZ2HU{oBzYJ&vl%8;vO9Pk4Bwr}574nZ4KSVv~i8T!%h*WwBR zj|yi<@Hj1aXsGybbaW}Q!73E5#$6D>nkNvMTyHS4ayoFR4kJ2n2b6)LCNIR}l=QFi z?H)D^A#ozgERDIij48hH6wVq8nV+YuHrnojF9fg5w97O941c;qmx1Ju>LDEkLgH}a z{ylhgq$nh|Ptv`jR#fTf0pig^ESNIOox4WL8Sz$O#&_v%P=@Fws_$WD>xy#Rpe#uD zm_`?Mk1*^>^66~q$y>&R2r*7de<2q_LkJ~Cs4Hj0)njs?Jl@&|-? z;EVkn(=o)`$gN7jz52{Qv}(roEkKIF;LkJ2o{eI_2VzRRPeaNsG+f$>eCpV`idQ1( zV#;nb4nYoxtq$5AG+`Q`(AOmdIxaME_kTyC^?aPAH6>8ctR^b9fqJ~RYaa}I5vep| z5Pc}meKTYjzV!2<%R^asS~}@-d$!v)==$`>qfRqT#bQeR4*_Akr^5#5JHz8Q*G4QJ zxWu1A2+Jj8)>ZwHg(o8hXWH}J-S@Dd4U_@FwWyQpi>@fBC&3td3h7+1py$1Ku|clz zTu;g+7}gN!EU*vpwwf=F9SwFOiQE1y4p+cqx0j5MCyPLLA8`ARFiE@#oiI_Lhps1( z`Ab4h2FEb^P(J4JNH`38Dji*9&_Eq9m4|11EE3uWNgQPLn2Pb21U&^Z&m*vrMnEnM zQm6YHf0wLUcAm>H=IfxPr7Z0cl)!BV8%^c(*C+0sC4l2%GWYZ{w_jK>9yq9dz93}c zddd1S&v-_w8gR&UFD}^v5h`D=ZCx3wXjOnKmiY1QXv+;uCYX9SiC@q&lbR~uW~Q&J?tlKgSMmAt0u4_mPm^~kJEv~OnlVR;N}k6C z$ZmjO^uf9zAb@)QbYV`}85s!ad=Es^7REAv3ZXgJ4Xk2~V#@`0wyEwr751$b@ z^TRV`bEvlq{)l(LZ4Ax@nBCwQMMr;}&duEl!-9Gv$8=)Edl{J|>*T!wIz}(SXu0uc zI`|jxd_*YXO9Je)_=s;OUf=wPH(eo+gIk#W`iu`zQ>2a_jn=c`7Ax`Dldgk~Pw5%a z&CrPlXi@`J$Mia0t;OT{4k%#<2f?S+*Y#6;1N4}_=3D96Y9r6gpLTQvGdTsS5)(7+ z`R*7kzRo&vQD8f?kp|n9uI^q);?Iqo*10zCb5=wR?l&3@@P13+WlYQ*q5w}1kC08!iA3%LKm+W=fGeI*K+Z@9T+=XN6@aG(>mGT8ks9FX&OOGD3RA50 zaP1C(c;H9kN0F`!$h?+Nx?*pIem(ssMv02QcJ0t6h?2;ao;1TX2QB)ycwCZ(5~Mly zFV!z=}PCTUr09@&cuR~3SeH?2wnR5YGF&18x|lqB>BSU)(heqiD>*xiTd za_OO0;W2pU05e0{PH2-X2fiNpi-0cPB0VSHl#Yk#fsL7%0hWRs%7D)(44Ci;t1~+e zhw^cz;uXwC3DU`NI3`|6NeQu@>?Gu0jf4LeZz!4Q%zUJ^l>8$q%ji@!bVQ`;pl5dPW-0UQNup;ltLlc<2HjR zE#6=icDf(-Qh_sW5u?xcz=el}A2|RiQ!qznCvaCwf>ITTa(R6Ag*VZ`arz5oE>t||vOiYMkGf1%5Ycpku z?6ds=2I{*Y^Gj{0#b27ne#Vf=yK4*J?2%>~iSI9Whe2O^qvLTd;bG?`MWC}DqswAW zo!@)KAg%l8xCZK6T7$KN6!g+f&!^3Q@$EOsT>#0~D7vaD8UAT?&F7}&&OL4 zw<}`h!dZ~6mrN4F->$n{e0q!Ar%l@UGy2N!x}dlD%=f$43cJ_fX0JX)9F2}f?~SH? z)6s)@O`Aoplr^b9KYlaJ?&5-?H$!;!fp}bbYmAinq!*q|o({4b(SuG`jWALaydlkl z?r}JMHF4wmurb7U9cC_R{MiPm>PgIJ>QVbib?CZhf%WEEYGif?Hy58M%_v>m$>dw0&P&vZ!dcTn-EzK6Sz?qp-hooKmx zkHk-$h%7wycYDC{>t(a*3>q&o&^s8atw>Bh?Gfx!Q6Ysb4|i3Ie9l6=Z8A7pC#v5* zqgP1#xPbU^SF4hfTGZK*+9zK(>zkI|JawBrz5M#?WrHwyJ#8`W$MJv7NUhu7lej~9 zGBbM+9;rP6pW3d;A7FXtuujA;SM6(B6~|M(wZ3q#&R$GlP!u%R;S*|2t5xIJgQahZ zt~pHn)J9T26C@R;J+IPY_w6$AN&QzIW%4HezOf#|LxM3_#!4dI@E?A9Gey+pm)>dG zwq2aqZ47V>!5=A!#nv>L_8G%GZ<8d^_yNYri}+;6pMA^eKTVBjn=0kahT|M9D5Tma zNyd*$;`A*OMYwRJ+3XOpBV4ZB?VbMc*NL@YR8U$`?O<#;P6Bn8LU0sfc$`seq!4(o zn5sP&%AT=;4LI``#w+i8QxwvW1qYi}OC;iuW_u&ue!00@d*=x!iWrz?ZNHvNQIKQU zZIwYazj4S$@_bw|PlP))E6pC3s(@omi~<+tM-dB?2?QbgAKPsEnXvsox*-i8sN}d z_U~rc+3T=wAn zy`H&`L|C|OnUvx9j*Lp8?zqDjEE{UkG#`r*8Pq`;&hk&%SMUa^%xz45&Fj5%^k#$- zstinE@HSx;YoCNJN^nnc+&?{@D{e@}(9}~4lZI{vjQ8%dxcip48 z1ZP#JU8pOx#9oMM+(_t8RsT4WXv*D|Bf$~okDB#WXe=zI-UBp^hx}%(spk=kA4MfN znbuBCqz4h1rUiGA?6c+HkvJ;(^Fs;LrL!_kq&OLlMzfYS>&Et2LVo92q`g?3J zp)J>c5CgpLY1hAM~_z3E1;vcC@-`*mPZ_l(GIrn$Pi(oj`o9C(m>j z=Wdq$jR)7SQdFmJCrPdAHVeLqF?jz|h*2adDYf8Sy=JmBN@u@R(>mPxM49?XZhWBEGdks$HQ8q#sv2k~{OXMpfkBJM@CBPAB2)ohEnSwMpDm=n$}b=Z9T& zH_S_GtlSP+$D~QQy+0gCYt-i3NgafR@aB=-XSz&#mwyfn_om1HMqa*3BP0?Njkhtf zrFR^Y;RvKqpORXFTm)tcYxYpMcwx6{y&JStKN>=Uk3x_Sg$`XMbjAM3YrXb+Wioo@{*5emV1f8xP{ zyrMh@zm(_`G)FHQTY?etQJY29H#XVv`ewQ<_luZ33Pcq0u_U5v`6kAiaOa)czWrK{ z!|Lr<(kz>B=xzLEgBsL~vg?RLmY!-Cq|LNsjD`IizuppDzT*y+^h*Y{dzsgg+TFBf zHFc?Z%GLp~V3z5jmQ3aSDi;uKzO_HOfqk`IxOv}$7s@{p_TP*+>-=R`x#SP+CKhY@ zWF)RTTu>a@I2d$+;6p&^K zhm2Vm+s->Wa?hULwPDbZRe$y?tms)f&dQ6n)F$6_{Vv5uG zok+Mt$QgfW48YlrKNOI{$Tri(aJm@&JPKgA4*)W#Fe@7R7J*y1o2AS$66S+A!J<)n zK(AW#6IT*o`0CFXquaVnoi`?eJNT2UN zQhI=1X?PqvoHW}Z9eHaru!JkzZ}fADH(kd`0yX@Qu6Zwi?94Zx(_V_eFv!wjk>&4h z8<7bF0Sf81Yl*=xEdjci1jogu<7m9sO^Sjti!N52A_xQnpT1=7s<}fD$dN|%Rx}+% z06QwPZrq5FNid#?M51~OQRm$+hGsL&#$Kckj|IT*ei)4{k2m}B;j@_$T`W47w$HzM zfAOMEvUTkAgE_j=spXUjnbfuO4|Ld!PR*;zW4x;8ng&)+x#h<=4SEd8PBU z_`a}`l4G=j)l)8ie$NzbNZ`I|R4rqq*|JX+NIR-n>3`I)i<+H#gHlM;P3CsT9uQ0* zE|q=Wm~S*0tdp=UCpY8t#9q@w4nQL30a==|3FzW}qpzw$xhAKL?erl^VAH z7nStAV$%;%B!|Rc*^wIH>|@M|He!qQLpP#sf^nphWCHPKGSMm1PE9KV|8%;qVBl^Q z4*e!*=P>wm;`+O%_?ATcbM;e_EpO6NRxjpzybz2Cr3|zNPF_D(>R9WmHXeQ6mAVPp zgKu#aNI(;QUfA;Yfumes?b3;p#msRef_%DPAc6SF-dn?!q>K*OX|r}NH0BXuYHZ91 zBKnN()D%OtR|UEz{%Q_nr#R3{0v;DY)1hQhO%rBtNn?$#CkN)-)Ct~SNJVa{mn?K( zxppQXCWqc!R5AVg=XD)o07h3tf2)!X-XB>VfW}?X=xe$v&1wH5CV?twl)7{Dp%dqA zt59U`yI?od7*j@%!mtUa`K6tAmuXOFfNTZxC{7ABe?>@{<;vy0D>#BO%6-~8=zCMW zYw)NWEmcwuLV8yGGO9n*6w!9(hnR2Vt$zG212VxW^2TKa{PxrfYQbof1q?iaXcq7&u0`Ilwq2kyyDE8J5uWLabFQNJZV(mvES z)`lz08TOl;Fw3p{)Jr!1xlR7Y9h;~&DPG4!hpFh2&5I6+M6aLmC&0ws3TyfnSB-_G z#NJS$E~K-Tb)Az!zZaeoS=_?B*$`$AkXtyWE1snfwTa~0MTgv1xozQWq4X`Q=K zZ)z&#dN5Bz!nujId%imb6u>lrF!X%=@%2wUUD)&U=mXjf51eo(#Ns!CdC=(n*9Y$FfLp zmo96gI@p9sPi01vof0)3#wd8N?eUq5opm2P$U2-^>z001j znT?o^8%1EyD?|MkUq>%^Mx`ge7?EmqSKrGCL8jX#`hc&C9D6G=jui#XfBT^`Nh$(l__X%5mNER7m0GYoXSZg8e|&5Cz$wFn%q;;YQXo07FlAw{}vyGQj7~f9lBMJmiRXrX@-9W-mK)cO>eTD!q?6#ICTp2 ze0FUzl|*^HB?P&&@}f9#_M$$M%?F(aW9V=f+MUTiLJ}x}#CdMW(1+Yv4Mv@R^_VFG zz76GtCrn)-wwP9|JiLb3#c@pB$H_)YC-S#K9&JFsP9?%b=R?-#*qLH_T|k-RpdRaS z!87a{(tzM1`j8hMN+vzhJQSI%Gl_H48JDjOrd7~R(_r)v-}30Y0l~5y<gJ`-);ig2QZv%NicW9d{b)Y!(=gH~pRSc%QVX?O+jK^+v{4&waa>#$le4hSkEYJO!X>DX3 zf~H0n``JV~{>-2#ur8LCo|pBEdG{Mz4yD4vTGN;arMR{S=wIiDG+F9M;RJ5mQfG8V z*3cnVNO$tL%4%VdE}-TrtK04q$#}K$-0y&z1jjS;b+-##m6iAh-Q>=fF4nK-G_`=T z`V;ri7*HWDUNuN>KKcOGU*cOD&)4{|f%IiYH&^ZhGjM?-3sBIT5tW{*f0RpL{UK7E zQ$VqUUaT0>F&U8E4EkpuQ-bDo*f(I;V z_l;*1km5(iOa^ze%Ha*yF#AB**9r@$o+2q-*%Ejs{#g?aOwg}bJBC)iLR%l84|bT_ zu@eOLY5s71WDfp#1cN~Qw}uqwCp~Y@(Cem@7ZjygjGPJZUW2ZvQIdE^Y&ZTn0Wpi{ z&&{FW#TUq)djX?ZLsrq?wh2?gC40@grb@GOW)@F@xENVDtDw6tT#J0T*yYP~3H2DD z;&ITG;(pR>wkII#rd>GcMhD@xVwaecIdO>rCKNozaJ3%xS6-XYv_I^Jv)b#3w%`Z z%GZtDr4$oo|FY4%P@1%c746nW!x&}P=v`W?J{py$*#u#Ys+bs>I4p`W{K0E>qgUFR z9pRRAr37w%<94L*x7o`IvS+03Th>%Eqllb>2?h7?%1Z4^u-Gk6D|uj}i8&Ox2~Vm-AN$o(sA=^E=mc-xf18fos&#MmXY$0LdabYUncR*0o0{ zGBtxbE6#NvZ9;Mi74ls|hR)yTdSm^gmyX<9rTx>l85<>huCPC+GHq2@`}4btCVV@p z7w?gwcEDhjq#Q+G+o2rzP{w<$L3VAFx9gwTd!roisy6+}si@AhJ3AKJcRww5&T25- z#<4^gNvR3tkJbJ|s3T*yNQ4<)I(-#$>|0TK&L)+%*&58ok}7g*kOHduU%ofzuSiTQ z^$kyzZyV?Ae`6_i`x~F}RPKJ5h&8-Y>!Fp@ZFHPR&|1D7F)s09MNazjUxv`^`r%_W z(G^ctCASA7vlyD`vcImZ3>k)tDq@O!R z6xr%|;eplnNb%UOu}0oW*6K->+EIKHnFXI?$%I~g^3Nz&BWXQc<bS- z=_&&PZ~}TYr@v%ebbI-qPyi8MA9gL59JMQ2Q@8JP1fTzqp)qYAv&H(>B_&-N*@SP$ zKL;y5WBYxsc2GsDv^Vkx9h(1}Ag6DS`FdT1ah!Im6vsU<9~FNytxRhmew|aDDXV}+ zKcesZYxuRKZ1 zfl*%d!D9yQtWp{}BFHu7v|C!q+}(dw!-jl*fBFU>_dv*#dPVaHq>l-mL_s1DJhA$!PA%UdM0nMaxYf$k$dqC_-xIpKthn8G_S=sBClK_)!LP2QC$!?p>n(5pC|N476 zKw4LNoraz4S8LagxO|mJYXTkyVhpXqEhSX^NXjYvn!1NSx73X(4JN!~#ln;)nn%gc*p0zIBS|+8oTbyDF zj@8a?o6z6=z?u8BbCOZK@}28bwUb->+XDl(ED`>dXpECOQ>wGm2OmFhrkwriyqd$U z+Q$R02H`6fYyG=;{V%|3(QZy~v$c0Lnfvpm?8B%`P=;ct2ay+5Fh;{S9ntcDUKV^9 z*9}?L)ya+&uj3R}kH5ez?g?FM*na%_-?tyybe`6BMt+=vhCeGUtW=Q}gUwLhH&!e< zVr%G1H@;U+iT@v$fBx?fXM*6B`+f5twfz=?W;RKu$!o1T>ba`^vp;rJ06KFy$GgMq zIc0Sy4OAo``)^*~-}2OD4@pu;#?Yn~Ge1>TBxi*GaBvqy_%IPMS!#L5=Qe>O0J-cw z{qDUGC>((g8bH+u-1r|l!}v8Ma~c(VG0DG!?ttR|#y|tmePgU{+R>}pn(51x1W6PW zFBN(??d3gsRU=3izPKFvaxq*(FV>Zh_p&j3SIhf*X^2FSR1D*P9rC{K@+H=Rb{~Cl z4t!$Xfl_b}dbM|B0(Zvk;*Hw1H5g2OIsfsCkq#CmZ*r{I{(L0l zmPrFJ!B@6-#ECsN{KuajS!I@QJy&$>+#;P%jVY%5tMA0GzZkZ7Cabu zjW4&nXzwuFH^6aZ;6|(Rf2Kxi7sl79dWNg8C?vsp?=yb63jb_U8-(*qlYtc<;hR?| z1}=KnlrHb)6N8nUYN}Mr*0k3%V|?}* z@{@{qTK8~FVRR&a?!}FdS(nre=S&SB6C^q3Ae@eM_1m3k7jUHRxy!9aO0#BJMr;*B z`kIa7qBIC+RE@2y=%dBR414s2cZ|;&;N*;;>?xH*@(`UFxM%;!lxLh_P&IpI-DUE; zsGIPuz%=#A-yrzw?o(9OeJ*c~`}m)9i8qI$5OMP3Q1{`WorvhgdZ%A+bpM!6x+x&j z=g;_%iyOCb*02q0$$#EBg&)B0YK?UJ;^3M3eP#eD?$>dH+Od!2SE9Wc=qC2`LTdKc zxaWtYR1ksmzJaQyfiWBPH<2mQSBoh=aEWhbd(rKu?H&l;(P>NhY4?>S$AD)=wQNg3 z+hdm$x*XMu^Ak^gH2T*BXXk9=O#bYDt;Sk~+Lx09J5Co&V)}B2{|tSvh;gC~ywfsL zK(d9NJ@jLE1i7EvEBpSKU8@2MO|MhKIP~2|tJMcaTZQ!mrasYL%5_2?h_fUMYh3J5x$VA#!K?;GovJ?GmZIQ~&>_`l&&PFz(8~KtO;y8`lvyz^FyqaV zsFy~Fnxe5l{cggU5YdR>QF$}g0)izvWk+MenrD*#P*q_rTkjK1>qGSF{@B{ZcdYK%2)P&-W=eODO8{Nc9SLbd);^*I<=bKD}@JAUZZ=VAK&Ck^FTCw}H9+uuib z+i8M&d8iA`VSYKKhh4xrw>!|0rAvng7>FQ+GcFxcWuS>w6V2C)zP>Ws$-6#ErpTmX z3I|nL4u}MuKoU5Nx1O07Dq((lL)IZL|Lthqo*yy3PjCrF{+8KoWVV}itSB#3+-{Bj zIc9Hh*>=>+=Qb_hQ0%WEb0X5$i_#4LRBddG-27KA1feNPn;jCt2X_FEOoT^Ra~SeP+a1n! z6YS6Jb8`t?#R6)9B7b#*I*ymOip#{RZ9{3_Bs={0mwCA{lBeRN@i^~Cwc)#QF^wl& z>Z*1!_qFP!>?0bf8O0uurbao9D1=Je(Llme|H_2j=z_D_37AYNm!ud!khf;r^+dCc za-Apxc8#z1EgXZNG9h7TcLplk91~xFun_gTZh-ht>-3M=j=6UpvZ3ixNVX!!qUDgU zr#eWoW?ylOd29Qlz8oNRl<0kn&$w%Vntrt_XbkAhEp_bHq64@r+xZ1IM@J2BKc+%IupbA$q zx|=<2QAi93>Kk%29WrKl1P?hA@`P0BVtVw7Z(-g6zii0&$gA1AAI`l~oh32g{g{K+ zmd~YuOQhhxomU!u_s?~-pI9P3xf>Hh(;`YcBPU~k4WC-Hyhkb8uXd8+&u69m5?LWTli`#Kr>@A4(7{JTW=^_xlJ(Epnr?M1U3vXjHNz-_iHU_$~1^V zO`Z^0G)P8kV>U38Z(v27FcAJY}VE2~R3(+g}ASP|KZH91d1 zU7U5>O%WPK>A#d&4mJ_op+3v?fzD&EE<44(ne6`Xdxr&l#hBxcw6_iM^wg@WeNNy& zajcTSyqX^7>jhLe;?Uok593BBS0Br?)y5$bxK~YXlt$dD>k{AORlOwB>7XMdSWGwQdB(e>Qj&L75ARaxjXT^S@L}yKn%TAw zgN4q88PDXy*dqA5sezsEc!>nW@-uKeZzUMxbc(yV-R{g?_cDKOafrQ3mqsN_D%an$LBY_VMc*%_TuLnS_{Rg0}o|U_Ox4U#TxrJ zyk_LYj(2^l#^?69El1EA-hz6|ss+r&_dwRyL#Xi1(lBFo?kZZ6KIpldWo}Ux<=5au z!i^o|{YjkNb}z({z9v!x2k)}Bj`fu{M6bfrs#j!Lc+=NEJWqoUR3tg#LU;RKaou7a zm@N~B(COfp~n&&D&K+syx&!m!wCt~^a*|LB=UYi0I-X+f4kvgP#kO01LfX3zxZBA zbk6EOgge>N8&JG|%|$myV@}<-B%1)r^%vkb&Dh~6txLu)26_e3`*yPy#K5w8ni$X0 z92mgDpPoQ>Z5aIbIqObC-KzHjWT#As>HjwQ)Mm2y+GvdR{X~A!Wu18D25krnmyTX) z{WE>bDX4MJg#O)OOUi%VDAcXzT7tJRQI};|^QdsY%58st$WmEjYAYFL#;jDh-#(VP zw@rYc=kqwV)rY}d8|-4*0;+?Me{G8C-~HZ#Q?lNXfX8^!;jL!#D6t~(%Pt2yGEbHK`_%x7N zYru-?H0KT9=v;!|$h=3e>E1r^44ETnKGz>u(7KnVeE8K0%o;@Q`$6Bfx*db`$b}w% zwBAl>giGfJ>}JKz939oap~_Olx_gdCy;uIu68n6pp>Z|h%HCqj1rXwiFPuVG;`lU@XkUg2iRe@X(aW9fIw{I<} z3?_pA%WDR;z7a`JB*kb%6_Q27(W3sp^3C*}?L>>?(S=_7%yiU#O9!nIw8W?-)iAnA zZ*8rlp_ZT#H5Ec@q`AhWHPj`o(4=Y$)5%ymekEzhsIezH7j4KwhDcLeG_m9o%T51| zd)}TG=lPuTJm)#j_xqg78!jx%WNTMB#!`j#D%Fc^*>TZ7Tk_AL`W7#!uY4LJZ$7tun%|a>;(i+Clb!CqGbo$~lhr%TY9(Lpe+_5m3{= zs^oz7WAi&-FEOZ25`uw7^&XDr`16|g^L)*Bp6eW)grk>arzp$bTeo@{$*sj|tWW8s zs53(mjRsoh?;(@+g1IXYIZ9H6z1E67<01sEBeYp>?*EOLK(Qem_FjjiTBzvBqPpd% z-Ech=oRB)ITR;*v@GE~WX?)XY$QB0MtH^ilpfpY?>Nk?y?D(me+ z$}v!5W|MC|v~isQ3AU2>CBOW5c?DH}bCk$8fQoywHmJo1dJpgAMZj#}Zmcdu zb}Fxy(fKwhK=8bC%Zn#vvD`x7ceh za+X3c*W#32#CYfvx8d{c;&*&ci=PyUxk#uk#BMpZJcNXZf%$x&)YZmO?@!&snPMt_ z!TJC{i=V}wgfKID+}w#fQmH_)^9H?#yN<~cC}O>VNiIS_PvUUxLEH2V(~F1S>2%{%eoU?{QDY>rvm@BVQfulw%d&ACO}zV zhpa+5w}=eNyR6f21*QY&c>aTqM^BK!24GsZ_Y)V_FI zJ*3(me7(<=tU#3;C+?9ZF&kX8dz4cTsH2uFn-7zjB4f>6WShu0o zj6XyEfVMV+EUh7#X3HySB|?CdAhV4Zo*QZ5EqRw zaxEdqfDi5wqP~&#&^%v{yL1mLL65!XuP?^&W<(};RLa7}c0Ut54qF{f-%L-W)C4>b z5Jhf%eq!^R;5Pgwm{m~$Pf75moeT6NDU28o^#83oHt2h4p*2+JCYZG4S-J|){Q7+D`!W5_Z4SF#tHbZ95Z zL88~C(Cv6Y24W+7+=m4yuDBv*1%MW1b^U`|ITo$IE`1sSr>WQX98L39**~Re=dn#D%H#Rbn$NAth9~?}B2Yb?O`PF?( z6NV<~zJg5GYc`_0$ZJ@QYsFtTa6GH1$xAg{>wdjA(g#P1j?|<)!6doM!RK8 uqD$G|pWUa??T&ujTf`q&(EK_6pM>!XR-x&t=q>v*?}b2kK=Zkqx&H&LAD=(~ diff --git a/static/assets/icons/galoy_blink.png b/static/assets/icons/galoy_blink.png new file mode 100644 index 0000000000000000000000000000000000000000..855bf3acc1173ddbc04437c2f3a2508e1f5da391 GIT binary patch literal 34904 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 BK?MK+ literal 0 HcmV?d00001 diff --git a/webpack.config.js b/webpack.config.js index 06b47c7fac..9e9ef59d40 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -115,8 +115,8 @@ if (!process.env.ALBY_OAUTH_AUTHORIZE_URL) { } // default value is set in the code where it is used -if (!process.env.BITCOIN_BEACH_GALOY_URL) { - process.env.BITCOIN_BEACH_GALOY_URL = ""; // env variables are passed as string. empty strings are still falsy +if (!process.env.BLINK_GALOY_URL) { + process.env.BLINK_GALOY_URL = ""; // env variables are passed as string. empty strings are still falsy } // default value is set in the code where it is used @@ -242,7 +242,7 @@ var options = { // new webpack.SourceMapDevToolPlugin({ filename: false }), // environmental variables new webpack.EnvironmentPlugin([ - "BITCOIN_BEACH_GALOY_URL", + "BLINK_GALOY_URL", "BITCOIN_JUNGLE_GALOY_URL", "NODE_ENV", "TARGET_BROWSER", From 58e2c3349b10dfbc338e6148f88b214ca2aea299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Thu, 7 Sep 2023 15:03:20 +0200 Subject: [PATCH 02/18] feat: sketch --- src/app/screens/Enable/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/screens/Enable/index.tsx b/src/app/screens/Enable/index.tsx index 984736cfce..6ba83ea7f6 100644 --- a/src/app/screens/Enable/index.tsx +++ b/src/app/screens/Enable/index.tsx @@ -22,6 +22,10 @@ function Enable(props: Props) { }); const { t: tCommon } = useTranslation("common"); + // Fetch acount and check for keys if this is a call that requires keys + // Render onboarding prompt (instead of enable screen) + // Remove all content script changes introduced with the previous PR + const enable = useCallback(() => { try { setLoading(true); From 8d565e22d2e75a51edce7b3dfc4155a5ddd52f36 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 11 Sep 2023 10:15:27 +0530 Subject: [PATCH 03/18] feat: onboard in enable --- src/app/components/Enable/index.tsx | 122 ++++++++++++++++++ src/app/router/Prompt/Prompt.tsx | 7 +- src/app/screens/Enable/NostrEnable.tsx | 92 +++++++++++++ src/app/screens/Enable/index.tsx | 4 - .../actions/allowances/enable.ts | 4 +- src/extension/content-script/nostr.js | 11 +- 6 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 src/app/components/Enable/index.tsx create mode 100644 src/app/screens/Enable/NostrEnable.tsx diff --git a/src/app/components/Enable/index.tsx b/src/app/components/Enable/index.tsx new file mode 100644 index 0000000000..00fd3f3301 --- /dev/null +++ b/src/app/components/Enable/index.tsx @@ -0,0 +1,122 @@ +import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; +function Enable(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation("translation", { + keyPrefix: "enable", + }); + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + function reject(event: React.MouseEvent) { + event.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + async function block(event: React.MouseEvent) { + event.preventDefault(); + await msg.request("addBlocklist", { + domain: props.origin.domain, + host: props.origin.host, + }); + alert(t("block_added", { host: props.origin.host })); + msg.error(USER_REJECTED_ERROR); + } + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + if (allowance && allowance.enabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + return ( + + ); +} + +export default Enable; diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 3f4b13d4a9..a5d283ca9a 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -1,4 +1,3 @@ -import AccountMenu from "@components/AccountMenu"; import ConfirmAddAccount from "@screens/ConfirmAddAccount"; import ConfirmKeysend from "@screens/ConfirmKeysend"; import ConfirmPayment from "@screens/ConfirmPayment"; @@ -23,6 +22,7 @@ import Toaster from "~/app/components/Toast/Toaster"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; +import NostrEnable from "~/app/screens/Enable/NostrEnable"; import Onboard from "~/app/screens/Onboard/Prompt"; import type { NavigationState, OriginData } from "~/types"; @@ -86,7 +86,9 @@ function Prompt() { /> } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp + element={ + + } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp /> {
-
diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx new file mode 100644 index 0000000000..f2ce9479ce --- /dev/null +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Enable from "~/app/components/Enable"; +import toast from "~/app/components/Toast"; +import Onboard from "~/app/screens/Onboard/Prompt"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; + +function NostrEnable(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const [accountComponent, setAccountComponent] = + useState(null); // State to store the component to render + + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + const account = await api.getAccount(); + if (allowance && allowance.enabled && account.nostrEnabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + useEffect(() => { + // Fetch account data and set the component to render based on the result + async function fetchAccountAndSetComponent() { + try { + const account = await api.getAccount(); + + if (account.nostrEnabled) { + setAccountComponent(); + } else { + setAccountComponent(); + } + } catch (e) { + // Handle any errors that occur during account fetching + console.error(e); + } + } + + fetchAccountAndSetComponent(); // Call the function when the component mounts + }, [props.origin]); + + return ( +
+ {loading ? ( +
Loading...
+ ) : ( + // Render the component based on the accountComponent state + accountComponent + )} +
+ ); +} + +export default NostrEnable; diff --git a/src/app/screens/Enable/index.tsx b/src/app/screens/Enable/index.tsx index 45e36a2267..07a76a6876 100644 --- a/src/app/screens/Enable/index.tsx +++ b/src/app/screens/Enable/index.tsx @@ -22,10 +22,6 @@ function Enable(props: Props) { }); const { t: tCommon } = useTranslation("common"); - // Fetch acount and check for keys if this is a call that requires keys - // Render onboarding prompt (instead of enable screen) - // Remove all content script changes introduced with the previous PR - const enable = useCallback(() => { try { setLoading(true); diff --git a/src/extension/background-script/actions/allowances/enable.ts b/src/extension/background-script/actions/allowances/enable.ts index ceb6665d07..5bb4c0b2d2 100644 --- a/src/extension/background-script/actions/allowances/enable.ts +++ b/src/extension/background-script/actions/allowances/enable.ts @@ -15,7 +15,9 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - + // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once + // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. + // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. if (isUnlocked && allowance && allowance.enabled) { return { data: { enabled: true }, diff --git a/src/extension/content-script/nostr.js b/src/extension/content-script/nostr.js index 76fc586729..92ff55266c 100644 --- a/src/extension/content-script/nostr.js +++ b/src/extension/content-script/nostr.js @@ -1,6 +1,5 @@ import browser from "webextension-polyfill"; -import api from "~/common/lib/api"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; @@ -23,7 +22,6 @@ const disabledCalls = ["nostr/enable"]; let isEnabled = false; // store if nostr is enabled for this content page let isRejected = false; // store if the nostr enable call failed. if so we do not prompt again -let account; async function init() { const inject = await shouldInject(); @@ -84,14 +82,7 @@ async function init() { origin: getOriginData(), }; - // Overrides the enable action so the user can go through onboarding to setup their keys - if (!account || !account.nostrEnabled) { - account = await api.getAccount(); - if (!account.nostrEnabled) { - messageWithOrigin.action = ev.data.action = `public/nostr/onboard`; - } - } - + // we don't handle onboard in content script. hence we will be resolving original call nostr/enable with an error hence we need reload the next time we execute the call const replyFunction = (response) => { if (ev.data.action === "nostr/enable") { isEnabled = response.data?.enabled; From a60e8d91d02a219c090cd4546b44087350aa1578 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 11 Sep 2023 10:30:33 +0530 Subject: [PATCH 04/18] chore: add comment --- src/app/router/Prompt/Prompt.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index a5d283ca9a..842bacd43e 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -161,6 +161,8 @@ const Layout = () => { + {/* maybe just remove accounts from the prompt and use previous method, in this way we acheive no need of reloading as well and don't reject original promise + what is the use of doing account switching in the prompt? */}
From e8d30b9ecc5058112d66c3d22872ef2cb5db67ae Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 13 Sep 2023 11:58:34 +0530 Subject: [PATCH 05/18] feat: onboarding shall work even after allowance is set by some other provider --- src/app/router/Prompt/Prompt.tsx | 5 +- src/app/screens/Enable/NostrEnable.tsx | 1 + .../allowances/__tests__/enable.test.ts | 2 +- .../actions/allowances/{ => enable}/enable.ts | 4 +- .../allowances/enable/mnemonicEnable.ts | 83 ++++++++++++++++++ .../actions/allowances/enable/nostrEnable.ts | 86 +++++++++++++++++++ .../actions/allowances/index.ts | 16 +++- src/extension/background-script/router.ts | 6 +- 8 files changed, 192 insertions(+), 11 deletions(-) rename src/extension/background-script/actions/allowances/{ => enable}/enable.ts (96%) create mode 100644 src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts create mode 100644 src/extension/background-script/actions/allowances/enable/nostrEnable.ts diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 842bacd43e..0bc8c10bfb 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -17,6 +17,7 @@ import NostrConfirmSignMessage from "@screens/Nostr/ConfirmSignMessage"; import NostrConfirmSignSchnorr from "@screens/Nostr/ConfirmSignSchnorr"; import Unlock from "@screens/Unlock"; import { HashRouter, Navigate, Outlet, Route, Routes } from "react-router-dom"; +import AccountMenu from "~/app/components/AccountMenu"; import AlbyLogo from "~/app/components/AlbyLogo"; import Toaster from "~/app/components/Toast/Toaster"; import Providers from "~/app/context/Providers"; @@ -160,10 +161,8 @@ const Layout = () => {
+ - {/* maybe just remove accounts from the prompt and use previous method, in this way we acheive no need of reloading as well and don't reject original promise - what is the use of doing account switching in the prompt? */} -
diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index f2ce9479ce..dda491d7bb 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -59,6 +59,7 @@ function NostrEnable(props: Props) { useEffect(() => { // Fetch account data and set the component to render based on the result + async function fetchAccountAndSetComponent() { try { const account = await api.getAccount(); diff --git a/src/extension/background-script/actions/allowances/__tests__/enable.test.ts b/src/extension/background-script/actions/allowances/__tests__/enable.test.ts index d84e67197d..32fa3c4404 100644 --- a/src/extension/background-script/actions/allowances/__tests__/enable.test.ts +++ b/src/extension/background-script/actions/allowances/__tests__/enable.test.ts @@ -4,7 +4,7 @@ import state from "~/extension/background-script/state"; import { allowanceFixture } from "~/fixtures/allowances"; import type { DbAllowance, MessageAllowanceEnable, Sender } from "~/types"; -import enableAllowance from "../enable"; +import enableAllowance from "../enable/enable"; jest.mock("~/extension/background-script/state"); diff --git a/src/extension/background-script/actions/allowances/enable.ts b/src/extension/background-script/actions/allowances/enable/enable.ts similarity index 96% rename from src/extension/background-script/actions/allowances/enable.ts rename to src/extension/background-script/actions/allowances/enable/enable.ts index 5bb4c0b2d2..488b3db9ba 100644 --- a/src/extension/background-script/actions/allowances/enable.ts +++ b/src/extension/background-script/actions/allowances/enable/enable.ts @@ -3,8 +3,8 @@ import { getHostFromSender } from "~/common/utils/helpers"; import db from "~/extension/background-script/db"; import type { MessageAllowanceEnable, Sender } from "~/types"; -import state from "../../state"; -import { ExtensionIcon, setIcon } from "../setup/setIcon"; +import state from "../../../state"; +import { ExtensionIcon, setIcon } from "../../setup/setIcon"; const enable = async (message: MessageAllowanceEnable, sender: Sender) => { const host = getHostFromSender(sender); diff --git a/src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts b/src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts new file mode 100644 index 0000000000..abafb6944c --- /dev/null +++ b/src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts @@ -0,0 +1,83 @@ +import utils from "~/common/lib/utils"; +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../../state"; +import { ExtensionIcon, setIcon } from "../../setup/setIcon"; + +const mnemonicEnable = async ( + message: MessageAllowanceEnable, + sender: Sender +) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const account = await state.getState().getAccount(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once + // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. + // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { + return { + data: { enabled: true }, + }; + } else { + try { + const response = await utils.openPrompt<{ + enabled: boolean; + remember: boolean; + }>(message); + + if (response.data.enabled && sender.tab) { + await setIcon(ExtensionIcon.Active, sender.tab.id as number); // highlight the icon when enabled + } + + // if the response should be saved/remembered we update the allowance for the domain + // as this returns a promise we must wait until it resolves + if (response.data.enabled && response.data.remember) { + if (allowance) { + if (!allowance.id) { + return { data: { error: "id is missing" } }; + } + await db.allowances.update(allowance.id, { + enabled: true, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } else { + await db.allowances.add({ + host: host, + name: message.origin.name, + imageURL: message.origin.icon, + enabled: true, + lastPaymentAt: 0, + totalBudget: 0, + remainingBudget: 0, + createdAt: Date.now().toString(), + lnurlAuth: false, + tag: "", + }); + } + await db.saveToStorage(); + } + return { + data: { + enabled: response.data.enabled, + remember: response.data.remember, + }, + }; + } catch (e) { + console.error(e); + if (e instanceof Error) { + return { error: e.message }; + } + } + } +}; + +export default mnemonicEnable; diff --git a/src/extension/background-script/actions/allowances/enable/nostrEnable.ts b/src/extension/background-script/actions/allowances/enable/nostrEnable.ts new file mode 100644 index 0000000000..3b073a704f --- /dev/null +++ b/src/extension/background-script/actions/allowances/enable/nostrEnable.ts @@ -0,0 +1,86 @@ +import utils from "~/common/lib/utils"; +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../../state"; +import { ExtensionIcon, setIcon } from "../../setup/setIcon"; + +const nostrEnable = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const account = await state.getState().getAccount(); + const allowance = await db.allowances + + .where("host") + .equalsIgnoreCase(host) + .first(); + // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once + // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. + // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if ( + isUnlocked && + allowance && + allowance.enabled && + account?.nostrPrivateKey + ) { + return { + data: { enabled: true }, + }; + } else { + try { + const response = await utils.openPrompt<{ + enabled: boolean; + remember: boolean; + }>(message); + + if (response.data.enabled && sender.tab) { + await setIcon(ExtensionIcon.Active, sender.tab.id as number); // highlight the icon when enabled + } + + // if the response should be saved/remembered we update the allowance for the domain + // as this returns a promise we must wait until it resolves + if (response.data.enabled && response.data.remember) { + if (allowance) { + if (!allowance.id) { + return { data: { error: "id is missing" } }; + } + await db.allowances.update(allowance.id, { + enabled: true, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } else { + await db.allowances.add({ + host: host, + name: message.origin.name, + imageURL: message.origin.icon, + enabled: true, + lastPaymentAt: 0, + totalBudget: 0, + remainingBudget: 0, + createdAt: Date.now().toString(), + lnurlAuth: false, + tag: "", + }); + } + await db.saveToStorage(); + } + return { + data: { + enabled: response.data.enabled, + remember: response.data.remember, + }, + }; + } catch (e) { + console.error(e); + if (e instanceof Error) { + return { error: e.message }; + } + } + } +}; + +export default nostrEnable; diff --git a/src/extension/background-script/actions/allowances/index.ts b/src/extension/background-script/actions/allowances/index.ts index 072d493211..cc0c68bc8d 100644 --- a/src/extension/background-script/actions/allowances/index.ts +++ b/src/extension/background-script/actions/allowances/index.ts @@ -1,9 +1,21 @@ import add from "./add"; import deleteAllowance from "./delete"; -import enable from "./enable"; +import enable from "./enable/enable"; +import mnemonicEnable from "./enable/mnemonicEnable"; +import nostrEnable from "./enable/nostrEnable"; import get from "./get"; import getById from "./getById"; import list from "./list"; import updateAllowance from "./update"; -export { add, deleteAllowance, enable, get, getById, list, updateAllowance }; +export { + add, + deleteAllowance, + enable, + get, + getById, + list, + mnemonicEnable, + nostrEnable, + updateAllowance, +}; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index ed63b54de8..f10f6437a2 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -85,7 +85,7 @@ const routes = { public: { webbtc: { onboard: onboard.prompt, - enable: allowances.enable, + enable: allowances.mnemonicEnable, getInfo: webbtc.getInfo, getAddressOrPrompt: webbtc.getAddressOrPrompt, }, @@ -106,13 +106,13 @@ const routes = { }, liquid: { onboard: onboard.prompt, - enable: allowances.enable, + enable: allowances.mnemonicEnable, getAddressOrPrompt: liquid.getAddressOrPrompt, signPsetWithPrompt: liquid.signPsetWithPrompt, }, nostr: { onboard: onboard.prompt, - enable: allowances.enable, + enable: allowances.nostrEnable, getPublicKeyOrPrompt: nostr.getPublicKeyOrPrompt, signEventOrPrompt: nostr.signEventOrPrompt, signSchnorrOrPrompt: nostr.signSchnorrOrPrompt, From b575a937291c6870692dbbd3d88f6edf0a58d73c Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 11:27:26 +0530 Subject: [PATCH 06/18] refactor: refactor providers --- src/app/router/Prompt/Prompt.tsx | 25 ++--- src/app/screens/Enable/AlbyEnable.tsx | 9 ++ src/app/screens/Enable/LiquidEnable.tsx | 91 ++++++++++++++++++ src/app/screens/Enable/WebbtcEnable.tsx | 91 ++++++++++++++++++ src/app/screens/Enable/WeblnEnable.tsx | 9 ++ src/app/screens/Enable/index.tsx | 123 ------------------------ 6 files changed, 213 insertions(+), 135 deletions(-) create mode 100644 src/app/screens/Enable/AlbyEnable.tsx create mode 100644 src/app/screens/Enable/LiquidEnable.tsx create mode 100644 src/app/screens/Enable/WebbtcEnable.tsx create mode 100644 src/app/screens/Enable/WeblnEnable.tsx delete mode 100644 src/app/screens/Enable/index.tsx diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 0bc8c10bfb..29dd7727ef 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -1,9 +1,9 @@ +import AccountMenu from "@components/AccountMenu"; import ConfirmAddAccount from "@screens/ConfirmAddAccount"; import ConfirmKeysend from "@screens/ConfirmKeysend"; import ConfirmPayment from "@screens/ConfirmPayment"; import ConfirmRequestPermission from "@screens/ConfirmRequestPermission"; import ConfirmSignMessage from "@screens/ConfirmSignMessage"; -import Enable from "@screens/Enable"; import LNURLAuth from "@screens/LNURLAuth"; import LNURLChannel from "@screens/LNURLChannel"; import LNURLPay from "@screens/LNURLPay"; @@ -17,13 +17,16 @@ import NostrConfirmSignMessage from "@screens/Nostr/ConfirmSignMessage"; import NostrConfirmSignSchnorr from "@screens/Nostr/ConfirmSignSchnorr"; import Unlock from "@screens/Unlock"; import { HashRouter, Navigate, Outlet, Route, Routes } from "react-router-dom"; -import AccountMenu from "~/app/components/AccountMenu"; import AlbyLogo from "~/app/components/AlbyLogo"; import Toaster from "~/app/components/Toast/Toaster"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; +import AlbyEnable from "~/app/screens/Enable/AlbyEnable"; +import LiquidEnable from "~/app/screens/Enable/LiquidEnable"; import NostrEnable from "~/app/screens/Enable/NostrEnable"; +import WebbtcEnable from "~/app/screens/Enable/WebbtcEnable"; +import WeblnEnable from "~/app/screens/Enable/WeblnEnable"; import Onboard from "~/app/screens/Onboard/Prompt"; import type { NavigationState, OriginData } from "~/types"; @@ -73,17 +76,13 @@ function Prompt() { /> } /> - } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp - /> - } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp - /> + } /> + } /> } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp + element={ + + } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp /> } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp + element={ + + } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp /> ; +} diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx new file mode 100644 index 0000000000..4ae5fa3a24 --- /dev/null +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -0,0 +1,91 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Enable from "~/app/components/Enable"; +import toast from "~/app/components/Toast"; +import Onboard from "~/app/screens/Onboard/Prompt"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; + +export default function LiquidEnable(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const [accountComponent, setAccountComponent] = + useState(null); // State to store the component to render + + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + const account = await api.getAccount(); + if (allowance && allowance.enabled && account.nostrEnabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + useEffect(() => { + // Fetch account data and set the component to render based on the result + + async function fetchAccountAndSetComponent() { + try { + const account = await api.getAccount(); + + if (account.nostrEnabled) { + setAccountComponent(); + } else { + setAccountComponent(); + } + } catch (e) { + // Handle any errors that occur during account fetching + console.error(e); + } + } + + fetchAccountAndSetComponent(); // Call the function when the component mounts + }, [props.origin]); + + return ( +
+ {loading ? ( +
Loading...
+ ) : ( + // Render the component based on the accountComponent state + accountComponent + )} +
+ ); +} diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx new file mode 100644 index 0000000000..64b6100eae --- /dev/null +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -0,0 +1,91 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Enable from "~/app/components/Enable"; +import toast from "~/app/components/Toast"; +import Onboard from "~/app/screens/Onboard/Prompt"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; + +export default function WebbtcEnable(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const [accountComponent, setAccountComponent] = + useState(null); // State to store the component to render + + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + const account = await api.getAccount(); + if (allowance && allowance.enabled && account.nostrEnabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + useEffect(() => { + // Fetch account data and set the component to render based on the result + + async function fetchAccountAndSetComponent() { + try { + const account = await api.getAccount(); + + if (account.nostrEnabled) { + setAccountComponent(); + } else { + setAccountComponent(); + } + } catch (e) { + // Handle any errors that occur during account fetching + console.error(e); + } + } + + fetchAccountAndSetComponent(); // Call the function when the component mounts + }, [props.origin]); + + return ( +
+ {loading ? ( +
Loading...
+ ) : ( + // Render the component based on the accountComponent state + accountComponent + )} +
+ ); +} diff --git a/src/app/screens/Enable/WeblnEnable.tsx b/src/app/screens/Enable/WeblnEnable.tsx new file mode 100644 index 0000000000..e28a60a8f6 --- /dev/null +++ b/src/app/screens/Enable/WeblnEnable.tsx @@ -0,0 +1,9 @@ +import Enable from "~/app/components/Enable"; +import { useNavigationState } from "~/app/hooks/useNavigationState"; +import { OriginData } from "~/types"; + +export default function WeblnEnable() { + const navState = useNavigationState(); + const origin = navState.origin as OriginData; + return ; +} diff --git a/src/app/screens/Enable/index.tsx b/src/app/screens/Enable/index.tsx deleted file mode 100644 index 07a76a6876..0000000000 --- a/src/app/screens/Enable/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; -import ConfirmOrCancel from "@components/ConfirmOrCancel"; -import Container from "@components/Container"; -import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import ScreenHeader from "~/app/components/ScreenHeader"; -import toast from "~/app/components/Toast"; -import { USER_REJECTED_ERROR } from "~/common/constants"; -import msg from "~/common/lib/msg"; -import type { OriginData } from "~/types"; - -type Props = { - origin: OriginData; -}; - -function Enable(props: Props) { - const hasFetchedData = useRef(false); - const [loading, setLoading] = useState(false); - const { t } = useTranslation("translation", { - keyPrefix: "enable", - }); - const { t: tCommon } = useTranslation("common"); - - const enable = useCallback(() => { - try { - setLoading(true); - msg.reply({ - enabled: true, - remember: true, - }); - } catch (e) { - console.error(e); - if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); - } finally { - setLoading(false); - } - }, [tCommon]); - - function reject(event: React.MouseEvent) { - event.preventDefault(); - msg.error(USER_REJECTED_ERROR); - } - - async function block(event: React.MouseEvent) { - event.preventDefault(); - await msg.request("addBlocklist", { - domain: props.origin.domain, - host: props.origin.host, - }); - alert(t("block_added", { host: props.origin.host })); - msg.error(USER_REJECTED_ERROR); - } - - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - - return ( -
- - -
- - -
-

{t("allow")}

- -
- -

{t("request1")}

-
-
- -

{t("request2")}

-
-
-
- -
-
- ); -} - -export default Enable; From d2a32c276db2337685b4766bf5193296d5c8ffc1 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 12:25:44 +0530 Subject: [PATCH 07/18] feat: cleanup screens --- src/app/router/Prompt/Prompt.tsx | 14 ++++- src/app/screens/Enable/AlbyEnable.tsx | 11 ++-- src/app/screens/Enable/LiquidEnable.tsx | 70 +++-------------------- src/app/screens/Enable/NostrEnable.tsx | 74 +++---------------------- src/app/screens/Enable/WebbtcEnable.tsx | 70 +++-------------------- src/app/screens/Enable/WeblnEnable.tsx | 11 ++-- 6 files changed, 49 insertions(+), 201 deletions(-) diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 655ca7e640..18ee8696b0 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -76,8 +76,18 @@ function Prompt() { /> } /> - } /> - } /> + + } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp + /> + + } // prompt will always have an `origin` set, just the type is optional to support usage via PopUp + /> ; +type Props = { + origin: OriginData; +}; + +export default function AlbyEnable(props: Props) { + return ; } diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 4ae5fa3a24..fb1726323b 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -1,10 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; -import toast from "~/app/components/Toast"; +import { useAccount } from "~/app/context/AccountContext"; import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; -import msg from "~/common/lib/msg"; import type { OriginData } from "~/types"; type Props = { @@ -12,80 +10,28 @@ type Props = { }; export default function LiquidEnable(props: Props) { - const hasFetchedData = useRef(false); - const [loading, setLoading] = useState(false); const [accountComponent, setAccountComponent] = useState(null); // State to store the component to render - const { t: tCommon } = useTranslation("common"); - - const enable = useCallback(() => { - try { - setLoading(true); - msg.reply({ - enabled: true, - remember: true, - }); - } catch (e) { - console.error(e); - if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); - } finally { - setLoading(false); - } - }, [tCommon]); - - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - const account = await api.getAccount(); - if (allowance && allowance.enabled && account.nostrEnabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); + const { account } = useAccount(); useEffect(() => { - // Fetch account data and set the component to render based on the result - async function fetchAccountAndSetComponent() { try { - const account = await api.getAccount(); + const fetchedAccount = await api.getAccount(); - if (account.nostrEnabled) { + if (fetchedAccount.hasMnemonic) { setAccountComponent(); } else { setAccountComponent(); } } catch (e) { - // Handle any errors that occur during account fetching console.error(e); } } - fetchAccountAndSetComponent(); // Call the function when the component mounts - }, [props.origin]); + fetchAccountAndSetComponent(); + }, [props.origin, account]); - return ( -
- {loading ? ( -
Loading...
- ) : ( - // Render the component based on the accountComponent state - accountComponent - )} -
- ); + return
{accountComponent}
; } diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index dda491d7bb..192ab49452 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -1,93 +1,37 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; -import toast from "~/app/components/Toast"; +import { useAccount } from "~/app/context/AccountContext"; import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; -import msg from "~/common/lib/msg"; import type { OriginData } from "~/types"; type Props = { origin: OriginData; }; -function NostrEnable(props: Props) { - const hasFetchedData = useRef(false); - const [loading, setLoading] = useState(false); +export default function NostrEnable(props: Props) { const [accountComponent, setAccountComponent] = useState(null); // State to store the component to render - const { t: tCommon } = useTranslation("common"); - - const enable = useCallback(() => { - try { - setLoading(true); - msg.reply({ - enabled: true, - remember: true, - }); - } catch (e) { - console.error(e); - if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); - } finally { - setLoading(false); - } - }, [tCommon]); + const { account } = useAccount(); useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - const account = await api.getAccount(); - if (allowance && allowance.enabled && account.nostrEnabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - - useEffect(() => { - // Fetch account data and set the component to render based on the result - async function fetchAccountAndSetComponent() { try { - const account = await api.getAccount(); + const fetchedAccount = await api.getAccount(); - if (account.nostrEnabled) { + if (fetchedAccount.nostrEnabled) { setAccountComponent(); } else { setAccountComponent(); } } catch (e) { - // Handle any errors that occur during account fetching console.error(e); } } - fetchAccountAndSetComponent(); // Call the function when the component mounts - }, [props.origin]); + fetchAccountAndSetComponent(); + }, [props.origin, account]); - return ( -
- {loading ? ( -
Loading...
- ) : ( - // Render the component based on the accountComponent state - accountComponent - )} -
- ); + return
{accountComponent}
; } - -export default NostrEnable; diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index 64b6100eae..fea71485d3 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -1,10 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; -import toast from "~/app/components/Toast"; +import { useAccount } from "~/app/context/AccountContext"; import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; -import msg from "~/common/lib/msg"; import type { OriginData } from "~/types"; type Props = { @@ -12,80 +10,28 @@ type Props = { }; export default function WebbtcEnable(props: Props) { - const hasFetchedData = useRef(false); - const [loading, setLoading] = useState(false); const [accountComponent, setAccountComponent] = useState(null); // State to store the component to render - const { t: tCommon } = useTranslation("common"); - - const enable = useCallback(() => { - try { - setLoading(true); - msg.reply({ - enabled: true, - remember: true, - }); - } catch (e) { - console.error(e); - if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); - } finally { - setLoading(false); - } - }, [tCommon]); - - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - const account = await api.getAccount(); - if (allowance && allowance.enabled && account.nostrEnabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); + const { account } = useAccount(); useEffect(() => { - // Fetch account data and set the component to render based on the result - async function fetchAccountAndSetComponent() { try { - const account = await api.getAccount(); + const fetchedAccount = await api.getAccount(); - if (account.nostrEnabled) { + if (fetchedAccount.hasMnemonic) { setAccountComponent(); } else { setAccountComponent(); } } catch (e) { - // Handle any errors that occur during account fetching console.error(e); } } - fetchAccountAndSetComponent(); // Call the function when the component mounts - }, [props.origin]); + fetchAccountAndSetComponent(); + }, [props.origin, account]); - return ( -
- {loading ? ( -
Loading...
- ) : ( - // Render the component based on the accountComponent state - accountComponent - )} -
- ); + return
{accountComponent}
; } diff --git a/src/app/screens/Enable/WeblnEnable.tsx b/src/app/screens/Enable/WeblnEnable.tsx index e28a60a8f6..a999b4b0ca 100644 --- a/src/app/screens/Enable/WeblnEnable.tsx +++ b/src/app/screens/Enable/WeblnEnable.tsx @@ -1,9 +1,10 @@ import Enable from "~/app/components/Enable"; -import { useNavigationState } from "~/app/hooks/useNavigationState"; import { OriginData } from "~/types"; -export default function WeblnEnable() { - const navState = useNavigationState(); - const origin = navState.origin as OriginData; - return ; +type Props = { + origin: OriginData; +}; + +export default function WeblnEnable(props: Props) { + return ; } From 49b69339e206c1fb45dfc0d66bf48ffb1b1829a6 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 12:33:49 +0530 Subject: [PATCH 08/18] refactor: move onboarding to components --- .../{screens/Onboard/Prompt => components/onboard}/index.tsx | 0 src/app/router/Prompt/Prompt.tsx | 2 +- src/app/screens/Enable/LiquidEnable.tsx | 2 +- src/app/screens/Enable/NostrEnable.tsx | 2 +- src/app/screens/Enable/WebbtcEnable.tsx | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/app/{screens/Onboard/Prompt => components/onboard}/index.tsx (100%) diff --git a/src/app/screens/Onboard/Prompt/index.tsx b/src/app/components/onboard/index.tsx similarity index 100% rename from src/app/screens/Onboard/Prompt/index.tsx rename to src/app/components/onboard/index.tsx diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 18ee8696b0..f713015da4 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -19,6 +19,7 @@ import Unlock from "@screens/Unlock"; import { HashRouter, Navigate, Outlet, Route, Routes } from "react-router-dom"; import AlbyLogo from "~/app/components/AlbyLogo"; import Toaster from "~/app/components/Toast/Toaster"; +import Onboard from "~/app/components/onboard"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; @@ -27,7 +28,6 @@ import LiquidEnable from "~/app/screens/Enable/LiquidEnable"; import NostrEnable from "~/app/screens/Enable/NostrEnable"; import WebbtcEnable from "~/app/screens/Enable/WebbtcEnable"; import WeblnEnable from "~/app/screens/Enable/WeblnEnable"; -import Onboard from "~/app/screens/Onboard/Prompt"; import type { NavigationState, OriginData } from "~/types"; // Parse out the parameters from the querystring. diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index fb1726323b..0e2b79e654 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; +import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; -import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; import type { OriginData } from "~/types"; diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index 192ab49452..a1cc51f4eb 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; +import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; -import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; import type { OriginData } from "~/types"; diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index fea71485d3..235878298d 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import Enable from "~/app/components/Enable"; +import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; -import Onboard from "~/app/screens/Onboard/Prompt"; import api from "~/common/lib/api"; import type { OriginData } from "~/types"; From 552d1a0293a398d7aabd5bc4e94fbbe815e4ba8e Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 14:19:05 +0530 Subject: [PATCH 09/18] refactor: background prompts and screens for the providers --- .../Enable/{index.tsx => AlbyEnable.tsx} | 4 +- src/app/components/Enable/LiquidEnable.tsx | 122 ++++++++++++++++++ src/app/components/Enable/NostrEnable.tsx | 122 ++++++++++++++++++ src/app/components/Enable/WebbtcEnable.tsx | 122 ++++++++++++++++++ src/app/components/Enable/WeblnEnable.tsx | 122 ++++++++++++++++++ src/app/screens/Enable/AlbyEnable.tsx | 4 +- src/app/screens/Enable/LiquidEnable.tsx | 5 +- src/app/screens/Enable/NostrEnable.tsx | 4 +- src/app/screens/Enable/WebbtcEnable.tsx | 4 +- src/app/screens/Enable/WeblnEnable.tsx | 4 +- .../{allowances/enable => alby}/enable.ts | 4 +- .../background-script/actions/alby/index.ts | 2 + .../allowances/__tests__/enable.test.ts | 2 +- .../actions/allowances/index.ts | 15 +-- .../mnemonicEnable.ts => liquid/enable.ts} | 11 +- .../background-script/actions/liquid/index.ts | 2 + .../enable/nostrEnable.ts => nostr/enable.ts} | 8 +- .../background-script/actions/nostr/index.ts | 2 + .../actions/webbtc/enable.ts | 80 ++++++++++++ .../background-script/actions/webbtc/index.ts | 3 +- .../background-script/actions/webln/enable.ts | 79 ++++++++++++ .../background-script/actions/webln/index.ts | 10 +- src/extension/background-script/router.ts | 14 +- 23 files changed, 692 insertions(+), 53 deletions(-) rename src/app/components/Enable/{index.tsx => AlbyEnable.tsx} (97%) create mode 100644 src/app/components/Enable/LiquidEnable.tsx create mode 100644 src/app/components/Enable/NostrEnable.tsx create mode 100644 src/app/components/Enable/WebbtcEnable.tsx create mode 100644 src/app/components/Enable/WeblnEnable.tsx rename src/extension/background-script/actions/{allowances/enable => alby}/enable.ts (96%) create mode 100644 src/extension/background-script/actions/alby/index.ts rename src/extension/background-script/actions/{allowances/enable/mnemonicEnable.ts => liquid/enable.ts} (92%) rename src/extension/background-script/actions/{allowances/enable/nostrEnable.ts => nostr/enable.ts} (92%) create mode 100644 src/extension/background-script/actions/webbtc/enable.ts create mode 100644 src/extension/background-script/actions/webln/enable.ts diff --git a/src/app/components/Enable/index.tsx b/src/app/components/Enable/AlbyEnable.tsx similarity index 97% rename from src/app/components/Enable/index.tsx rename to src/app/components/Enable/AlbyEnable.tsx index 00fd3f3301..81f611cbb7 100644 --- a/src/app/components/Enable/index.tsx +++ b/src/app/components/Enable/AlbyEnable.tsx @@ -13,7 +13,7 @@ import type { OriginData } from "~/types"; type Props = { origin: OriginData; }; -function Enable(props: Props) { +function AlbyEnableComponent(props: Props) { const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { @@ -119,4 +119,4 @@ function Enable(props: Props) { ); } -export default Enable; +export default AlbyEnableComponent; diff --git a/src/app/components/Enable/LiquidEnable.tsx b/src/app/components/Enable/LiquidEnable.tsx new file mode 100644 index 0000000000..3ebd732f12 --- /dev/null +++ b/src/app/components/Enable/LiquidEnable.tsx @@ -0,0 +1,122 @@ +import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; +function LiquidEnableComponent(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation("translation", { + keyPrefix: "enable", + }); + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + function reject(event: React.MouseEvent) { + event.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + async function block(event: React.MouseEvent) { + event.preventDefault(); + await msg.request("addBlocklist", { + domain: props.origin.domain, + host: props.origin.host, + }); + alert(t("block_added", { host: props.origin.host })); + msg.error(USER_REJECTED_ERROR); + } + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + if (allowance && allowance.enabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + return ( +
+ + +
+ + +
+

{t("allow")}

+ +
+ +

{t("request1")}

+
+
+ +

{t("request2")}

+
+
+
+ +
+
+ ); +} + +export default LiquidEnableComponent; diff --git a/src/app/components/Enable/NostrEnable.tsx b/src/app/components/Enable/NostrEnable.tsx new file mode 100644 index 0000000000..0003fe94c5 --- /dev/null +++ b/src/app/components/Enable/NostrEnable.tsx @@ -0,0 +1,122 @@ +import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; +function NostrEnableComponent(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation("translation", { + keyPrefix: "enable", + }); + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + function reject(event: React.MouseEvent) { + event.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + async function block(event: React.MouseEvent) { + event.preventDefault(); + await msg.request("addBlocklist", { + domain: props.origin.domain, + host: props.origin.host, + }); + alert(t("block_added", { host: props.origin.host })); + msg.error(USER_REJECTED_ERROR); + } + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + if (allowance && allowance.enabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + return ( +
+ + +
+ + +
+

{t("allow")}

+ +
+ +

{t("request1")}

+
+
+ +

{t("request2")}

+
+
+
+ +
+
+ ); +} + +export default NostrEnableComponent; diff --git a/src/app/components/Enable/WebbtcEnable.tsx b/src/app/components/Enable/WebbtcEnable.tsx new file mode 100644 index 0000000000..e8f4fb469f --- /dev/null +++ b/src/app/components/Enable/WebbtcEnable.tsx @@ -0,0 +1,122 @@ +import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; +function WebbtcEnableComponent(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation("translation", { + keyPrefix: "enable", + }); + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + function reject(event: React.MouseEvent) { + event.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + async function block(event: React.MouseEvent) { + event.preventDefault(); + await msg.request("addBlocklist", { + domain: props.origin.domain, + host: props.origin.host, + }); + alert(t("block_added", { host: props.origin.host })); + msg.error(USER_REJECTED_ERROR); + } + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + if (allowance && allowance.enabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + return ( +
+ + +
+ + +
+

{t("allow")}

+ +
+ +

{t("request1")}

+
+
+ +

{t("request2")}

+
+
+
+ +
+
+ ); +} + +export default WebbtcEnableComponent; diff --git a/src/app/components/Enable/WeblnEnable.tsx b/src/app/components/Enable/WeblnEnable.tsx new file mode 100644 index 0000000000..c153a0e718 --- /dev/null +++ b/src/app/components/Enable/WeblnEnable.tsx @@ -0,0 +1,122 @@ +import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import msg from "~/common/lib/msg"; +import type { OriginData } from "~/types"; + +type Props = { + origin: OriginData; +}; +function WeblnEnableComponent(props: Props) { + const hasFetchedData = useRef(false); + const [loading, setLoading] = useState(false); + const { t } = useTranslation("translation", { + keyPrefix: "enable", + }); + const { t: tCommon } = useTranslation("common"); + + const enable = useCallback(() => { + try { + setLoading(true); + msg.reply({ + enabled: true, + remember: true, + }); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + }, [tCommon]); + + function reject(event: React.MouseEvent) { + event.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + async function block(event: React.MouseEvent) { + event.preventDefault(); + await msg.request("addBlocklist", { + domain: props.origin.domain, + host: props.origin.host, + }); + alert(t("block_added", { host: props.origin.host })); + msg.error(USER_REJECTED_ERROR); + } + + useEffect(() => { + async function getAllowance() { + try { + const allowance = await msg.request("getAllowance", { + domain: props.origin.domain, + host: props.origin.host, + }); + if (allowance && allowance.enabled) { + enable(); + } + } catch (e) { + if (e instanceof Error) console.error(e.message); + } + } + + // Run once. + if (!hasFetchedData.current) { + getAllowance(); + hasFetchedData.current = true; + } + }, [enable, props.origin.domain, props.origin.host]); + + return ( +
+ + +
+ + +
+

{t("allow")}

+ +
+ +

{t("request1")}

+
+
+ +

{t("request2")}

+
+
+
+ +
+
+ ); +} + +export default WeblnEnableComponent; diff --git a/src/app/screens/Enable/AlbyEnable.tsx b/src/app/screens/Enable/AlbyEnable.tsx index 837e139e7b..18238c7a8b 100644 --- a/src/app/screens/Enable/AlbyEnable.tsx +++ b/src/app/screens/Enable/AlbyEnable.tsx @@ -1,4 +1,4 @@ -import Enable from "~/app/components/Enable"; +import AlbyEnableComponent from "~/app/components/Enable/AlbyEnable"; import { OriginData } from "~/types"; type Props = { @@ -6,5 +6,5 @@ type Props = { }; export default function AlbyEnable(props: Props) { - return ; + return ; } diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 0e2b79e654..351b927059 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; -import Enable from "~/app/components/Enable"; +import LiquidEnableComponent from "~/app/components/Enable/LiquidEnable"; + import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -21,7 +22,7 @@ export default function LiquidEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.hasMnemonic) { - setAccountComponent(); + setAccountComponent(); } else { setAccountComponent(); } diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index a1cc51f4eb..28188146ca 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import Enable from "~/app/components/Enable"; +import NostrEnableComponent from "~/app/components/Enable/NostrEnable"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -21,7 +21,7 @@ export default function NostrEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.nostrEnabled) { - setAccountComponent(); + setAccountComponent(); } else { setAccountComponent(); } diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index 235878298d..0b180787c4 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import Enable from "~/app/components/Enable"; +import WebbtcEnableComponent from "~/app/components/Enable/WebbtcEnable"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -21,7 +21,7 @@ export default function WebbtcEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.hasMnemonic) { - setAccountComponent(); + setAccountComponent(); } else { setAccountComponent(); } diff --git a/src/app/screens/Enable/WeblnEnable.tsx b/src/app/screens/Enable/WeblnEnable.tsx index a999b4b0ca..827dd18865 100644 --- a/src/app/screens/Enable/WeblnEnable.tsx +++ b/src/app/screens/Enable/WeblnEnable.tsx @@ -1,4 +1,4 @@ -import Enable from "~/app/components/Enable"; +import WeblnEnableComponent from "~/app/components/Enable/WeblnEnable"; import { OriginData } from "~/types"; type Props = { @@ -6,5 +6,5 @@ type Props = { }; export default function WeblnEnable(props: Props) { - return ; + return ; } diff --git a/src/extension/background-script/actions/allowances/enable/enable.ts b/src/extension/background-script/actions/alby/enable.ts similarity index 96% rename from src/extension/background-script/actions/allowances/enable/enable.ts rename to src/extension/background-script/actions/alby/enable.ts index 488b3db9ba..5bb4c0b2d2 100644 --- a/src/extension/background-script/actions/allowances/enable/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -3,8 +3,8 @@ import { getHostFromSender } from "~/common/utils/helpers"; import db from "~/extension/background-script/db"; import type { MessageAllowanceEnable, Sender } from "~/types"; -import state from "../../../state"; -import { ExtensionIcon, setIcon } from "../../setup/setIcon"; +import state from "../../state"; +import { ExtensionIcon, setIcon } from "../setup/setIcon"; const enable = async (message: MessageAllowanceEnable, sender: Sender) => { const host = getHostFromSender(sender); diff --git a/src/extension/background-script/actions/alby/index.ts b/src/extension/background-script/actions/alby/index.ts new file mode 100644 index 0000000000..e9478e1569 --- /dev/null +++ b/src/extension/background-script/actions/alby/index.ts @@ -0,0 +1,2 @@ +import enable from "./enable"; +export { enable }; diff --git a/src/extension/background-script/actions/allowances/__tests__/enable.test.ts b/src/extension/background-script/actions/allowances/__tests__/enable.test.ts index 32fa3c4404..e8b957b5d2 100644 --- a/src/extension/background-script/actions/allowances/__tests__/enable.test.ts +++ b/src/extension/background-script/actions/allowances/__tests__/enable.test.ts @@ -4,7 +4,7 @@ import state from "~/extension/background-script/state"; import { allowanceFixture } from "~/fixtures/allowances"; import type { DbAllowance, MessageAllowanceEnable, Sender } from "~/types"; -import enableAllowance from "../enable/enable"; +import enableAllowance from "../../webln/enable"; jest.mock("~/extension/background-script/state"); diff --git a/src/extension/background-script/actions/allowances/index.ts b/src/extension/background-script/actions/allowances/index.ts index cc0c68bc8d..bc39862c9d 100644 --- a/src/extension/background-script/actions/allowances/index.ts +++ b/src/extension/background-script/actions/allowances/index.ts @@ -1,21 +1,8 @@ import add from "./add"; import deleteAllowance from "./delete"; -import enable from "./enable/enable"; -import mnemonicEnable from "./enable/mnemonicEnable"; -import nostrEnable from "./enable/nostrEnable"; import get from "./get"; import getById from "./getById"; import list from "./list"; import updateAllowance from "./update"; -export { - add, - deleteAllowance, - enable, - get, - getById, - list, - mnemonicEnable, - nostrEnable, - updateAllowance, -}; +export { add, deleteAllowance, get, getById, list, updateAllowance }; diff --git a/src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts b/src/extension/background-script/actions/liquid/enable.ts similarity index 92% rename from src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts rename to src/extension/background-script/actions/liquid/enable.ts index abafb6944c..166205438c 100644 --- a/src/extension/background-script/actions/allowances/enable/mnemonicEnable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -3,13 +3,10 @@ import { getHostFromSender } from "~/common/utils/helpers"; import db from "~/extension/background-script/db"; import type { MessageAllowanceEnable, Sender } from "~/types"; -import state from "../../../state"; -import { ExtensionIcon, setIcon } from "../../setup/setIcon"; +import state from "../../state"; +import { ExtensionIcon, setIcon } from "../setup/setIcon"; -const mnemonicEnable = async ( - message: MessageAllowanceEnable, - sender: Sender -) => { +const enable = async (message: MessageAllowanceEnable, sender: Sender) => { const host = getHostFromSender(sender); if (!host) return; @@ -80,4 +77,4 @@ const mnemonicEnable = async ( } }; -export default mnemonicEnable; +export default enable; diff --git a/src/extension/background-script/actions/liquid/index.ts b/src/extension/background-script/actions/liquid/index.ts index 27255b8e1d..e1e48d8bd0 100644 --- a/src/extension/background-script/actions/liquid/index.ts +++ b/src/extension/background-script/actions/liquid/index.ts @@ -1,3 +1,4 @@ +import enable from "./enable"; import fetchAssetRegistry from "./fetchAssetRegistry"; import getAddressOrPrompt from "./getAddressOrPrompt"; import getPsetPreview from "./getPsetPreview"; @@ -5,6 +6,7 @@ import signPset from "./signPset"; import signPsetWithPrompt from "./signPsetWithPrompt"; export { + enable, fetchAssetRegistry, getAddressOrPrompt, getPsetPreview, diff --git a/src/extension/background-script/actions/allowances/enable/nostrEnable.ts b/src/extension/background-script/actions/nostr/enable.ts similarity index 92% rename from src/extension/background-script/actions/allowances/enable/nostrEnable.ts rename to src/extension/background-script/actions/nostr/enable.ts index 3b073a704f..0b3f078201 100644 --- a/src/extension/background-script/actions/allowances/enable/nostrEnable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -3,10 +3,10 @@ import { getHostFromSender } from "~/common/utils/helpers"; import db from "~/extension/background-script/db"; import type { MessageAllowanceEnable, Sender } from "~/types"; -import state from "../../../state"; -import { ExtensionIcon, setIcon } from "../../setup/setIcon"; +import state from "../../state"; +import { ExtensionIcon, setIcon } from "../setup/setIcon"; -const nostrEnable = async (message: MessageAllowanceEnable, sender: Sender) => { +const enable = async (message: MessageAllowanceEnable, sender: Sender) => { const host = getHostFromSender(sender); if (!host) return; @@ -83,4 +83,4 @@ const nostrEnable = async (message: MessageAllowanceEnable, sender: Sender) => { } }; -export default nostrEnable; +export default enable; diff --git a/src/extension/background-script/actions/nostr/index.ts b/src/extension/background-script/actions/nostr/index.ts index cf1e1d38f5..54ae6b8c47 100644 --- a/src/extension/background-script/actions/nostr/index.ts +++ b/src/extension/background-script/actions/nostr/index.ts @@ -1,6 +1,7 @@ import getPublicKey from "~/extension/background-script/actions/nostr/getPublicKey"; import decryptOrPrompt from "./decryptOrPrompt"; +import enable from "./enable"; import encryptOrPrompt from "./encryptOrPrompt"; import generatePrivateKey from "./generatePrivateKey"; import getPrivateKey from "./getPrivateKey"; @@ -13,6 +14,7 @@ import signSchnorrOrPrompt from "./signSchnorrOrPrompt"; export { decryptOrPrompt, + enable, encryptOrPrompt, generatePrivateKey, getPrivateKey, diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts new file mode 100644 index 0000000000..166205438c --- /dev/null +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -0,0 +1,80 @@ +import utils from "~/common/lib/utils"; +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; +import { ExtensionIcon, setIcon } from "../setup/setIcon"; + +const enable = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const account = await state.getState().getAccount(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once + // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. + // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { + return { + data: { enabled: true }, + }; + } else { + try { + const response = await utils.openPrompt<{ + enabled: boolean; + remember: boolean; + }>(message); + + if (response.data.enabled && sender.tab) { + await setIcon(ExtensionIcon.Active, sender.tab.id as number); // highlight the icon when enabled + } + + // if the response should be saved/remembered we update the allowance for the domain + // as this returns a promise we must wait until it resolves + if (response.data.enabled && response.data.remember) { + if (allowance) { + if (!allowance.id) { + return { data: { error: "id is missing" } }; + } + await db.allowances.update(allowance.id, { + enabled: true, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } else { + await db.allowances.add({ + host: host, + name: message.origin.name, + imageURL: message.origin.icon, + enabled: true, + lastPaymentAt: 0, + totalBudget: 0, + remainingBudget: 0, + createdAt: Date.now().toString(), + lnurlAuth: false, + tag: "", + }); + } + await db.saveToStorage(); + } + return { + data: { + enabled: response.data.enabled, + remember: response.data.remember, + }, + }; + } catch (e) { + console.error(e); + if (e instanceof Error) { + return { error: e.message }; + } + } + } +}; + +export default enable; diff --git a/src/extension/background-script/actions/webbtc/index.ts b/src/extension/background-script/actions/webbtc/index.ts index ece0f0d4f7..18779f9280 100644 --- a/src/extension/background-script/actions/webbtc/index.ts +++ b/src/extension/background-script/actions/webbtc/index.ts @@ -1,5 +1,6 @@ +import enable from "./enable"; import getAddress from "./getAddress"; import getAddressOrPrompt from "./getAddressOrPrompt"; import getInfo from "./getInfo"; -export { getAddress, getAddressOrPrompt, getInfo }; +export { enable, getAddress, getAddressOrPrompt, getInfo }; diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts new file mode 100644 index 0000000000..5bb4c0b2d2 --- /dev/null +++ b/src/extension/background-script/actions/webln/enable.ts @@ -0,0 +1,79 @@ +import utils from "~/common/lib/utils"; +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; +import { ExtensionIcon, setIcon } from "../setup/setIcon"; + +const enable = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once + // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. + // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled) { + return { + data: { enabled: true }, + }; + } else { + try { + const response = await utils.openPrompt<{ + enabled: boolean; + remember: boolean; + }>(message); + + if (response.data.enabled && sender.tab) { + await setIcon(ExtensionIcon.Active, sender.tab.id as number); // highlight the icon when enabled + } + + // if the response should be saved/remembered we update the allowance for the domain + // as this returns a promise we must wait until it resolves + if (response.data.enabled && response.data.remember) { + if (allowance) { + if (!allowance.id) { + return { data: { error: "id is missing" } }; + } + await db.allowances.update(allowance.id, { + enabled: true, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } else { + await db.allowances.add({ + host: host, + name: message.origin.name, + imageURL: message.origin.icon, + enabled: true, + lastPaymentAt: 0, + totalBudget: 0, + remainingBudget: 0, + createdAt: Date.now().toString(), + lnurlAuth: false, + tag: "", + }); + } + await db.saveToStorage(); + } + return { + data: { + enabled: response.data.enabled, + remember: response.data.remember, + }, + }; + } catch (e) { + console.error(e); + if (e instanceof Error) { + return { error: e.message }; + } + } + } +}; + +export default enable; diff --git a/src/extension/background-script/actions/webln/index.ts b/src/extension/background-script/actions/webln/index.ts index 16cb886fbd..faa8513fec 100644 --- a/src/extension/background-script/actions/webln/index.ts +++ b/src/extension/background-script/actions/webln/index.ts @@ -1,3 +1,4 @@ +import enable from "./enable"; import getBalanceOrPrompt from "./getBalanceOrPrompt"; import keysendOrPrompt from "./keysendOrPrompt"; import lnurl from "./lnurl"; @@ -6,10 +7,11 @@ import { sendPaymentOrPrompt } from "./sendPaymentOrPrompt"; import signMessageOrPrompt from "./signMessageOrPrompt"; export { - sendPaymentOrPrompt, + enable, + getBalanceOrPrompt, keysendOrPrompt, - signMessageOrPrompt, - makeInvoiceOrPrompt, lnurl, - getBalanceOrPrompt, + makeInvoiceOrPrompt, + sendPaymentOrPrompt, + signMessageOrPrompt, }; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 81fe8f19e1..1cd1b50b4e 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -1,4 +1,5 @@ import * as accounts from "./actions/accounts"; +import * as alby from "./actions/alby"; import * as allowances from "./actions/allowances"; import * as blocklist from "./actions/blocklist"; import * as cache from "./actions/cache"; @@ -84,18 +85,17 @@ const routes = { // Public calls that are accessible from the inpage script (through the content script) public: { webbtc: { - onboard: onboard.prompt, - enable: allowances.mnemonicEnable, + enable: webbtc.enable, getInfo: webbtc.getInfo, getAddressOrPrompt: webbtc.getAddressOrPrompt, }, alby: { - enable: allowances.enable, + enable: alby.enable, addAccount: accounts.promptAdd, }, webln: { onboard: onboard.prompt, - enable: allowances.enable, + enable: webln.enable, getInfo: ln.getInfo, sendPaymentOrPrompt: webln.sendPaymentOrPrompt, keysendOrPrompt: webln.keysendOrPrompt, @@ -106,14 +106,12 @@ const routes = { request: ln.request, }, liquid: { - onboard: onboard.prompt, - enable: allowances.mnemonicEnable, + enable: liquid.enable, getAddressOrPrompt: liquid.getAddressOrPrompt, signPsetWithPrompt: liquid.signPsetWithPrompt, }, nostr: { - onboard: onboard.prompt, - enable: allowances.nostrEnable, + enable: nostr.enable, getPublicKeyOrPrompt: nostr.getPublicKeyOrPrompt, signEventOrPrompt: nostr.signEventOrPrompt, signSchnorrOrPrompt: nostr.signSchnorrOrPrompt, From 77d1472b57889aee99d5dd0e2487b27714f164ab Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 14:20:27 +0530 Subject: [PATCH 10/18] chore: remove onboarding from content script --- src/extension/content-script/liquid.js | 10 ---------- src/extension/content-script/webbtc.js | 11 +---------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/extension/content-script/liquid.js b/src/extension/content-script/liquid.js index 65d22d4f24..0d1f5e147a 100644 --- a/src/extension/content-script/liquid.js +++ b/src/extension/content-script/liquid.js @@ -1,6 +1,5 @@ import browser from "webextension-polyfill"; -import api from "~/common/lib/api"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; @@ -16,7 +15,6 @@ const disabledCalls = ["liquid/enable"]; let isEnabled = false; // store if liquid is enabled for this content page let isRejected = false; // store if the liquid enable call failed. if so we do not prompt again -let account; const SCOPE = "liquid"; @@ -68,14 +66,6 @@ async function init() { origin: getOriginData(), }; - // Overrides the enable action so the user can go through onboarding to setup their keys - if (!account || !account.hasMnemonic) { - account = await api.getAccount(); - if (!account.hasMnemonic) { - messageWithOrigin.action = ev.data.action = `public/liquid/onboard`; - } - } - const replyFunction = (response) => { if (ev.data.action === `${SCOPE}/enable`) { isEnabled = response.data?.enabled; diff --git a/src/extension/content-script/webbtc.js b/src/extension/content-script/webbtc.js index 2470b6d4cd..db2ff09d87 100644 --- a/src/extension/content-script/webbtc.js +++ b/src/extension/content-script/webbtc.js @@ -1,6 +1,5 @@ import browser from "webextension-polyfill"; -import api from "~/common/lib/api"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; // WebBTC calls that can be executed from the WebBTC Provider. @@ -15,7 +14,7 @@ const disabledCalls = ["webbtc/enable"]; let isEnabled = false; // store if webbtc is enabled for this content page let isRejected = false; // store if the webbtc enable call failed. if so we do not prompt again -let account; + const SCOPE = "webbtc"; async function init() { @@ -66,14 +65,6 @@ async function init() { origin: getOriginData(), }; - // Overrides the enable action so the user can go through onboarding to setup their keys - if (!account || !account.hasMnemonic) { - account = await api.getAccount(); - if (!account.hasMnemonic) { - messageWithOrigin.action = ev.data.action = `public/webbtc/onboard`; - } - } - const replyFunction = (response) => { if (ev.data.action === `${SCOPE}/enable`) { isEnabled = response.data?.enabled; From 6d2785ca2a62c13b2a4e1499ae3f2d479f279501 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 14:23:49 +0530 Subject: [PATCH 11/18] chore: remove comments --- src/extension/background-script/actions/alby/enable.ts | 4 +--- src/extension/background-script/actions/liquid/enable.ts | 4 +--- src/extension/background-script/actions/nostr/enable.ts | 4 +--- src/extension/background-script/actions/webbtc/enable.ts | 4 +--- src/extension/background-script/actions/webln/enable.ts | 4 +--- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index 5bb4c0b2d2..ceb6665d07 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -15,9 +15,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once - // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. - // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled) { return { data: { enabled: true }, diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index 166205438c..b560416bbf 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -16,9 +16,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once - // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. - // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { return { data: { enabled: true }, diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index 0b3f078201..d8c9fa8d98 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -17,9 +17,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once - // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. - // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if ( isUnlocked && allowance && diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index 166205438c..b560416bbf 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -16,9 +16,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once - // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. - // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { return { data: { enabled: true }, diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index 5bb4c0b2d2..ceb6665d07 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -15,9 +15,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .where("host") .equalsIgnoreCase(host) .first(); - // remove this? cause next time the allowance is set and enable is called we directly return from here.hence onboarding will work only once - // i suggest to not remove it. as if we go to the screen everytime. for other providers. it will be a flickering glitch. - // screen will popup for a second and close automatically as allowance is set but we are returning from the screen if we remove it. + if (isUnlocked && allowance && allowance.enabled) { return { data: { enabled: true }, From c39c77398702c41e43a0162e87de507dbad07c64 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 14 Sep 2023 15:02:22 +0530 Subject: [PATCH 12/18] feat: redesign component rendering for provider screens --- src/app/screens/Enable/LiquidEnable.tsx | 21 +++++++++++++-------- src/app/screens/Enable/NostrEnable.tsx | 20 +++++++++++++------- src/app/screens/Enable/WebbtcEnable.tsx | 22 ++++++++++++++-------- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 351b927059..813acd07de 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -1,6 +1,5 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import LiquidEnableComponent from "~/app/components/Enable/LiquidEnable"; - import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -11,10 +10,8 @@ type Props = { }; export default function LiquidEnable(props: Props) { - const [accountComponent, setAccountComponent] = - useState(null); // State to store the component to render - const { account } = useAccount(); + const [hasMnemonic, setHasMnemonic] = useState(false); useEffect(() => { async function fetchAccountAndSetComponent() { @@ -22,9 +19,9 @@ export default function LiquidEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.hasMnemonic) { - setAccountComponent(); + setHasMnemonic(true); } else { - setAccountComponent(); + setHasMnemonic(false); } } catch (e) { console.error(e); @@ -34,5 +31,13 @@ export default function LiquidEnable(props: Props) { fetchAccountAndSetComponent(); }, [props.origin, account]); - return
{accountComponent}
; + return ( +
+ {hasMnemonic ? ( + + ) : ( + + )} +
+ ); } diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index 28188146ca..e52c7bf783 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import NostrEnableComponent from "~/app/components/Enable/NostrEnable"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; @@ -10,10 +10,8 @@ type Props = { }; export default function NostrEnable(props: Props) { - const [accountComponent, setAccountComponent] = - useState(null); // State to store the component to render - const { account } = useAccount(); + const [hasNostrKeys, setHasNostrkeys] = useState(false); useEffect(() => { async function fetchAccountAndSetComponent() { @@ -21,9 +19,9 @@ export default function NostrEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.nostrEnabled) { - setAccountComponent(); + setHasNostrkeys(true); } else { - setAccountComponent(); + setHasNostrkeys(false); } } catch (e) { console.error(e); @@ -33,5 +31,13 @@ export default function NostrEnable(props: Props) { fetchAccountAndSetComponent(); }, [props.origin, account]); - return
{accountComponent}
; + return ( +
+ {hasNostrKeys ? ( + + ) : ( + + )} +
+ ); } diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index 0b180787c4..a7e1267266 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from "react"; -import WebbtcEnableComponent from "~/app/components/Enable/WebbtcEnable"; +import { useEffect, useState } from "react"; +import LiquidEnableComponent from "~/app/components/Enable/LiquidEnable"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -10,10 +10,8 @@ type Props = { }; export default function WebbtcEnable(props: Props) { - const [accountComponent, setAccountComponent] = - useState(null); // State to store the component to render - const { account } = useAccount(); + const [hasMnemonic, setHasMnemonic] = useState(false); useEffect(() => { async function fetchAccountAndSetComponent() { @@ -21,9 +19,9 @@ export default function WebbtcEnable(props: Props) { const fetchedAccount = await api.getAccount(); if (fetchedAccount.hasMnemonic) { - setAccountComponent(); + setHasMnemonic(true); } else { - setAccountComponent(); + setHasMnemonic(false); } } catch (e) { console.error(e); @@ -33,5 +31,13 @@ export default function WebbtcEnable(props: Props) { fetchAccountAndSetComponent(); }, [props.origin, account]); - return
{accountComponent}
; + return ( +
+ {hasMnemonic ? ( + + ) : ( + + )} +
+ ); } From 9f018d649673d41850d241e603a4cb0b3d3e5bc9 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 18 Sep 2023 11:21:50 +0530 Subject: [PATCH 13/18] chore: remove existing lnurl-auth onboard from webln --- src/extension/content-script/webln.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index fa5822ff03..21b51e9f99 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -1,6 +1,5 @@ import browser from "webextension-polyfill"; -import api from "~/common/lib/api"; import extractLightningData from "./batteries"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; @@ -26,7 +25,6 @@ const disabledCalls = ["webln/enable"]; let isEnabled = false; // store if webln is enabled for this content page let isRejected = false; // store if the webln enable call failed. if so we do not prompt again -let account; async function init() { const inject = await shouldInject(); @@ -91,16 +89,6 @@ async function init() { origin: getOriginData(), }; - // Overrides the enable action so the user can go through onboarding to setup their keys - - // Overrides the enable action so the user can go through onboarding to setup their keys - if (!account || !account.hasMnemonic) { - account = await api.getAccount(); - if (!account.hasMnemonic) { - messageWithOrigin.action = ev.data.action = `public/webln/onboard`; - } - } - const replyFunction = (response) => { // if it is the enable call we store if webln is enabled for this content script if (ev.data.action === "webln/enable") { From 6780075fe2af09aa8437e7fccfe0df932244b72d Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 18 Sep 2023 11:38:43 +0530 Subject: [PATCH 14/18] chore: remove onboard routes as no longer required --- src/app/router/Prompt/Prompt.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index f713015da4..36ffae360c 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -19,7 +19,6 @@ import Unlock from "@screens/Unlock"; import { HashRouter, Navigate, Outlet, Route, Routes } from "react-router-dom"; import AlbyLogo from "~/app/components/AlbyLogo"; import Toaster from "~/app/components/Toast/Toaster"; -import Onboard from "~/app/components/onboard"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; @@ -141,10 +140,6 @@ function Prompt() { } /> } /> } /> - } /> - } /> - } /> - } /> } From ca8189cbd6ba4c09a9f73f29857361bcac5868cf Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 18 Sep 2023 14:31:19 +0530 Subject: [PATCH 15/18] refactor: separate translations for each provider modify contents in the screen clean up blocklist, allowance and enable functions in the provider signed-off-by: pavan joshi --- src/app/components/Enable/AlbyEnable.tsx | 41 ++++++---------------- src/app/components/Enable/LiquidEnable.tsx | 41 ++++++---------------- src/app/components/Enable/NostrEnable.tsx | 37 ++++--------------- src/app/components/Enable/WebbtcEnable.tsx | 41 ++++++---------------- src/app/components/Enable/WeblnEnable.tsx | 41 ++++++---------------- src/app/screens/Enable/LiquidEnable.tsx | 4 +-- src/app/screens/Enable/NostrEnable.tsx | 10 +++--- src/app/screens/Enable/WebbtcEnable.tsx | 4 +-- src/i18n/locales/en/translation.json | 34 +++++++++++++----- 9 files changed, 82 insertions(+), 171 deletions(-) diff --git a/src/app/components/Enable/AlbyEnable.tsx b/src/app/components/Enable/AlbyEnable.tsx index 81f611cbb7..4af8083253 100644 --- a/src/app/components/Enable/AlbyEnable.tsx +++ b/src/app/components/Enable/AlbyEnable.tsx @@ -2,7 +2,7 @@ import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; @@ -14,14 +14,13 @@ type Props = { origin: OriginData; }; function AlbyEnableComponent(props: Props) { - const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { - keyPrefix: "enable", + keyPrefix: "alby_enable", }); const { t: tCommon } = useTranslation("common"); - const enable = useCallback(() => { + const enable = () => { try { setLoading(true); msg.reply({ @@ -34,7 +33,7 @@ function AlbyEnableComponent(props: Props) { } finally { setLoading(false); } - }, [tCommon]); + }; function reject(event: React.MouseEvent) { event.preventDefault(); @@ -47,32 +46,10 @@ function AlbyEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(t("block_added", { host: props.origin.host })); + alert(tCommon("block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - return (
@@ -86,11 +63,13 @@ function AlbyEnableComponent(props: Props) { />
-

{t("allow")}

+

{tCommon("allow")}

-

{t("request1")}

+

+ {tCommon("request_transaction_approval")} +

@@ -111,7 +90,7 @@ function AlbyEnableComponent(props: Props) { href="#" onClick={block} > - {t("block_and_ignore", { host: props.origin.host })} + {tCommon("actions.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/LiquidEnable.tsx b/src/app/components/Enable/LiquidEnable.tsx index 3ebd732f12..eb0248d5d8 100644 --- a/src/app/components/Enable/LiquidEnable.tsx +++ b/src/app/components/Enable/LiquidEnable.tsx @@ -2,7 +2,7 @@ import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; @@ -14,14 +14,13 @@ type Props = { origin: OriginData; }; function LiquidEnableComponent(props: Props) { - const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { - keyPrefix: "enable", + keyPrefix: "liquid_enable", }); const { t: tCommon } = useTranslation("common"); - const enable = useCallback(() => { + const enable = () => { try { setLoading(true); msg.reply({ @@ -34,7 +33,7 @@ function LiquidEnableComponent(props: Props) { } finally { setLoading(false); } - }, [tCommon]); + }; function reject(event: React.MouseEvent) { event.preventDefault(); @@ -47,32 +46,10 @@ function LiquidEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(t("block_added", { host: props.origin.host })); + alert(tCommon("block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - return (
@@ -86,11 +63,13 @@ function LiquidEnableComponent(props: Props) { />
-

{t("allow")}

+

{tCommon("allow")}

-

{t("request1")}

+

+ {tCommon("request_transaction_approval")} +

@@ -111,7 +90,7 @@ function LiquidEnableComponent(props: Props) { href="#" onClick={block} > - {t("block_and_ignore", { host: props.origin.host })} + {tCommon("actions.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/NostrEnable.tsx b/src/app/components/Enable/NostrEnable.tsx index 0003fe94c5..72919c1329 100644 --- a/src/app/components/Enable/NostrEnable.tsx +++ b/src/app/components/Enable/NostrEnable.tsx @@ -2,7 +2,7 @@ import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; @@ -14,14 +14,13 @@ type Props = { origin: OriginData; }; function NostrEnableComponent(props: Props) { - const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { - keyPrefix: "enable", + keyPrefix: "nostr_enable", }); const { t: tCommon } = useTranslation("common"); - const enable = useCallback(() => { + const enable = () => { try { setLoading(true); msg.reply({ @@ -34,7 +33,7 @@ function NostrEnableComponent(props: Props) { } finally { setLoading(false); } - }, [tCommon]); + }; function reject(event: React.MouseEvent) { event.preventDefault(); @@ -47,32 +46,10 @@ function NostrEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(t("block_added", { host: props.origin.host })); + alert(tCommon("block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - return (
@@ -86,7 +63,7 @@ function NostrEnableComponent(props: Props) { />
-

{t("allow")}

+

{tCommon("allow")}

@@ -111,7 +88,7 @@ function NostrEnableComponent(props: Props) { href="#" onClick={block} > - {t("block_and_ignore", { host: props.origin.host })} + {tCommon("actions.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/WebbtcEnable.tsx b/src/app/components/Enable/WebbtcEnable.tsx index e8f4fb469f..19f471c569 100644 --- a/src/app/components/Enable/WebbtcEnable.tsx +++ b/src/app/components/Enable/WebbtcEnable.tsx @@ -2,7 +2,7 @@ import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; @@ -14,14 +14,13 @@ type Props = { origin: OriginData; }; function WebbtcEnableComponent(props: Props) { - const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { - keyPrefix: "enable", + keyPrefix: "webbtc_enable", }); const { t: tCommon } = useTranslation("common"); - const enable = useCallback(() => { + const enable = () => { try { setLoading(true); msg.reply({ @@ -34,7 +33,7 @@ function WebbtcEnableComponent(props: Props) { } finally { setLoading(false); } - }, [tCommon]); + }; function reject(event: React.MouseEvent) { event.preventDefault(); @@ -47,32 +46,10 @@ function WebbtcEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(t("block_added", { host: props.origin.host })); + alert(tCommon("block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - return (
@@ -86,11 +63,13 @@ function WebbtcEnableComponent(props: Props) { />
-

{t("allow")}

+

{tCommon("allow")}

-

{t("request1")}

+

+ {tCommon("request_transaction_approval")} +

@@ -111,7 +90,7 @@ function WebbtcEnableComponent(props: Props) { href="#" onClick={block} > - {t("block_and_ignore", { host: props.origin.host })} + {tCommon("actions.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/WeblnEnable.tsx b/src/app/components/Enable/WeblnEnable.tsx index c153a0e718..1ada6d86b3 100644 --- a/src/app/components/Enable/WeblnEnable.tsx +++ b/src/app/components/Enable/WeblnEnable.tsx @@ -2,7 +2,7 @@ import { CheckIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; @@ -14,14 +14,13 @@ type Props = { origin: OriginData; }; function WeblnEnableComponent(props: Props) { - const hasFetchedData = useRef(false); const [loading, setLoading] = useState(false); const { t } = useTranslation("translation", { - keyPrefix: "enable", + keyPrefix: "webln_enable", }); const { t: tCommon } = useTranslation("common"); - const enable = useCallback(() => { + const enable = () => { try { setLoading(true); msg.reply({ @@ -34,7 +33,7 @@ function WeblnEnableComponent(props: Props) { } finally { setLoading(false); } - }, [tCommon]); + }; function reject(event: React.MouseEvent) { event.preventDefault(); @@ -47,32 +46,10 @@ function WeblnEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(t("block_added", { host: props.origin.host })); + alert(tCommon("block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } - useEffect(() => { - async function getAllowance() { - try { - const allowance = await msg.request("getAllowance", { - domain: props.origin.domain, - host: props.origin.host, - }); - if (allowance && allowance.enabled) { - enable(); - } - } catch (e) { - if (e instanceof Error) console.error(e.message); - } - } - - // Run once. - if (!hasFetchedData.current) { - getAllowance(); - hasFetchedData.current = true; - } - }, [enable, props.origin.domain, props.origin.host]); - return (
@@ -86,11 +63,13 @@ function WeblnEnableComponent(props: Props) { />
-

{t("allow")}

+

{tCommon("allow")}

-

{t("request1")}

+

+ {tCommon("request_transaction_approval")} +

@@ -111,7 +90,7 @@ function WeblnEnableComponent(props: Props) { href="#" onClick={block} > - {t("block_and_ignore", { host: props.origin.host })} + {tCommon("actions.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 813acd07de..27f54eaba0 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -14,7 +14,7 @@ export default function LiquidEnable(props: Props) { const [hasMnemonic, setHasMnemonic] = useState(false); useEffect(() => { - async function fetchAccountAndSetComponent() { + async function fetchAccountInfo() { try { const fetchedAccount = await api.getAccount(); @@ -28,7 +28,7 @@ export default function LiquidEnable(props: Props) { } } - fetchAccountAndSetComponent(); + fetchAccountInfo(); }, [props.origin, account]); return ( diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index e52c7bf783..3cb043e8c3 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -11,24 +11,24 @@ type Props = { export default function NostrEnable(props: Props) { const { account } = useAccount(); - const [hasNostrKeys, setHasNostrkeys] = useState(false); + const [hasNostrKeys, setHasNostrKeys] = useState(false); useEffect(() => { - async function fetchAccountAndSetComponent() { + async function fetchAccountInfo() { try { const fetchedAccount = await api.getAccount(); if (fetchedAccount.nostrEnabled) { - setHasNostrkeys(true); + setHasNostrKeys(true); } else { - setHasNostrkeys(false); + setHasNostrKeys(false); } } catch (e) { console.error(e); } } - fetchAccountAndSetComponent(); + fetchAccountInfo(); }, [props.origin, account]); return ( diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index a7e1267266..b3a6fa9e46 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -14,7 +14,7 @@ export default function WebbtcEnable(props: Props) { const [hasMnemonic, setHasMnemonic] = useState(false); useEffect(() => { - async function fetchAccountAndSetComponent() { + async function fetchAccountInfo() { try { const fetchedAccount = await api.getAccount(); @@ -28,7 +28,7 @@ export default function WebbtcEnable(props: Props) { } } - fetchAccountAndSetComponent(); + fetchAccountInfo(); }, [props.origin, account]); return ( diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 4c2960a1dd..a72bb5a7d8 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -528,13 +528,27 @@ "add_account": "Add account" } }, - "enable": { - "title": "Connect", - "allow": "Allow this website to:", - "request1": "Request approval for transactions", - "request2": "Request invoices and lightning information", - "block_and_ignore": "Block and ignore {{host}}", - "block_added": "Added {{host}} to the blocklist, please reload the website." + "webln_enable": { + "title": "Connect to WebLN", + + "request2": "Request invoices and lightning information" + }, + "alby_enable": { + "title": "Connect to Alby", + "request2": "Request invoices and alby information" + }, + "nostr_enable": { + "title": "Connect to Nostr", + "request1": "Request approval to read your Nostr public key", + "request2": "sign events using your Nostr private key" + }, + "liquid_enable": { + "title": "Connect to Liquid", + "request2": "Request invoices and liquid information" + }, + "webbtc_enable": { + "title": "Connect to WebBTC", + "request2": "Request invoices and liquid information" }, "unlock": { "unlock_to_continue": "Unlock to continue", @@ -957,6 +971,7 @@ } }, "common": { + "allow": "Allow this website to:", "password": "Password", "confirm_password": "Confirm Password", "advanced": "Advanced", @@ -983,6 +998,8 @@ "help": "Help", "balance": "Balance", "or": "or", + "block_added": "Added {{host}} to the blocklist, please reload the website.", + "request_transaction_approval": "Request approval for transactions", "actions": { "back": "Back", "delete": "Delete", @@ -1015,7 +1032,8 @@ "more": "More", "disconnect": "Disconnect", "review": "Review", - "download": "Download" + "download": "Download", + "block_and_ignore": "Block and ignore {{host}}" }, "connectors": { "lnd": "LND", From bb3906949633df52b6275baa42963627f0e61d2a Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 18 Sep 2023 14:52:51 +0530 Subject: [PATCH 16/18] feat: correct confirm or cancel button position --- src/app/screens/Enable/LiquidEnable.tsx | 4 ++-- src/app/screens/Enable/NostrEnable.tsx | 4 ++-- src/app/screens/Enable/WebbtcEnable.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 27f54eaba0..f7712f67be 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -32,12 +32,12 @@ export default function LiquidEnable(props: Props) { }, [props.origin, account]); return ( -
+ <> {hasMnemonic ? ( ) : ( )} -
+ ); } diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index 3cb043e8c3..3e720a239b 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -32,12 +32,12 @@ export default function NostrEnable(props: Props) { }, [props.origin, account]); return ( -
+ <> {hasNostrKeys ? ( ) : ( )} -
+ ); } diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index b3a6fa9e46..99fc52d15e 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -32,12 +32,12 @@ export default function WebbtcEnable(props: Props) { }, [props.origin, account]); return ( -
+ <> {hasMnemonic ? ( ) : ( )} -
+ ); } From 56b6278a12867814a386a274c29552ba5a9ccf0c Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 18 Sep 2023 16:28:46 +0530 Subject: [PATCH 17/18] feat: restructure translations --- src/app/components/Enable/AlbyEnable.tsx | 10 ++++------ src/app/components/Enable/LiquidEnable.tsx | 10 ++++------ src/app/components/Enable/NostrEnable.tsx | 6 +++--- src/app/components/Enable/WebbtcEnable.tsx | 10 ++++------ src/app/components/Enable/WeblnEnable.tsx | 10 ++++------ src/i18n/locales/en/translation.json | 12 +++++++----- 6 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/app/components/Enable/AlbyEnable.tsx b/src/app/components/Enable/AlbyEnable.tsx index 4af8083253..f0d77ad086 100644 --- a/src/app/components/Enable/AlbyEnable.tsx +++ b/src/app/components/Enable/AlbyEnable.tsx @@ -46,7 +46,7 @@ function AlbyEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(tCommon("block_added", { host: props.origin.host })); + alert(tCommon("enable.block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } @@ -63,13 +63,11 @@ function AlbyEnableComponent(props: Props) { />
-

{tCommon("allow")}

+

{tCommon("enable.allow")}

-

- {tCommon("request_transaction_approval")} -

+

{tCommon("enable.request1")}

@@ -90,7 +88,7 @@ function AlbyEnableComponent(props: Props) { href="#" onClick={block} > - {tCommon("actions.block_and_ignore", { host: props.origin.host })} + {tCommon("enable.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/LiquidEnable.tsx b/src/app/components/Enable/LiquidEnable.tsx index eb0248d5d8..3b34f56655 100644 --- a/src/app/components/Enable/LiquidEnable.tsx +++ b/src/app/components/Enable/LiquidEnable.tsx @@ -46,7 +46,7 @@ function LiquidEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(tCommon("block_added", { host: props.origin.host })); + alert(tCommon("enable.block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } @@ -63,13 +63,11 @@ function LiquidEnableComponent(props: Props) { />
-

{tCommon("allow")}

+

{tCommon("enable.allow")}

-

- {tCommon("request_transaction_approval")} -

+

{tCommon("enable.request1")}

@@ -90,7 +88,7 @@ function LiquidEnableComponent(props: Props) { href="#" onClick={block} > - {tCommon("actions.block_and_ignore", { host: props.origin.host })} + {tCommon("enable.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/NostrEnable.tsx b/src/app/components/Enable/NostrEnable.tsx index 72919c1329..8b7d0d0245 100644 --- a/src/app/components/Enable/NostrEnable.tsx +++ b/src/app/components/Enable/NostrEnable.tsx @@ -46,7 +46,7 @@ function NostrEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(tCommon("block_added", { host: props.origin.host })); + alert(tCommon("enable.block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } @@ -63,7 +63,7 @@ function NostrEnableComponent(props: Props) { />
-

{tCommon("allow")}

+

{tCommon("enable.allow")}

@@ -88,7 +88,7 @@ function NostrEnableComponent(props: Props) { href="#" onClick={block} > - {tCommon("actions.block_and_ignore", { host: props.origin.host })} + {tCommon("enable.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/WebbtcEnable.tsx b/src/app/components/Enable/WebbtcEnable.tsx index 19f471c569..61c8179b8d 100644 --- a/src/app/components/Enable/WebbtcEnable.tsx +++ b/src/app/components/Enable/WebbtcEnable.tsx @@ -46,7 +46,7 @@ function WebbtcEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(tCommon("block_added", { host: props.origin.host })); + alert(tCommon("enable.block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } @@ -63,13 +63,11 @@ function WebbtcEnableComponent(props: Props) { />
-

{tCommon("allow")}

+

{tCommon("enable.allow")}

-

- {tCommon("request_transaction_approval")} -

+

{tCommon("enable.request1")}

@@ -90,7 +88,7 @@ function WebbtcEnableComponent(props: Props) { href="#" onClick={block} > - {tCommon("actions.block_and_ignore", { host: props.origin.host })} + {tCommon("enable.block_and_ignore", { host: props.origin.host })}
diff --git a/src/app/components/Enable/WeblnEnable.tsx b/src/app/components/Enable/WeblnEnable.tsx index 1ada6d86b3..d857ebaaa4 100644 --- a/src/app/components/Enable/WeblnEnable.tsx +++ b/src/app/components/Enable/WeblnEnable.tsx @@ -46,7 +46,7 @@ function WeblnEnableComponent(props: Props) { domain: props.origin.domain, host: props.origin.host, }); - alert(tCommon("block_added", { host: props.origin.host })); + alert(tCommon("enable.block_added", { host: props.origin.host })); msg.error(USER_REJECTED_ERROR); } @@ -63,13 +63,11 @@ function WeblnEnableComponent(props: Props) { />
-

{tCommon("allow")}

+

{tCommon("enable.allow")}

-

- {tCommon("request_transaction_approval")} -

+

{tCommon("enable.request1")}

@@ -90,7 +88,7 @@ function WeblnEnableComponent(props: Props) { href="#" onClick={block} > - {tCommon("actions.block_and_ignore", { host: props.origin.host })} + {tCommon("enable.block_and_ignore", { host: props.origin.host })}
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index a72bb5a7d8..67c55260ce 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -971,7 +971,6 @@ } }, "common": { - "allow": "Allow this website to:", "password": "Password", "confirm_password": "Confirm Password", "advanced": "Advanced", @@ -998,8 +997,6 @@ "help": "Help", "balance": "Balance", "or": "or", - "block_added": "Added {{host}} to the blocklist, please reload the website.", - "request_transaction_approval": "Request approval for transactions", "actions": { "back": "Back", "delete": "Delete", @@ -1032,8 +1029,7 @@ "more": "More", "disconnect": "Disconnect", "review": "Review", - "download": "Download", - "block_and_ignore": "Block and ignore {{host}}" + "download": "Download" }, "connectors": { "lnd": "LND", @@ -1056,6 +1052,12 @@ "between": "between {{min}} and {{max}}", "lessThanOrEqual": "≤ {{max}}", "greaterOrEqual": "≥ {{min}}" + }, + "enable": { + "allow": "Allow this website to:", + "block_added": "Added {{host}} to the blocklist, please reload the website.", + "request1": "Request approval for transactions", + "block_and_ignore": "Block and ignore {{host}}" } }, "components": { From ad07912fc9d6f5ae5a4c85735e56fd110f26559e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 18 Sep 2023 20:09:08 +0700 Subject: [PATCH 18/18] chore: make nostr enable text fit on one line --- src/i18n/locales/en/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 67c55260ce..44750cf601 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -539,8 +539,8 @@ }, "nostr_enable": { "title": "Connect to Nostr", - "request1": "Request approval to read your Nostr public key", - "request2": "sign events using your Nostr private key" + "request1": "Request to read your Nostr public key", + "request2": "Sign events using your Nostr private key" }, "liquid_enable": { "title": "Connect to Liquid",