feat: polish

This commit is contained in:
2024-01-18 15:09:16 +07:00
parent 0e9418949b
commit ed6423e4aa
6 changed files with 176 additions and 141 deletions

View File

@@ -5,15 +5,14 @@ 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 { QRCodeSVG } from "qrcode.react";
import { useState } from "react";
import CurrencyInput from "react-currency-input-field";
import { toast } from "sonner";
import { useArk } from "../../../hooks/useArk";
import { useProfile } from "../../../hooks/useProfile";
import { useNoteContext } from "../provider";
export function NoteZap() {
const ark = useArk();
const storage = useStorage();
const event = useNoteContext();
@@ -22,11 +21,12 @@ export function NoteZap() {
const [isOpen, setIsOpen] = useState(false);
const [isCompleted, setIsCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [invoice, setInvoice] = useState<string>(null);
const { user } = useProfile(event.pubkey);
const createZapRequest = async (instant?: boolean) => {
if (!storage.nwc) return;
if (instant && !storage.nwc) return;
let nwc: webln.NostrWebLNProvider = undefined;
@@ -37,6 +37,8 @@ export function NoteZap() {
const zapAmount = parseInt(amount) * 1000;
const res = await event.zap(zapAmount, zapMessage);
if (!storage.nwc) return setInvoice(res);
// user connect nwc
nwc = new webln.NostrWebLNProvider({
nostrWalletConnectUrl: storage.nwc,
@@ -144,101 +146,105 @@ export function NoteZap() {
<CancelIcon className="w-4 h-4" />
</Dialog.Close>
</div>
<div className="px-5 pb-5 overflow-x-hidden overflow-y-auto">
<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"
{!invoice ? (
<div className="px-5 pb-5 overflow-x-hidden overflow-y-auto">
<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"
/>
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
sats
<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
? "Zapped"
: isLoading
? "Processing..."
: "Zap"}
</button>
</div>
</div>
</div>
) : (
<div className="px-5 pb-5 flex flex-col items-center justify-center gap-4">
<div className="rounded-lg bg-neutral-100 p-3 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-center text-sm 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="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&apos;re using nostr wallet connect
</p>
</span>
)}
</button>
</div>
</div>
</div>
)}
</div>
</Dialog.Content>
</Dialog.Portal>

View File

@@ -1,7 +1,12 @@
import { LoaderIcon } from "@lume/icons";
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
import { useStorage } from "@lume/storage";
import { FETCH_LIMIT, QUOTES, sendNativeNotification } from "@lume/utils";
import {
FETCH_LIMIT,
QUOTES,
activityUnreadAtom,
sendNativeNotification,
} from "@lume/utils";
import NDK, {
NDKEvent,
NDKKind,
@@ -14,6 +19,7 @@ import NDK, {
import { useQueryClient } from "@tanstack/react-query";
import { message } from "@tauri-apps/plugin-dialog";
import { fetch } from "@tauri-apps/plugin-http";
import { useSetAtom } from "jotai";
import Linkify from "linkify-react";
import { normalizeRelayUrlSet } from "nostr-fetch";
import { PropsWithChildren, useEffect, useState } from "react";
@@ -23,6 +29,7 @@ import { LumeContext } from "./context";
export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
const storage = useStorage();
const queryClient = useQueryClient();
const setUnreadActivity = useSetAtom(activityUnreadAtom);
const [ark, setArk] = useState<Ark>(undefined);
const [ndk, setNDK] = useState<NDK>(undefined);
@@ -66,6 +73,11 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
const userPrivkey = await storage.loadPrivkey(storage.currentUser.pubkey);
if (!userPrivkey) return null;
// load nwc
storage.nwc = await storage.loadPrivkey(
`${storage.currentUser.pubkey}.nwc`,
);
return new NDKPrivateKeySigner(userPrivkey);
} catch (e) {
console.error(e);
@@ -99,7 +111,7 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
enableOutboxModel: !storage.settings.lowPower,
autoConnectUserRelays: !storage.settings.lowPower,
autoFetchUserMutelist: !storage.settings.lowPower,
// clientName: "Lume",
clientName: "Lume",
// clientNip89: '',
});
@@ -120,7 +132,7 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
ndk.relayAuthDefaultPolicy = async (relay: NDKRelay, challenge: string) => {
const signIn = NDKRelayAuthPolicies.signIn({ ndk });
const event = await signIn(relay, challenge).catch((e) =>
console.error(e),
console.log("auth failed", e),
);
if (event) {
await sendNativeNotification(
@@ -147,7 +159,7 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
await ark.getUserContacts();
// subscribe for new activity
const notifySub = ndk.subscribe(
const activitySub = ndk.subscribe(
{
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
since: Math.floor(Date.now() / 1000),
@@ -156,25 +168,26 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
{ closeOnEose: false, groupable: false },
);
notifySub.addListener("event", async (event: NDKEvent) => {
activitySub.addListener("event", async (event: NDKEvent) => {
setUnreadActivity((state) => state + 1);
const profile = await ark.getUserProfile(event.pubkey);
switch (event.kind) {
case NDKKind.Text:
return await sendNativeNotification(
`${
profile.displayName || profile.name || "anon"
profile.displayName || profile.name || "Anon"
} has replied to your note`,
);
case NDKKind.Repost:
return await sendNativeNotification(
`${
profile.displayName || profile.name || "anon"
profile.displayName || profile.name || "Anon"
} has reposted to your note`,
);
case NDKKind.Zap:
return await sendNativeNotification(
`${
profile.displayName || profile.name || "anon"
profile.displayName || profile.name || "Anon"
} has zapped to your note`,
);
default: