feat: add instant zap
This commit is contained in:
@@ -305,9 +305,7 @@ export class Ark {
|
||||
public async getThreads(id: string) {
|
||||
const eventId = this.getCleanEventId(id);
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
||||
(item) => item.url,
|
||||
);
|
||||
const relayUrls = Array.from(this.ndk.pool.relays.keys());
|
||||
|
||||
try {
|
||||
const rawEvents = (await fetcher.fetchAllEvents(
|
||||
@@ -357,9 +355,7 @@ export class Ark {
|
||||
|
||||
public async getAllRelaysFromContacts({ signal }: { signal: AbortSignal }) {
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||
const connectedRelays = this.ndk.pool
|
||||
.connectedRelays()
|
||||
.map((item) => item.url);
|
||||
const connectedRelays = Array.from(this.ndk.pool.relays.keys());
|
||||
|
||||
try {
|
||||
const relayMap = new Map<string, string[]>();
|
||||
@@ -373,8 +369,6 @@ export class Ark {
|
||||
{ abortSignal: signal },
|
||||
);
|
||||
|
||||
console.log(relayEvents);
|
||||
|
||||
for await (const { author, events } of relayEvents) {
|
||||
if (events.length) {
|
||||
const relayTags = events[0].tags.filter((item) => item[0] === "r");
|
||||
@@ -411,9 +405,7 @@ export class Ark {
|
||||
dedup?: boolean;
|
||||
}) {
|
||||
const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk));
|
||||
const relayUrls = [...this.ndk.pool.relays.values()].map(
|
||||
(item) => item.url,
|
||||
);
|
||||
const relayUrls = Array.from(this.ndk.pool.relays.keys());
|
||||
const seenIds = new Set<string>();
|
||||
const dedupQueue = new Set<string>();
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { webln } from "@getalby/sdk";
|
||||
import { SendPaymentResponse } from "@getalby/sdk/dist/types";
|
||||
import { CancelIcon, ZapIcon } from "@lume/icons";
|
||||
import {
|
||||
compactNumber,
|
||||
displayNpub,
|
||||
sendNativeNotification,
|
||||
} from "@lume/utils";
|
||||
import { type SendPaymentResponse } from "@getalby/sdk/dist/types";
|
||||
import { CancelIcon, LoaderIcon, ZapIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { cn, compactNumber, displayNpub } from "@lume/utils";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import CurrencyInput from "react-currency-input-field";
|
||||
import { toast } from "sonner";
|
||||
import { useArk } from "../../../hooks/useArk";
|
||||
@@ -18,83 +13,98 @@ import { useProfile } from "../../../hooks/useProfile";
|
||||
import { useNoteContext } from "../provider";
|
||||
|
||||
export function NoteZap() {
|
||||
const [walletConnectURL, setWalletConnectURL] = useState<string>(null);
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
const event = useNoteContext();
|
||||
|
||||
const [amount, setAmount] = useState<string>("21");
|
||||
const [zapMessage, setZapMessage] = useState<string>("");
|
||||
const [invoice, setInvoice] = useState<null | string>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const ark = useArk();
|
||||
const event = useNoteContext();
|
||||
|
||||
const { user } = useProfile(event.pubkey);
|
||||
|
||||
const createZapRequest = async () => {
|
||||
const createZapRequest = async (instant?: boolean) => {
|
||||
if (!storage.nwc) return;
|
||||
|
||||
let nwc: webln.NostrWebLNProvider = undefined;
|
||||
|
||||
try {
|
||||
// start loading
|
||||
setIsLoading(true);
|
||||
|
||||
const zapAmount = parseInt(amount) * 1000;
|
||||
const res = await event.zap(zapAmount, zapMessage);
|
||||
|
||||
if (!res) return toast.error("Cannot create zap request");
|
||||
|
||||
// user don't connect nwc, create QR Code for invoice
|
||||
if (!walletConnectURL) return setInvoice(res);
|
||||
|
||||
// user connect nwc
|
||||
nwc = new webln.NostrWebLNProvider({
|
||||
nostrWalletConnectUrl: walletConnectURL,
|
||||
nostrWalletConnectUrl: storage.nwc,
|
||||
});
|
||||
await nwc.enable();
|
||||
|
||||
// start loading
|
||||
setIsLoading(true);
|
||||
|
||||
// send payment via nwc
|
||||
const send: SendPaymentResponse = await nwc.sendPayment(res);
|
||||
|
||||
if (send) {
|
||||
await sendNativeNotification(
|
||||
toast.success(
|
||||
`You've zapped ${compactNumber.format(send.amount)} sats to ${
|
||||
user?.name || user?.displayName || "anon"
|
||||
}`,
|
||||
);
|
||||
|
||||
// eose
|
||||
nwc.close();
|
||||
setIsCompleted(true);
|
||||
setIsLoading(false);
|
||||
|
||||
// reset after 1.5 secs
|
||||
const timeout = setTimeout(() => setIsCompleted(false), 1500);
|
||||
clearTimeout(timeout);
|
||||
if (!instant) {
|
||||
const timeout = setTimeout(() => setIsCompleted(false), 1500);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
// eose
|
||||
nwc.close();
|
||||
|
||||
// update state
|
||||
setIsCompleted(true);
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
nwc?.close();
|
||||
setIsLoading(false);
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getWalletConnectURL() {
|
||||
const uri: string = await invoke("secure_load", {
|
||||
key: `${ark.account.pubkey}-nwc`,
|
||||
});
|
||||
if (uri) setWalletConnectURL(uri);
|
||||
}
|
||||
|
||||
if (isOpen) getWalletConnectURL();
|
||||
|
||||
return () => {
|
||||
setAmount("21");
|
||||
setZapMessage("");
|
||||
setIsCompleted(false);
|
||||
setIsLoading(false);
|
||||
};
|
||||
}, [isOpen]);
|
||||
if (storage.settings.instantZap) {
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createZapRequest(true)}
|
||||
className="inline-flex items-center justify-center group size-7 text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : (
|
||||
<ZapIcon
|
||||
className={cn(
|
||||
"size-5 group-hover:text-blue-500",
|
||||
isCompleted ? "text-blue-500" : "",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||
Zap
|
||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
@@ -135,129 +145,99 @@ export function NoteZap() {
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<div className="px-5 pb-5 overflow-x-hidden overflow-y-auto">
|
||||
{!invoice ? (
|
||||
<>
|
||||
<div className="relative flex flex-col h-40">
|
||||
<div className="inline-flex items-center justify-center flex-1 h-full gap-1">
|
||||
<CurrencyInput
|
||||
placeholder="0"
|
||||
defaultValue={"21"}
|
||||
value={amount}
|
||||
decimalsLimit={2}
|
||||
min={0} // 0 sats
|
||||
max={10000} // 1M sats
|
||||
maxLength={10000} // 1M sats
|
||||
onValueChange={(value) => setAmount(value)}
|
||||
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
||||
/>
|
||||
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
|
||||
sats
|
||||
</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("69")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
69 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("100")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
100 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("200")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
200 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("500")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
500 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("1000")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
1K sats
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full gap-2 mt-4">
|
||||
<input
|
||||
name="zapMessage"
|
||||
value={zapMessage}
|
||||
onChange={(e) => setZapMessage(e.target.value)}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
placeholder="Enter message (optional)"
|
||||
className="w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:text-neutral-400"
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
{walletConnectURL ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createZapRequest()}
|
||||
className="inline-flex items-center justify-center w-full px-4 font-medium text-white bg-blue-500 rounded-lg h-11 hover:bg-blue-600"
|
||||
>
|
||||
{isCompleted ? (
|
||||
<p className="leading-tight">Successfully zapped</p>
|
||||
) : isLoading ? (
|
||||
<span className="flex flex-col">
|
||||
<p className="leading-tight">
|
||||
Waiting for approval
|
||||
</p>
|
||||
<p className="text-xs leading-tight text-neutral-100">
|
||||
Go to your wallet and approve payment request
|
||||
</p>
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex flex-col">
|
||||
<p className="leading-tight">Send zap</p>
|
||||
<p className="text-xs leading-tight text-neutral-100">
|
||||
You're using nostr wallet connect
|
||||
</p>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createZapRequest()}
|
||||
className="inline-flex items-center justify-center w-full px-4 font-medium text-white bg-blue-500 rounded-lg h-11 hover:bg-blue-600"
|
||||
>
|
||||
Create Lightning invoice
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-4 mt-3">
|
||||
<div className="p-3 rounded-md bg-neutral-100 dark:bg-neutral-900">
|
||||
<QRCodeSVG value={invoice} size={256} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<h3 className="text-lg font-medium">Scan to zap</h3>
|
||||
<span className="text-sm text-center text-neutral-600 dark:text-neutral-400">
|
||||
You must use Bitcoin wallet which support Lightning
|
||||
<br />
|
||||
such as: Blue Wallet, Bitkit, Phoenix,...
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative flex flex-col h-40">
|
||||
<div className="inline-flex items-center justify-center flex-1 h-full gap-1">
|
||||
<CurrencyInput
|
||||
placeholder="0"
|
||||
defaultValue={"21"}
|
||||
value={amount}
|
||||
decimalsLimit={2}
|
||||
min={0} // 0 sats
|
||||
max={10000} // 1M sats
|
||||
maxLength={10000} // 1M sats
|
||||
onValueChange={(value) => setAmount(value)}
|
||||
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
||||
/>
|
||||
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
|
||||
sats
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="inline-flex items-center justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("69")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
69 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("100")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
100 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("200")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
200 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("500")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
500 sats
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount("1000")}
|
||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
1K sats
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full gap-2 mt-4">
|
||||
<input
|
||||
name="zapMessage"
|
||||
value={zapMessage}
|
||||
onChange={(e) => setZapMessage(e.target.value)}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
placeholder="Enter message (optional)"
|
||||
className="w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:text-neutral-400"
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createZapRequest()}
|
||||
className="inline-flex items-center justify-center w-full px-4 font-medium text-white bg-blue-500 rounded-lg h-11 hover:bg-blue-600"
|
||||
>
|
||||
{isCompleted ? (
|
||||
<p className="leading-tight">Successfully zapped</p>
|
||||
) : isLoading ? (
|
||||
<span className="flex flex-col">
|
||||
<p className="leading-tight">Waiting for approval</p>
|
||||
<p className="text-xs leading-tight text-neutral-100">
|
||||
Go to your wallet and approve payment request
|
||||
</p>
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex flex-col">
|
||||
<p className="leading-tight">Send zap</p>
|
||||
<p className="text-xs leading-tight text-neutral-100">
|
||||
You're using nostr wallet connect
|
||||
</p>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -38,7 +38,7 @@ export function NoteContent({
|
||||
|
||||
const [content, setContent] = useState(event.content);
|
||||
const [translate, setTranslate] = useState({
|
||||
translatable: true,
|
||||
translatable: false,
|
||||
translated: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export function LinkPreview({ url }: { url: string }) {
|
||||
to={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex flex-col w-full my-1 overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-900"
|
||||
className="flex flex-col w-full my-1 overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-900 border border-black/5 dark:border-white/5"
|
||||
>
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
|
||||
@@ -12,7 +12,13 @@ export function VideoPreview({ url }: { url: string }) {
|
||||
return (
|
||||
<div className="my-1 w-full rounded-lg overflow-hidden">
|
||||
<MediaController>
|
||||
<video slot="media" src={url} preload="auto" muted />
|
||||
<video
|
||||
slot="media"
|
||||
src={url}
|
||||
preload="auto"
|
||||
muted
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
<MediaControlBar>
|
||||
<MediaPlayButton />
|
||||
<MediaTimeRange />
|
||||
|
||||
@@ -6,13 +6,13 @@ export function ChildReply({
|
||||
}: { event: NDKEvent; rootEventId?: string }) {
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root className="pl-6">
|
||||
<Note.Root className="py-2">
|
||||
<div className="flex items-center justify-between h-14">
|
||||
<Note.User className="flex-1" />
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<Note.Content />
|
||||
<div className="flex items-center justify-end gap-10 mt-4">
|
||||
<div className="flex items-center justify-end gap-4 mt-2">
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
|
||||
@@ -37,14 +37,20 @@ export function Reply({
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="inline-flex items-center gap-10">
|
||||
<div className="inline-flex items-center gap-4">
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("", open ? "pb-3" : "")}>
|
||||
<div
|
||||
className={cn(
|
||||
open
|
||||
? "pb-3 border-t border-neutral-100 dark:border-neutral-900"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{event.replies?.length > 0 ? (
|
||||
<Collapsible.Content>
|
||||
<Collapsible.Content className="divide-y divide-neutral-100 dark:divide-neutral-900 pl-6">
|
||||
{event.replies?.map((childEvent) => (
|
||||
<ChildReply key={childEvent.id} event={childEvent} />
|
||||
))}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function ThreadNote({ eventId }: { eventId: string }) {
|
||||
<Note.Content className="min-w-0 px-3" />
|
||||
<div className="flex items-center justify-between px-3 h-14">
|
||||
<Note.Pin />
|
||||
<div className="inline-flex items-center gap-10">
|
||||
<div className="inline-flex items-center gap-4">
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ export class LumeStorage {
|
||||
readonly platform: Platform;
|
||||
readonly locale: string;
|
||||
public currentUser: Account;
|
||||
public nwc: string;
|
||||
public settings: {
|
||||
autoupdate: boolean;
|
||||
nsecbunker: boolean;
|
||||
@@ -29,12 +30,14 @@ export class LumeStorage {
|
||||
lowPower: boolean;
|
||||
translation: boolean;
|
||||
translateApiKey: string;
|
||||
instantZap: boolean;
|
||||
};
|
||||
|
||||
constructor(db: Database, platform: Platform, locale: string) {
|
||||
this.#db = db;
|
||||
this.locale = locale;
|
||||
this.platform = platform;
|
||||
this.nwc = null;
|
||||
this.settings = {
|
||||
autoupdate: false,
|
||||
nsecbunker: false,
|
||||
@@ -45,6 +48,7 @@ export class LumeStorage {
|
||||
lowPower: false,
|
||||
translation: false,
|
||||
translateApiKey: "",
|
||||
instantZap: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,6 +65,8 @@ export class LumeStorage {
|
||||
|
||||
const account = await this.getActiveAccount();
|
||||
if (account) this.currentUser = account;
|
||||
|
||||
this.nwc = await this.loadPrivkey("Nostr Wallet Connect");
|
||||
}
|
||||
|
||||
async #keyring_save(key: string, value: string) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
AdvancedSettingsIcon,
|
||||
InfoIcon,
|
||||
NwcIcon,
|
||||
SecureIcon,
|
||||
SettingsIcon,
|
||||
UserIcon,
|
||||
ZapIcon,
|
||||
} from "@lume/icons";
|
||||
import { cn } from "@lume/utils";
|
||||
import { NavLink, Outlet } from "react-router-dom";
|
||||
@@ -13,7 +13,7 @@ export function SettingsLayout() {
|
||||
return (
|
||||
<div className="flex h-full min-h-0 w-full flex-col rounded-xl overflow-y-auto">
|
||||
<div className="flex h-24 shrink-0 w-full items-center justify-center px-2 bg-white/50 backdrop-blur-xl dark:bg-black/50">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<NavLink
|
||||
end
|
||||
to="/settings/"
|
||||
@@ -55,8 +55,8 @@ export function SettingsLayout() {
|
||||
)
|
||||
}
|
||||
>
|
||||
<NwcIcon className="size-6" />
|
||||
<p className="text-sm font-medium">Wallet</p>
|
||||
<ZapIcon className="size-6" />
|
||||
<p className="text-sm font-medium">Zap</p>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/settings/backup"
|
||||
|
||||
Reference in New Issue
Block a user