mirror of
https://github.com/lumehq/lume.git
synced 2025-03-17 21:32:32 +01:00
feat: add zap for multi accounts
This commit is contained in:
parent
9c61d6cad2
commit
64b78fff7f
@ -178,13 +178,13 @@ pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result<bool, Str
|
||||
|
||||
match client.signer().await {
|
||||
Ok(signer) => {
|
||||
let signer_key = signer.public_key().await.unwrap();
|
||||
// Emit reload in front-end
|
||||
// handle.emit("signer", ()).unwrap();
|
||||
|
||||
if signer_key == public_key {
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
let signer_key = signer.public_key().await.map_err(|e| e.to_string())?;
|
||||
let is_match = signer_key == public_key;
|
||||
|
||||
Ok(is_match)
|
||||
}
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
|
@ -449,7 +449,9 @@ pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, Stri
|
||||
|
||||
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
||||
let nwc = NWC::new(nwc_uri);
|
||||
let keyring = Entry::new("Lume Secret", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||
let keyring =
|
||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||
|
||||
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
||||
client.set_zapper(nwc).await;
|
||||
|
||||
@ -461,29 +463,25 @@ pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, Stri
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn load_wallet(state: State<'_, Nostr>) -> Result<String, String> {
|
||||
pub async fn load_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
||||
let client = &state.client;
|
||||
let keyring =
|
||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||
|
||||
match keyring.get_password() {
|
||||
Ok(val) => {
|
||||
let uri = NostrWalletConnectURI::from_str(&val).unwrap();
|
||||
let nwc = NWC::new(uri);
|
||||
if client.zapper().await.is_err() {
|
||||
let keyring =
|
||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||
|
||||
// Get current balance
|
||||
let balance = nwc.get_balance().await;
|
||||
match keyring.get_password() {
|
||||
Ok(val) => {
|
||||
let uri = NostrWalletConnectURI::from_str(&val).unwrap();
|
||||
let nwc = NWC::new(uri);
|
||||
|
||||
// Update zapper
|
||||
client.set_zapper(nwc).await;
|
||||
|
||||
match balance {
|
||||
Ok(val) => Ok(val.to_string()),
|
||||
Err(_) => Err("Get balance failed.".into()),
|
||||
client.set_zapper(nwc).await;
|
||||
}
|
||||
Err(_) => return Err("Wallet not found.".into()),
|
||||
}
|
||||
Err(_) => Err("NWC not found.".into()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -505,52 +503,40 @@ pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn zap_profile(
|
||||
id: &str,
|
||||
amount: &str,
|
||||
message: &str,
|
||||
id: String,
|
||||
amount: String,
|
||||
message: Option<String>,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<bool, String> {
|
||||
) -> Result<(), String> {
|
||||
let client = &state.client;
|
||||
|
||||
let public_key: PublicKey = PublicKey::parse(id).map_err(|e| e.to_string())?;
|
||||
|
||||
let details = ZapDetails::new(ZapType::Private).message(message);
|
||||
let num = amount.parse::<u64>().map_err(|e| e.to_string())?;
|
||||
let details = message.map(|m| ZapDetails::new(ZapType::Public).message(m));
|
||||
|
||||
if client.zap(public_key, num, Some(details)).await.is_ok() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err("Zap profile failed".into())
|
||||
match client.zap(public_key, num, details).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn zap_event(
|
||||
id: &str,
|
||||
amount: &str,
|
||||
message: &str,
|
||||
id: String,
|
||||
amount: String,
|
||||
message: Option<String>,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<bool, String> {
|
||||
) -> Result<(), String> {
|
||||
let client = &state.client;
|
||||
let event_id = match Nip19::from_bech32(id) {
|
||||
Ok(val) => match val {
|
||||
Nip19::EventId(id) => id,
|
||||
Nip19::Event(event) => event.event_id,
|
||||
_ => return Err("Event ID is invalid.".into()),
|
||||
},
|
||||
Err(_) => match EventId::from_hex(id) {
|
||||
Ok(val) => val,
|
||||
Err(_) => return Err("Event ID is invalid.".into()),
|
||||
},
|
||||
};
|
||||
|
||||
let details = ZapDetails::new(ZapType::Private).message(message);
|
||||
let event_id = EventId::from_str(&id).map_err(|e| e.to_string())?;
|
||||
let num = amount.parse::<u64>().map_err(|e| e.to_string())?;
|
||||
let details = message.map(|m| ZapDetails::new(ZapType::Public).message(m));
|
||||
|
||||
if client.zap(event_id, num, Some(details)).await.is_ok() {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err("Zap event failed".into())
|
||||
match client.zap(event_id, num, details).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,8 +90,7 @@ struct Subscription {
|
||||
struct NewSettings(Settings);
|
||||
|
||||
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
||||
pub const FETCH_LIMIT: usize = 100;
|
||||
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
||||
pub const FETCH_LIMIT: usize = 50;
|
||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||
|
||||
fn main() {
|
||||
@ -232,10 +231,10 @@ fn main() {
|
||||
// Config
|
||||
let opts = Options::new()
|
||||
.gossip(true)
|
||||
.max_avg_latency(Duration::from_millis(500))
|
||||
.max_avg_latency(Duration::from_millis(800))
|
||||
.automatic_authentication(false)
|
||||
.connection_timeout(Some(Duration::from_secs(20)))
|
||||
.send_timeout(Some(Duration::from_secs(10)))
|
||||
.send_timeout(Some(Duration::from_secs(20)))
|
||||
.timeout(Duration::from_secs(20));
|
||||
|
||||
// Setup nostr client
|
||||
@ -479,6 +478,17 @@ fn main() {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-nostr/nostr/issues/509
|
||||
// TODO: remove
|
||||
let _ = client
|
||||
.fetch_events(
|
||||
vec![Filter::new()
|
||||
.kind(Kind::TextNote)
|
||||
.limit(0)],
|
||||
Some(Duration::from_secs(5)),
|
||||
)
|
||||
.await;
|
||||
|
||||
if allow_notification {
|
||||
if let Err(e) = &handle_clone
|
||||
.notification()
|
||||
|
@ -224,7 +224,7 @@ async setWallet(uri: string) : Promise<Result<boolean, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async loadWallet() : Promise<Result<string, string>> {
|
||||
async loadWallet() : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("load_wallet") };
|
||||
} catch (e) {
|
||||
@ -240,7 +240,7 @@ async removeWallet() : Promise<Result<null, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async zapProfile(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
|
||||
async zapProfile(id: string, amount: string, message: string | null) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
|
||||
} catch (e) {
|
||||
@ -248,7 +248,7 @@ async zapProfile(id: string, amount: string, message: string) : Promise<Result<b
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async zapEvent(id: string, amount: string, message: string) : Promise<Result<boolean, string>> {
|
||||
async zapEvent(id: string, amount: string, message: string | null) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
|
||||
} catch (e) {
|
||||
|
@ -25,7 +25,6 @@ import { Route as SettingsIdWalletImport } from './routes/settings.$id/wallet'
|
||||
import { Route as SettingsIdRelayImport } from './routes/settings.$id/relay'
|
||||
import { Route as SettingsIdProfileImport } from './routes/settings.$id/profile'
|
||||
import { Route as SettingsIdGeneralImport } from './routes/settings.$id/general'
|
||||
import { Route as SettingsIdBitcoinConnectImport } from './routes/settings.$id/bitcoin-connect'
|
||||
import { Route as ColumnsLayoutGlobalImport } from './routes/columns/_layout/global'
|
||||
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
||||
import { Route as ColumnsLayoutStoriesIdImport } from './routes/columns/_layout/stories.$id'
|
||||
@ -215,13 +214,6 @@ const SettingsIdGeneralRoute = SettingsIdGeneralImport.update({
|
||||
import('./routes/settings.$id/general.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const SettingsIdBitcoinConnectRoute = SettingsIdBitcoinConnectImport.update({
|
||||
path: '/bitcoin-connect',
|
||||
getParentRoute: () => SettingsIdLazyRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/settings.$id/bitcoin-connect.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||
path: '/global',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
@ -436,13 +428,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/settings/$id/bitcoin-connect': {
|
||||
id: '/settings/$id/bitcoin-connect'
|
||||
path: '/bitcoin-connect'
|
||||
fullPath: '/settings/$id/bitcoin-connect'
|
||||
preLoaderRoute: typeof SettingsIdBitcoinConnectImport
|
||||
parentRoute: typeof SettingsIdLazyImport
|
||||
}
|
||||
'/settings/$id/general': {
|
||||
id: '/settings/$id/general'
|
||||
path: '/general'
|
||||
@ -653,7 +638,6 @@ const ColumnsRouteWithChildren =
|
||||
ColumnsRoute._addFileChildren(ColumnsRouteChildren)
|
||||
|
||||
interface SettingsIdLazyRouteChildren {
|
||||
SettingsIdBitcoinConnectRoute: typeof SettingsIdBitcoinConnectRoute
|
||||
SettingsIdGeneralRoute: typeof SettingsIdGeneralRoute
|
||||
SettingsIdProfileRoute: typeof SettingsIdProfileRoute
|
||||
SettingsIdRelayRoute: typeof SettingsIdRelayRoute
|
||||
@ -661,7 +645,6 @@ interface SettingsIdLazyRouteChildren {
|
||||
}
|
||||
|
||||
const SettingsIdLazyRouteChildren: SettingsIdLazyRouteChildren = {
|
||||
SettingsIdBitcoinConnectRoute: SettingsIdBitcoinConnectRoute,
|
||||
SettingsIdGeneralRoute: SettingsIdGeneralRoute,
|
||||
SettingsIdProfileRoute: SettingsIdProfileRoute,
|
||||
SettingsIdRelayRoute: SettingsIdRelayRoute,
|
||||
@ -690,7 +673,6 @@ export interface FileRoutesByFullPath {
|
||||
'/new-post': typeof NewPostIndexRoute
|
||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
@ -728,7 +710,6 @@ export interface FileRoutesByTo {
|
||||
'/new-post': typeof NewPostIndexRoute
|
||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
@ -769,7 +750,6 @@ export interface FileRoutesById {
|
||||
'/new-post/': typeof NewPostIndexRoute
|
||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
@ -810,7 +790,6 @@ export interface FileRouteTypes {
|
||||
| '/new-post'
|
||||
| '/columns/create-newsfeed'
|
||||
| '/columns/global'
|
||||
| '/settings/$id/bitcoin-connect'
|
||||
| '/settings/$id/general'
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
@ -847,7 +826,6 @@ export interface FileRouteTypes {
|
||||
| '/new-post'
|
||||
| '/columns/create-newsfeed'
|
||||
| '/columns/global'
|
||||
| '/settings/$id/bitcoin-connect'
|
||||
| '/settings/$id/general'
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
@ -886,7 +864,6 @@ export interface FileRouteTypes {
|
||||
| '/new-post/'
|
||||
| '/columns/_layout/create-newsfeed'
|
||||
| '/columns/_layout/global'
|
||||
| '/settings/$id/bitcoin-connect'
|
||||
| '/settings/$id/general'
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
@ -1035,7 +1012,6 @@ export const routeTree = rootRoute
|
||||
"/settings/$id": {
|
||||
"filePath": "settings.$id.lazy.tsx",
|
||||
"children": [
|
||||
"/settings/$id/bitcoin-connect",
|
||||
"/settings/$id/general",
|
||||
"/settings/$id/profile",
|
||||
"/settings/$id/relay",
|
||||
@ -1061,10 +1037,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "columns/_layout/global.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/settings/$id/bitcoin-connect": {
|
||||
"filePath": "settings.$id/bitcoin-connect.tsx",
|
||||
"parent": "/settings/$id"
|
||||
},
|
||||
"/settings/$id/general": {
|
||||
"filePath": "settings.$id/general.tsx",
|
||||
"parent": "/settings/$id"
|
||||
|
@ -4,10 +4,10 @@ import { PublishIcon } from "@/components";
|
||||
import { User } from "@/components/user";
|
||||
import { LumeWindow } from "@/system";
|
||||
import { MagnifyingGlass, Plus } from "@phosphor-icons/react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
import { memo, useCallback, useEffect, useState } from "react";
|
||||
|
||||
@ -115,11 +115,22 @@ const NegentropyBadge = memo(function NegentropyBadge() {
|
||||
);
|
||||
});
|
||||
|
||||
const Account = memo(function Account({ pubkey }: { pubkey: string }) {
|
||||
function Account({ pubkey }: { pubkey: string }) {
|
||||
const navigate = Route.useNavigate();
|
||||
const context = Route.useRouteContext();
|
||||
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const { data: isActive } = useQuery({
|
||||
queryKey: ["signer", pubkey],
|
||||
queryFn: async () => {
|
||||
const res = await commands.hasSigner(pubkey);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const showContextMenu = useCallback(
|
||||
async (e: React.MouseEvent) => {
|
||||
@ -165,26 +176,6 @@ const Account = memo(function Account({ pubkey }: { pubkey: string }) {
|
||||
[pubkey],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await commands.hasSigner(pubkey);
|
||||
|
||||
if (res.status === "ok" && res.data) {
|
||||
setIsActive(true);
|
||||
}
|
||||
})();
|
||||
}, [pubkey]);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = getCurrentWindow().listen("signer-updated", async () => {
|
||||
setIsActive((prev) => !prev);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@ -197,8 +188,8 @@ const Account = memo(function Account({ pubkey }: { pubkey: string }) {
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
{isActive ? (
|
||||
<div className="h-px w-full absolute bottom-0 left-0 bg-blue-500 rounded-full" />
|
||||
<div className="h-px w-full absolute bottom-0 left-0 bg-green-500 rounded-full" />
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -17,10 +17,13 @@ function Screen() {
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={cn(
|
||||
"w-[250px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
|
||||
"w-[200px] shrink-0 flex flex-col gap-1 border-r border-black/10 dark:border-white/10 p-2",
|
||||
platform === "macos" ? "pt-11" : "",
|
||||
)}
|
||||
>
|
||||
<div className="h-8 px-1.5">
|
||||
<h1 className="text-lg font-semibold">Settings</h1>
|
||||
</div>
|
||||
<Link to="/settings/$id/general" params={{ id }}>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
|
@ -1,37 +0,0 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { Button } from "@getalby/bitcoin-connect-react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
export const Route = createLazyFileRoute("/settings/$id/bitcoin-connect")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const setNwcUri = async (uri: string) => {
|
||||
const res = await commands.setWallet(uri);
|
||||
|
||||
if (res.status === "ok") {
|
||||
await getCurrentWebviewWindow().close();
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center size-full">
|
||||
<div className="flex flex-col items-center justify-center gap-3 text-center">
|
||||
<div>
|
||||
<p className="text-sm text-black/70 dark:text-white/70">
|
||||
Click to the button below to connect with your Bitcoin wallet.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onConnected={(provider) =>
|
||||
setNwcUri(provider.client.nostrWalletConnectUrl)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { init } from '@getalby/bitcoin-connect-react'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/settings/$id/bitcoin-connect')({
|
||||
beforeLoad: () => {
|
||||
init({
|
||||
appName: 'Lume',
|
||||
filters: ['nwc'],
|
||||
showBalance: true,
|
||||
})
|
||||
},
|
||||
})
|
@ -2,8 +2,8 @@ import { commands } from "@/commands.gen";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/settings/$id/relay")({
|
||||
beforeLoad: async () => {
|
||||
const res = await commands.getRelays();
|
||||
beforeLoad: async ({ params }) => {
|
||||
const res = await commands.getRelays(params.id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return { relayList: res.data };
|
||||
|
@ -1,20 +1,30 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { createLazyFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { Button } from "@getalby/bitcoin-connect-react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/settings/$id/wallet")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { id } = Route.useParams();
|
||||
const { balance } = Route.useRouteContext();
|
||||
const [_isConnect, setIsConnect] = useState(false);
|
||||
|
||||
const disconnect = async () => {
|
||||
const setWallet = async (uri: string) => {
|
||||
const res = await commands.setWallet(uri);
|
||||
|
||||
if (res.status === "ok") {
|
||||
setIsConnect((prev) => !prev);
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
};
|
||||
|
||||
const removeWallet = async () => {
|
||||
const res = await commands.removeWallet();
|
||||
|
||||
if (res.status === "ok") {
|
||||
window.localStorage.removeItem("bc:config");
|
||||
return redirect({ to: "/settings/$id/bitcoin-connect", params: { id } });
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
@ -22,32 +32,17 @@ function Screen() {
|
||||
|
||||
return (
|
||||
<div className="w-full px-3 pb-3">
|
||||
<div className="flex flex-col w-full gap-3">
|
||||
<div className="flex flex-col w-full px-3 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||
<div className="flex items-center justify-between w-full gap-4 py-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium">Connection</h3>
|
||||
</div>
|
||||
<div className="flex justify-end w-36 shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => disconnect()}
|
||||
className="h-8 w-max px-2.5 text-sm rounded-lg inline-flex items-center justify-center bg-black/10 dark:bg-white/10 hover:bg-black/20 dark:hover:bg-white/20"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full px-3 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||
<div className="flex items-center justify-between w-full gap-4 py-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium">Current Balance</h3>
|
||||
</div>
|
||||
<div className="flex justify-end w-36 shrink-0">
|
||||
₿ {balance.bitcoinFormatted}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full gap-2">
|
||||
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
Wallet
|
||||
</h2>
|
||||
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
||||
<Button
|
||||
onConnected={(provider) =>
|
||||
setWallet(provider.client.nostrWalletConnectUrl)
|
||||
}
|
||||
onDisconnected={() => removeWallet()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,21 +1,12 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { getBitcoinDisplayValues } from "@/commons";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { init } from "@getalby/bitcoin-connect-react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/settings/$id/wallet")({
|
||||
beforeLoad: async ({ params }) => {
|
||||
const query = await commands.loadWallet();
|
||||
|
||||
if (query.status === "ok") {
|
||||
const wallet = Number.parseInt(query.data);
|
||||
const balance = getBitcoinDisplayValues(wallet);
|
||||
|
||||
return { balance };
|
||||
} else {
|
||||
throw redirect({
|
||||
to: "/settings/$id/bitcoin-connect",
|
||||
params: { id: params.id },
|
||||
});
|
||||
}
|
||||
beforeLoad: async () => {
|
||||
init({
|
||||
appName: "Lume",
|
||||
filters: ["nwc"],
|
||||
showBalance: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,8 +1,14 @@
|
||||
import { User } from "@/components/user";
|
||||
import { commands } from "@/commands.gen";
|
||||
import { displayNpub } from "@/commons";
|
||||
import { User } from "@/components";
|
||||
import { LumeWindow } from "@/system";
|
||||
import type { Metadata } from "@/types";
|
||||
import { CaretDown } from "@phosphor-icons/react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
||||
import { type Window, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useState, useTransition } from "react";
|
||||
import { useCallback, useEffect, useState, useTransition } from "react";
|
||||
import CurrencyInput from "react-currency-input-field";
|
||||
|
||||
const DEFAULT_VALUES = [21, 50, 100, 200];
|
||||
@ -12,38 +18,102 @@ export const Route = createLazyFileRoute("/zap/$id")({
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { event } = Route.useRouteContext();
|
||||
const { accounts, event } = Route.useRouteContext();
|
||||
|
||||
const [currentUser, setCurrentUser] = useState<string>(null);
|
||||
const [popup, setPopup] = useState<Window>(null);
|
||||
const [amount, setAmount] = useState(21);
|
||||
const [content, setContent] = useState("");
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const submit = () => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const val = await event.zap(amount, content);
|
||||
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (val) {
|
||||
setIsCompleted(true);
|
||||
// close current window
|
||||
await getCurrentWebviewWindow().close();
|
||||
}
|
||||
} catch (e) {
|
||||
await message(String(e), {
|
||||
title: "Zap",
|
||||
kind: "error",
|
||||
});
|
||||
const list = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
const profile: Metadata = JSON.parse(res.data);
|
||||
name = profile.display_name ?? profile.name;
|
||||
}
|
||||
|
||||
list.push(
|
||||
MenuItem.new({
|
||||
text: `Zap as ${name} (${displayNpub(account, 16)})`,
|
||||
action: async () => setCurrentUser(account),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const items = await Promise.all(list);
|
||||
const menu = await Menu.new({ items });
|
||||
|
||||
await menu.popup().catch((e) => console.error(e));
|
||||
}, []);
|
||||
|
||||
const zap = () => {
|
||||
startTransition(async () => {
|
||||
const res = await commands.zapEvent(event.id, amount.toString(), content);
|
||||
|
||||
if (res.status === "ok") {
|
||||
setIsCompleted(true);
|
||||
// close current window
|
||||
await getCurrentWindow().close();
|
||||
} else {
|
||||
await message(res.error, { kind: "error" });
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (currentUser) {
|
||||
const signer = await commands.hasSigner(currentUser);
|
||||
|
||||
if (signer.status === "ok") {
|
||||
if (!signer.data) {
|
||||
const newPopup = await LumeWindow.openPopup(
|
||||
`/set-signer/${currentUser}`,
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
|
||||
setPopup(newPopup);
|
||||
return;
|
||||
}
|
||||
|
||||
zap();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!popup) return;
|
||||
|
||||
const unlisten = popup.listen("signer-updated", () => {
|
||||
zap();
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.then((f) => f());
|
||||
};
|
||||
}, [popup]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accounts?.length) {
|
||||
setCurrentUser(accounts[0]);
|
||||
}
|
||||
}, [accounts]);
|
||||
|
||||
return (
|
||||
<div data-tauri-drag-region className="flex flex-col pb-5 size-full">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex items-center justify-center h-24 gap-2 shrink-0"
|
||||
className="flex items-center justify-center h-32 gap-2 shrink-0"
|
||||
>
|
||||
<p className="text-sm">Send zap to </p>
|
||||
<User.Provider pubkey={event.pubkey}>
|
||||
@ -95,15 +165,34 @@ function Screen() {
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
placeholder="Enter message (optional)"
|
||||
className="h-11 w-full resize-none rounded-xl border-transparent bg-black/5 px-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:bg-white/5"
|
||||
className="h-10 w-full resize-none rounded-lg border-transparent bg-black/5 px-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:bg-white/5"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex items-center justify-center w-full h-10 font-medium rounded-xl bg-neutral-950 text-neutral-50 hover:bg-neutral-900 dark:bg-white/20 dark:hover:bg-white/30"
|
||||
>
|
||||
{isCompleted ? "Zapped" : isPending ? "Processing..." : "Zap"}
|
||||
</button>
|
||||
<div className="inline-flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
className="inline-flex items-center justify-center w-full h-9 text-sm font-semibold rounded-lg bg-blue-500 text-white hover:bg-blue-600 dark:hover:bg-blue-400"
|
||||
>
|
||||
{isCompleted ? "Zapped" : isPending ? "Processing..." : "Zap"}
|
||||
</button>
|
||||
{currentUser ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => showContextMenu(e)}
|
||||
className="inline-flex items-center gap-1.5"
|
||||
>
|
||||
<User.Provider pubkey={currentUser}>
|
||||
<User.Root>
|
||||
<User.Avatar className="size-6 rounded-full" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<CaretDown
|
||||
className="mt-px size-3 text-neutral-500"
|
||||
weight="bold"
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/zap/$id")({
|
||||
beforeLoad: async ({ params }) => {
|
||||
const accounts = await commands.getAccounts();
|
||||
const res = await commands.getEvent(params.id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
@ -15,7 +16,7 @@ export const Route = createFileRoute("/zap/$id")({
|
||||
raw.meta = data.parsed;
|
||||
}
|
||||
|
||||
return { event: new LumeEvent(raw) };
|
||||
return { accounts, event: new LumeEvent(raw) };
|
||||
} else {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ export const LumeWindow = {
|
||||
closable: true,
|
||||
});
|
||||
} else {
|
||||
await LumeWindow.openSettings(account, "bitcoin-connect");
|
||||
await LumeWindow.openSettings(account, "wallet");
|
||||
}
|
||||
},
|
||||
openSettings: async (account: string, path?: string) => {
|
||||
@ -155,7 +155,7 @@ export const LumeWindow = {
|
||||
? `/settings/${account}/${path}`
|
||||
: `/settings/${account}/general`,
|
||||
title: "Settings",
|
||||
width: 800,
|
||||
width: 700,
|
||||
height: 500,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user