diff --git a/apps/desktop2/package.json b/apps/desktop2/package.json
index 763359f7..fb531acf 100644
--- a/apps/desktop2/package.json
+++ b/apps/desktop2/package.json
@@ -26,6 +26,7 @@
"i18next-resources-to-backend": "^1.2.0",
"nostr-tools": "^2.3.1",
"react": "^18.2.0",
+ "react-currency-input-field": "^3.8.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.0.5",
"slate": "^0.101.5",
diff --git a/apps/desktop2/src/components/balance.tsx b/apps/desktop2/src/components/balance.tsx
new file mode 100644
index 00000000..5be1f4ec
--- /dev/null
+++ b/apps/desktop2/src/components/balance.tsx
@@ -0,0 +1,51 @@
+import { useArk } from "@lume/ark";
+import { User } from "@lume/ui";
+import { getBitcoinDisplayValues } from "@lume/utils";
+import { useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+
+export function Balance({
+ recipient,
+ account,
+}: {
+ recipient: string;
+ account: string;
+}) {
+ const [t] = useTranslation();
+ const [balance, setBalance] = useState(0);
+
+ const ark = useArk();
+ const value = useMemo(() => getBitcoinDisplayValues(balance), [balance]);
+
+ useEffect(() => {
+ async function getBalance() {
+ const val = await ark.get_balance();
+ setBalance(val);
+ }
+
+ getBalance();
+ }, []);
+
+ return (
+
+
+
+
+ Your balance
+
+
+ ₿ {value.bitcoinFormatted}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop2/src/components/login.tsx b/apps/desktop2/src/components/login.tsx
index 0d8cf6f2..9d644512 100644
--- a/apps/desktop2/src/components/login.tsx
+++ b/apps/desktop2/src/components/login.tsx
@@ -11,19 +11,15 @@ export function LoginDialog() {
const [nsec, setNsec] = useState("");
const [passphase, setPassphase] = useState("");
- const [loading, setLoading] = useState(false);
const login = async () => {
try {
- setLoading(true);
-
const save = await ark.save_account(nsec, passphase);
if (save) {
navigate({ to: "/", search: { guest: false } });
}
} catch (e) {
- setLoading(false);
toast.error(String(e));
}
};
diff --git a/apps/desktop2/src/routes/nwc.lazy.tsx b/apps/desktop2/src/routes/nwc.lazy.tsx
new file mode 100644
index 00000000..97c952ab
--- /dev/null
+++ b/apps/desktop2/src/routes/nwc.lazy.tsx
@@ -0,0 +1,68 @@
+import { useArk } from "@lume/ark";
+import { ArrowRightIcon, ZapIcon } from "@lume/icons";
+import { Container } from "@lume/ui";
+import { createLazyFileRoute } from "@tanstack/react-router";
+import { useState } from "react";
+
+export const Route = createLazyFileRoute("/nwc")({
+ component: Screen,
+});
+
+function Screen() {
+ const ark = useArk();
+
+ const [uri, setUri] = useState("");
+ const [isDone, setIsDone] = useState(false);
+
+ const save = async () => {
+ const nwc = await ark.set_nwc(uri);
+
+ if (nwc) {
+ setIsDone(true);
+ }
+ };
+
+ return (
+
+
+ {!isDone ? (
+ <>
+
+
+
+
+
+
+ Connect bitcoin wallet{" "}
+ to start zapping to your favorite content and creator.
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+
Done
+ )}
+
+
+ );
+}
diff --git a/apps/desktop2/src/routes/zap.$id.lazy.tsx b/apps/desktop2/src/routes/zap.$id.lazy.tsx
new file mode 100644
index 00000000..b84a4c72
--- /dev/null
+++ b/apps/desktop2/src/routes/zap.$id.lazy.tsx
@@ -0,0 +1,124 @@
+import { Balance } from "@/components/balance";
+import { useArk } from "@lume/ark";
+import { Box, Container, User } from "@lume/ui";
+import { createLazyFileRoute } from "@tanstack/react-router";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { getCurrent } from "@tauri-apps/api/webviewWindow";
+import { toast } from "sonner";
+import CurrencyInput from "react-currency-input-field";
+
+const DEFAULT_VALUES = [69, 100, 200, 500];
+
+export const Route = createLazyFileRoute("/zap/$id")({
+ component: Screen,
+});
+
+function Screen() {
+ const { t } = useTranslation();
+ const { id } = Route.useParams();
+ // @ts-ignore, magic !!!
+ const { pubkey, account } = Route.useSearch();
+
+ const [amount, setAmount] = useState(21);
+ const [message, setMessage] = useState("");
+ const [isCompleted, setIsCompleted] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const ark = useArk();
+
+ const submit = async () => {
+ try {
+ // start loading
+ setIsLoading(true);
+
+ const val = await ark.zap_event(id, amount, message);
+
+ if (val) {
+ setIsCompleted(true);
+ const window = getCurrent();
+ // close current window
+ window.close();
+ }
+ } catch (e) {
+ setIsLoading(false);
+ toast.error(e);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {t("note.zap.modalTitle")}{" "}
+
+
+
+
+
+
+
+
+
+
+ setAmount(Number(value))}
+ className="w-full flex-1 border-none bg-transparent text-right text-4xl font-semibold placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
+ />
+
+ sats
+
+
+
+ {DEFAULT_VALUES.map((value) => (
+
+ ))}
+
+
+
+
setMessage(e.target.value)}
+ spellCheck={false}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ placeholder={t("note.zap.messagePlaceholder")}
+ className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:text-neutral-400"
+ />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts
index bc79b58e..f41b01c3 100644
--- a/packages/ark/src/ark.ts
+++ b/packages/ark/src/ark.ts
@@ -404,8 +404,26 @@ export class Ark {
try {
const cmd: boolean = await invoke("set_nwc", { uri });
return cmd;
- } catch {
- return false;
+ } catch (e) {
+ throw new Error(String(e));
+ }
+ }
+
+ public async load_nwc() {
+ try {
+ const cmd: boolean = await invoke("load_nwc");
+ return cmd;
+ } catch (e) {
+ throw new Error(String(e));
+ }
+ }
+
+ public async get_balance() {
+ try {
+ const cmd: number = await invoke("get_balance");
+ return cmd;
+ } catch (e) {
+ throw new Error(String(e));
}
}
@@ -413,8 +431,8 @@ export class Ark {
try {
const cmd: boolean = await invoke("zap_profile", { id, amount, message });
return cmd;
- } catch {
- return false;
+ } catch (e) {
+ throw new Error(String(e));
}
}
@@ -422,8 +440,8 @@ export class Ark {
try {
const cmd: boolean = await invoke("zap_event", { id, amount, message });
return cmd;
- } catch {
- return false;
+ } catch (e) {
+ throw new Error(String(e));
}
}
@@ -477,8 +495,7 @@ export class Ark {
return content.url as string;
} catch (e) {
- console.error(String(e));
- return null;
+ throw new Error(String(e));
}
}
@@ -526,4 +543,30 @@ export class Ark {
fileDropEnabled: true,
});
}
+
+ public open_nwc() {
+ return new WebviewWindow("nwc", {
+ title: "Nostr Wallet Connect",
+ url: "/nwc",
+ minWidth: 400,
+ width: 400,
+ height: 600,
+ hiddenTitle: true,
+ titleBarStyle: "overlay",
+ fileDropEnabled: true,
+ });
+ }
+
+ public open_zap(id: string, pubkey: string, account: string) {
+ return new WebviewWindow(`zap-${id}`, {
+ title: "Nostr Wallet Connect",
+ url: `/zap/${id}?pubkey=${pubkey}&account=${account}`,
+ minWidth: 400,
+ width: 400,
+ height: 500,
+ hiddenTitle: true,
+ titleBarStyle: "overlay",
+ fileDropEnabled: true,
+ });
+ }
}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 8cf1c4b6..8a61e059 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -33,7 +33,6 @@
"react-hook-form": "^7.51.0",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.0.5",
- "react-router-dom": "^6.22.2",
"react-string-replace": "^1.1.1",
"slate": "^0.101.5",
"slate-react": "^0.101.6",
diff --git a/packages/ui/src/column/content.tsx b/packages/ui/src/column/content.tsx
deleted file mode 100644
index 09d2060e..00000000
--- a/packages/ui/src/column/content.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { ReactNode } from "react";
-import { Routes } from "react-router-dom";
-
-export function ColumnContent({ children }: { children: ReactNode }) {
- return {children};
-}
diff --git a/packages/ui/src/column/header.tsx b/packages/ui/src/column/header.tsx
deleted file mode 100644
index 9e9c8f7c..00000000
--- a/packages/ui/src/column/header.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import {
- ChevronDownIcon,
- MoveLeftIcon,
- MoveRightIcon,
- RefreshIcon,
- TrashIcon,
-} from "@lume/icons";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
-import { useQueryClient } from "@tanstack/react-query";
-import { useTranslation } from "react-i18next";
-
-export function ColumnHeader({ queryKey }: { queryKey?: string[] }) {
- const { t } = useTranslation();
- const queryClient = useQueryClient();
-
- const refresh = async () => {
- if (queryKey) await queryClient.refetchQueries({ queryKey });
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/packages/ui/src/column/index.ts b/packages/ui/src/column/index.ts
deleted file mode 100644
index 6b6813b8..00000000
--- a/packages/ui/src/column/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Route } from "react-router-dom";
-import { ColumnContent } from "./content";
-import { ColumnHeader } from "./header";
-import { ColumnLiveWidget } from "./live";
-import { ColumnProvider } from "./provider";
-import { ColumnRoot } from "./root";
-
-export const Column = {
- Provider: ColumnProvider,
- Root: ColumnRoot,
- Live: ColumnLiveWidget,
- Header: ColumnHeader,
- Content: ColumnContent,
- Route: Route,
-};
diff --git a/packages/ui/src/column/live.tsx b/packages/ui/src/column/live.tsx
deleted file mode 100644
index 09118ebe..00000000
--- a/packages/ui/src/column/live.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { ArrowUpIcon } from "@lume/icons";
-import { useEffect, useState } from "react";
-
-export function ColumnLiveWidget({
- filter,
- onClick,
-}: {
- filter: NDKFilter;
- onClick: (event: NDKEvent[]) => void;
-}) {
- const ark = useArk();
- const [events, setEvents] = useState([]);
-
- const update = async () => {
- onClick(events);
- // reset
- setEvents([]);
- };
-
- useEffect(() => {
- const sub = ark.subscribe({
- filter,
- closeOnEose: false,
- cb: (event: NDKEvent) => setEvents((prev) => [...prev, event]),
- });
-
- return () => {
- if (sub) sub.stop();
- };
- }, []);
-
- if (!events.length) return null;
-
- return (
-
-
-
- );
-}
diff --git a/packages/ui/src/column/provider.tsx b/packages/ui/src/column/provider.tsx
deleted file mode 100644
index d37d5cd3..00000000
--- a/packages/ui/src/column/provider.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { LumeColumn } from "@lume/types";
-import { ReactNode, createContext, useContext } from "react";
-
-const ColumnContext = createContext(null);
-
-export function ColumnProvider({
- column,
- children,
-}: { column: LumeColumn; children: ReactNode }) {
- return (
- {children}
- );
-}
-
-export function useColumnContext() {
- const context = useContext(ColumnContext);
- if (!context) {
- throw new Error(
- "Please import Column Provider to use useColumnContext() hook",
- );
- }
- return context;
-}
diff --git a/packages/ui/src/column/root.tsx b/packages/ui/src/column/root.tsx
deleted file mode 100644
index bcafbb5f..00000000
--- a/packages/ui/src/column/root.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { cn } from "@lume/utils";
-import { Resizable } from "re-resizable";
-import { ReactNode, useState } from "react";
-import { MemoryRouter, UNSAFE_LocationContext } from "react-router-dom";
-
-export function ColumnRoot({
- children,
- className,
-}: {
- children: ReactNode;
- className?: string;
-}) {
- const [width, setWidth] = useState(420);
-
- return (
-
- e.preventDefault()}
- onResizeStop={(_e, _direction, _ref, d) => {
- setWidth((prevWidth) => prevWidth + d.width);
- }}
- minWidth={420}
- maxWidth={600}
- className={cn(
- "relative flex flex-col border-r-2 border-neutral-50 hover:border-neutral-100 dark:border-neutral-950 dark:hover:border-neutral-900",
- className,
- )}
- enable={{ right: true }}
- >
-
- {children}
-
-
-
- );
-}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 566e7dcb..8ab1ac2d 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,6 +1,5 @@
export * from "./user";
export * from "./note";
-export * from "./column";
// UI
export * from "./container";
diff --git a/packages/ui/src/note/buttons/zap.tsx b/packages/ui/src/note/buttons/zap.tsx
index 3d00d580..c01dfe82 100644
--- a/packages/ui/src/note/buttons/zap.tsx
+++ b/packages/ui/src/note/buttons/zap.tsx
@@ -1,9 +1,29 @@
+import { useArk } from "@lume/ark";
import { ZapIcon } from "@lume/icons";
+import { toast } from "sonner";
+import { useNoteContext } from "../provider";
export function NoteZap() {
+ const ark = useArk();
+ const event = useNoteContext();
+
+ const zap = async () => {
+ try {
+ const nwc = await ark.load_nwc();
+ if (!nwc) {
+ ark.open_nwc();
+ } else {
+ ark.open_zap(event.id, event.pubkey);
+ }
+ } catch (e) {
+ toast.error(String(e));
+ }
+ };
+
return (