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 {
|
match client.signer().await {
|
||||||
Ok(signer) => {
|
Ok(signer) => {
|
||||||
let signer_key = signer.public_key().await.unwrap();
|
// Emit reload in front-end
|
||||||
|
// handle.emit("signer", ()).unwrap();
|
||||||
|
|
||||||
if signer_key == public_key {
|
let signer_key = signer.public_key().await.map_err(|e| e.to_string())?;
|
||||||
Ok(true)
|
let is_match = signer_key == public_key;
|
||||||
} else {
|
|
||||||
Ok(false)
|
Ok(is_match)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(_) => Ok(false),
|
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) {
|
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
||||||
let nwc = NWC::new(nwc_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())?;
|
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
||||||
client.set_zapper(nwc).await;
|
client.set_zapper(nwc).await;
|
||||||
|
|
||||||
@ -461,29 +463,25 @@ pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, Stri
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[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 client = &state.client;
|
||||||
let keyring =
|
|
||||||
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
match keyring.get_password() {
|
if client.zapper().await.is_err() {
|
||||||
Ok(val) => {
|
let keyring =
|
||||||
let uri = NostrWalletConnectURI::from_str(&val).unwrap();
|
Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||||
let nwc = NWC::new(uri);
|
|
||||||
|
|
||||||
// Get current balance
|
match keyring.get_password() {
|
||||||
let balance = nwc.get_balance().await;
|
Ok(val) => {
|
||||||
|
let uri = NostrWalletConnectURI::from_str(&val).unwrap();
|
||||||
|
let nwc = NWC::new(uri);
|
||||||
|
|
||||||
// Update zapper
|
client.set_zapper(nwc).await;
|
||||||
client.set_zapper(nwc).await;
|
|
||||||
|
|
||||||
match balance {
|
|
||||||
Ok(val) => Ok(val.to_string()),
|
|
||||||
Err(_) => Err("Get balance failed.".into()),
|
|
||||||
}
|
}
|
||||||
|
Err(_) => return Err("Wallet not found.".into()),
|
||||||
}
|
}
|
||||||
Err(_) => Err("NWC not found.".into()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -505,52 +503,40 @@ pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn zap_profile(
|
pub async fn zap_profile(
|
||||||
id: &str,
|
id: String,
|
||||||
amount: &str,
|
amount: String,
|
||||||
message: &str,
|
message: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<(), String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
let public_key: PublicKey = PublicKey::parse(id).map_err(|e| e.to_string())?;
|
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 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() {
|
match client.zap(public_key, num, details).await {
|
||||||
Ok(true)
|
Ok(()) => Ok(()),
|
||||||
} else {
|
Err(e) => Err(e.to_string()),
|
||||||
Err("Zap profile failed".into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn zap_event(
|
pub async fn zap_event(
|
||||||
id: &str,
|
id: String,
|
||||||
amount: &str,
|
amount: String,
|
||||||
message: &str,
|
message: Option<String>,
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<(), String> {
|
||||||
let client = &state.client;
|
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 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() {
|
match client.zap(event_id, num, details).await {
|
||||||
Ok(true)
|
Ok(()) => Ok(()),
|
||||||
} else {
|
Err(e) => Err(e.to_string()),
|
||||||
Err("Zap event failed".into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,7 @@ struct Subscription {
|
|||||||
struct NewSettings(Settings);
|
struct NewSettings(Settings);
|
||||||
|
|
||||||
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
||||||
pub const FETCH_LIMIT: usize = 100;
|
pub const FETCH_LIMIT: usize = 50;
|
||||||
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
|
||||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -232,10 +231,10 @@ fn main() {
|
|||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
.max_avg_latency(Duration::from_millis(500))
|
.max_avg_latency(Duration::from_millis(800))
|
||||||
.automatic_authentication(false)
|
.automatic_authentication(false)
|
||||||
.connection_timeout(Some(Duration::from_secs(20)))
|
.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));
|
.timeout(Duration::from_secs(20));
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
@ -479,6 +478,17 @@ fn main() {
|
|||||||
println!("Error: {}", e);
|
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 allow_notification {
|
||||||
if let Err(e) = &handle_clone
|
if let Err(e) = &handle_clone
|
||||||
.notification()
|
.notification()
|
||||||
|
@ -224,7 +224,7 @@ async setWallet(uri: string) : Promise<Result<boolean, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadWallet() : Promise<Result<string, string>> {
|
async loadWallet() : Promise<Result<null, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("load_wallet") };
|
return { status: "ok", data: await TAURI_INVOKE("load_wallet") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -240,7 +240,7 @@ async removeWallet() : Promise<Result<null, string>> {
|
|||||||
else return { status: "error", error: e as any };
|
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 {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
|
return { status: "ok", data: await TAURI_INVOKE("zap_profile", { id, amount, message }) };
|
||||||
} catch (e) {
|
} 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 };
|
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 {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
|
return { status: "ok", data: await TAURI_INVOKE("zap_event", { id, amount, message }) };
|
||||||
} catch (e) {
|
} 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 SettingsIdRelayImport } from './routes/settings.$id/relay'
|
||||||
import { Route as SettingsIdProfileImport } from './routes/settings.$id/profile'
|
import { Route as SettingsIdProfileImport } from './routes/settings.$id/profile'
|
||||||
import { Route as SettingsIdGeneralImport } from './routes/settings.$id/general'
|
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 ColumnsLayoutGlobalImport } from './routes/columns/_layout/global'
|
||||||
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
import { Route as ColumnsLayoutCreateNewsfeedImport } from './routes/columns/_layout/create-newsfeed'
|
||||||
import { Route as ColumnsLayoutStoriesIdImport } from './routes/columns/_layout/stories.$id'
|
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),
|
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({
|
const ColumnsLayoutGlobalRoute = ColumnsLayoutGlobalImport.update({
|
||||||
path: '/global',
|
path: '/global',
|
||||||
getParentRoute: () => ColumnsLayoutRoute,
|
getParentRoute: () => ColumnsLayoutRoute,
|
||||||
@ -436,13 +428,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
preLoaderRoute: typeof ColumnsLayoutGlobalImport
|
||||||
parentRoute: typeof ColumnsLayoutImport
|
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': {
|
'/settings/$id/general': {
|
||||||
id: '/settings/$id/general'
|
id: '/settings/$id/general'
|
||||||
path: '/general'
|
path: '/general'
|
||||||
@ -653,7 +638,6 @@ const ColumnsRouteWithChildren =
|
|||||||
ColumnsRoute._addFileChildren(ColumnsRouteChildren)
|
ColumnsRoute._addFileChildren(ColumnsRouteChildren)
|
||||||
|
|
||||||
interface SettingsIdLazyRouteChildren {
|
interface SettingsIdLazyRouteChildren {
|
||||||
SettingsIdBitcoinConnectRoute: typeof SettingsIdBitcoinConnectRoute
|
|
||||||
SettingsIdGeneralRoute: typeof SettingsIdGeneralRoute
|
SettingsIdGeneralRoute: typeof SettingsIdGeneralRoute
|
||||||
SettingsIdProfileRoute: typeof SettingsIdProfileRoute
|
SettingsIdProfileRoute: typeof SettingsIdProfileRoute
|
||||||
SettingsIdRelayRoute: typeof SettingsIdRelayRoute
|
SettingsIdRelayRoute: typeof SettingsIdRelayRoute
|
||||||
@ -661,7 +645,6 @@ interface SettingsIdLazyRouteChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SettingsIdLazyRouteChildren: SettingsIdLazyRouteChildren = {
|
const SettingsIdLazyRouteChildren: SettingsIdLazyRouteChildren = {
|
||||||
SettingsIdBitcoinConnectRoute: SettingsIdBitcoinConnectRoute,
|
|
||||||
SettingsIdGeneralRoute: SettingsIdGeneralRoute,
|
SettingsIdGeneralRoute: SettingsIdGeneralRoute,
|
||||||
SettingsIdProfileRoute: SettingsIdProfileRoute,
|
SettingsIdProfileRoute: SettingsIdProfileRoute,
|
||||||
SettingsIdRelayRoute: SettingsIdRelayRoute,
|
SettingsIdRelayRoute: SettingsIdRelayRoute,
|
||||||
@ -690,7 +673,6 @@ export interface FileRoutesByFullPath {
|
|||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
@ -728,7 +710,6 @@ export interface FileRoutesByTo {
|
|||||||
'/new-post': typeof NewPostIndexRoute
|
'/new-post': typeof NewPostIndexRoute
|
||||||
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
@ -769,7 +750,6 @@ export interface FileRoutesById {
|
|||||||
'/new-post/': typeof NewPostIndexRoute
|
'/new-post/': typeof NewPostIndexRoute
|
||||||
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
'/columns/_layout/create-newsfeed': typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||||
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
'/columns/_layout/global': typeof ColumnsLayoutGlobalRoute
|
||||||
'/settings/$id/bitcoin-connect': typeof SettingsIdBitcoinConnectRoute
|
|
||||||
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
'/settings/$id/general': typeof SettingsIdGeneralRoute
|
||||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||||
@ -810,7 +790,6 @@ export interface FileRouteTypes {
|
|||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/settings/$id/bitcoin-connect'
|
|
||||||
| '/settings/$id/general'
|
| '/settings/$id/general'
|
||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
@ -847,7 +826,6 @@ export interface FileRouteTypes {
|
|||||||
| '/new-post'
|
| '/new-post'
|
||||||
| '/columns/create-newsfeed'
|
| '/columns/create-newsfeed'
|
||||||
| '/columns/global'
|
| '/columns/global'
|
||||||
| '/settings/$id/bitcoin-connect'
|
|
||||||
| '/settings/$id/general'
|
| '/settings/$id/general'
|
||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
@ -886,7 +864,6 @@ export interface FileRouteTypes {
|
|||||||
| '/new-post/'
|
| '/new-post/'
|
||||||
| '/columns/_layout/create-newsfeed'
|
| '/columns/_layout/create-newsfeed'
|
||||||
| '/columns/_layout/global'
|
| '/columns/_layout/global'
|
||||||
| '/settings/$id/bitcoin-connect'
|
|
||||||
| '/settings/$id/general'
|
| '/settings/$id/general'
|
||||||
| '/settings/$id/profile'
|
| '/settings/$id/profile'
|
||||||
| '/settings/$id/relay'
|
| '/settings/$id/relay'
|
||||||
@ -1035,7 +1012,6 @@ export const routeTree = rootRoute
|
|||||||
"/settings/$id": {
|
"/settings/$id": {
|
||||||
"filePath": "settings.$id.lazy.tsx",
|
"filePath": "settings.$id.lazy.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/settings/$id/bitcoin-connect",
|
|
||||||
"/settings/$id/general",
|
"/settings/$id/general",
|
||||||
"/settings/$id/profile",
|
"/settings/$id/profile",
|
||||||
"/settings/$id/relay",
|
"/settings/$id/relay",
|
||||||
@ -1061,10 +1037,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "columns/_layout/global.tsx",
|
"filePath": "columns/_layout/global.tsx",
|
||||||
"parent": "/columns/_layout"
|
"parent": "/columns/_layout"
|
||||||
},
|
},
|
||||||
"/settings/$id/bitcoin-connect": {
|
|
||||||
"filePath": "settings.$id/bitcoin-connect.tsx",
|
|
||||||
"parent": "/settings/$id"
|
|
||||||
},
|
|
||||||
"/settings/$id/general": {
|
"/settings/$id/general": {
|
||||||
"filePath": "settings.$id/general.tsx",
|
"filePath": "settings.$id/general.tsx",
|
||||||
"parent": "/settings/$id"
|
"parent": "/settings/$id"
|
||||||
|
@ -4,10 +4,10 @@ import { PublishIcon } from "@/components";
|
|||||||
import { User } from "@/components/user";
|
import { User } from "@/components/user";
|
||||||
import { LumeWindow } from "@/system";
|
import { LumeWindow } from "@/system";
|
||||||
import { MagnifyingGlass, Plus } from "@phosphor-icons/react";
|
import { MagnifyingGlass, Plus } from "@phosphor-icons/react";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
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 { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { memo, useCallback, useEffect, useState } from "react";
|
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 navigate = Route.useNavigate();
|
||||||
const context = Route.useRouteContext();
|
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(
|
const showContextMenu = useCallback(
|
||||||
async (e: React.MouseEvent) => {
|
async (e: React.MouseEvent) => {
|
||||||
@ -165,26 +176,6 @@ const Account = memo(function Account({ pubkey }: { pubkey: string }) {
|
|||||||
[pubkey],
|
[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 (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -197,8 +188,8 @@ const Account = memo(function Account({ pubkey }: { pubkey: string }) {
|
|||||||
</User.Root>
|
</User.Root>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
{isActive ? (
|
{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}
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
@ -17,10 +17,13 @@ function Screen() {
|
|||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={cn(
|
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" : "",
|
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 }}>
|
<Link to="/settings/$id/general" params={{ id }}>
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
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";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/$id/relay")({
|
export const Route = createFileRoute("/settings/$id/relay")({
|
||||||
beforeLoad: async () => {
|
beforeLoad: async ({ params }) => {
|
||||||
const res = await commands.getRelays();
|
const res = await commands.getRelays(params.id);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
return { relayList: res.data };
|
return { relayList: res.data };
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
import { commands } from "@/commands.gen";
|
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")({
|
export const Route = createLazyFileRoute("/settings/$id/wallet")({
|
||||||
component: Screen,
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { id } = Route.useParams();
|
const [_isConnect, setIsConnect] = useState(false);
|
||||||
const { balance } = Route.useRouteContext();
|
|
||||||
|
|
||||||
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();
|
const res = await commands.removeWallet();
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
window.localStorage.removeItem("bc:config");
|
window.localStorage.removeItem("bc:config");
|
||||||
return redirect({ to: "/settings/$id/bitcoin-connect", params: { id } });
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
}
|
}
|
||||||
@ -22,32 +32,17 @@ function Screen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-3 pb-3">
|
<div className="w-full px-3 pb-3">
|
||||||
<div className="flex flex-col w-full gap-3">
|
<div className="flex flex-col w-full gap-2">
|
||||||
<div className="flex flex-col w-full px-3 bg-black/5 dark:bg-white/5 rounded-xl">
|
<h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||||
<div className="flex items-center justify-between w-full gap-4 py-3">
|
Wallet
|
||||||
<div className="flex-1">
|
</h2>
|
||||||
<h3 className="font-medium">Connection</h3>
|
<div className="w-full h-44 flex items-center justify-center bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
</div>
|
<Button
|
||||||
<div className="flex justify-end w-36 shrink-0">
|
onConnected={(provider) =>
|
||||||
<button
|
setWallet(provider.client.nostrWalletConnectUrl)
|
||||||
type="button"
|
}
|
||||||
onClick={() => disconnect()}
|
onDisconnected={() => removeWallet()}
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
import { commands } from "@/commands.gen";
|
import { init } from "@getalby/bitcoin-connect-react";
|
||||||
import { getBitcoinDisplayValues } from "@/commons";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/settings/$id/wallet")({
|
export const Route = createFileRoute("/settings/$id/wallet")({
|
||||||
beforeLoad: async ({ params }) => {
|
beforeLoad: async () => {
|
||||||
const query = await commands.loadWallet();
|
init({
|
||||||
|
appName: "Lume",
|
||||||
if (query.status === "ok") {
|
filters: ["nwc"],
|
||||||
const wallet = Number.parseInt(query.data);
|
showBalance: true,
|
||||||
const balance = getBitcoinDisplayValues(wallet);
|
});
|
||||||
|
|
||||||
return { balance };
|
|
||||||
} else {
|
|
||||||
throw redirect({
|
|
||||||
to: "/settings/$id/bitcoin-connect",
|
|
||||||
params: { id: params.id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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 { 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 { 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";
|
import CurrencyInput from "react-currency-input-field";
|
||||||
|
|
||||||
const DEFAULT_VALUES = [21, 50, 100, 200];
|
const DEFAULT_VALUES = [21, 50, 100, 200];
|
||||||
@ -12,38 +18,102 @@ export const Route = createLazyFileRoute("/zap/$id")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
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 [amount, setAmount] = useState(21);
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState<string>("");
|
||||||
const [isCompleted, setIsCompleted] = useState(false);
|
const [isCompleted, setIsCompleted] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const submit = () => {
|
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
|
||||||
startTransition(async () => {
|
e.preventDefault();
|
||||||
try {
|
|
||||||
const val = await event.zap(amount, content);
|
|
||||||
|
|
||||||
if (val) {
|
const list = [];
|
||||||
setIsCompleted(true);
|
|
||||||
// close current window
|
for (const account of accounts) {
|
||||||
await getCurrentWebviewWindow().close();
|
const res = await commands.getProfile(account);
|
||||||
}
|
let name = "unknown";
|
||||||
} catch (e) {
|
|
||||||
await message(String(e), {
|
if (res.status === "ok") {
|
||||||
title: "Zap",
|
const profile: Metadata = JSON.parse(res.data);
|
||||||
kind: "error",
|
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;
|
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 (
|
return (
|
||||||
<div data-tauri-drag-region className="flex flex-col pb-5 size-full">
|
<div data-tauri-drag-region className="flex flex-col pb-5 size-full">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
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>
|
<p className="text-sm">Send zap to </p>
|
||||||
<User.Provider pubkey={event.pubkey}>
|
<User.Provider pubkey={event.pubkey}>
|
||||||
@ -95,15 +165,34 @@ function Screen() {
|
|||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
placeholder="Enter message (optional)"
|
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
|
<div className="inline-flex items-center gap-3">
|
||||||
type="button"
|
<button
|
||||||
onClick={() => submit()}
|
type="button"
|
||||||
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"
|
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>
|
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
|||||||
|
|
||||||
export const Route = createFileRoute("/zap/$id")({
|
export const Route = createFileRoute("/zap/$id")({
|
||||||
beforeLoad: async ({ params }) => {
|
beforeLoad: async ({ params }) => {
|
||||||
|
const accounts = await commands.getAccounts();
|
||||||
const res = await commands.getEvent(params.id);
|
const res = await commands.getEvent(params.id);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
@ -15,7 +16,7 @@ export const Route = createFileRoute("/zap/$id")({
|
|||||||
raw.meta = data.parsed;
|
raw.meta = data.parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { event: new LumeEvent(raw) };
|
return { accounts, event: new LumeEvent(raw) };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.error);
|
throw new Error(res.error);
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ export const LumeWindow = {
|
|||||||
closable: true,
|
closable: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await LumeWindow.openSettings(account, "bitcoin-connect");
|
await LumeWindow.openSettings(account, "wallet");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openSettings: async (account: string, path?: string) => {
|
openSettings: async (account: string, path?: string) => {
|
||||||
@ -155,7 +155,7 @@ export const LumeWindow = {
|
|||||||
? `/settings/${account}/${path}`
|
? `/settings/${account}/${path}`
|
||||||
: `/settings/${account}/general`,
|
: `/settings/${account}/general`,
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
width: 800,
|
width: 700,
|
||||||
height: 500,
|
height: 500,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
minimizable: false,
|
minimizable: false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user